package cn.koolcloud.ipos.appstore.cache;
import java.io.BufferedInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
import cn.koolcloud.ipos.appstore.api.ApiService;
import cn.koolcloud.ipos.appstore.cache.BindDataIf.BindHolder;
import cn.koolcloud.ipos.appstore.cache.BindDataIf.Callback;
import cn.koolcloud.ipos.appstore.cache.base.AsyncTask;
import cn.koolcloud.ipos.appstore.common.AsyncHttpClient;
import cn.koolcloud.ipos.appstore.utils.Logger;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.ImageView;
/**
* download images.
* this class include memory image cache, file object cache, and the files in the sdcard.
* @author Teddy
* @Create 2013-10-29
*/
public class ImageDownloader {
private static final String LOG_TAG = ImageDownloader.class.getSimpleName();
/**
* the default memory size of LruMemoryCache
*/
private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 1024 * 6;
/**
* the times of retrying to download images.
*/
private static final int IMAGE_RETRY_TIMES = 3;
private ExecutorService executorService;
ImageFileCache imageFileCache;
private LruMemoryCache<String, Bitmap> mLruMemoryCache;
private static ImageDownloader instance;
private Context mContext;
public static ImageDownloader getInstance(Context context) {
if (instance == null) {
synchronized(ImageDownloader.class) {
if (instance == null)
instance = new ImageDownloader(context);
}
}
return instance;
}
private ImageDownloader(Context context) {
mContext = context;
int cpuNums = Runtime.getRuntime().availableProcessors();
executorService = Executors.newFixedThreadPool(cpuNums * 10);//thread pool
imageFileCache = new ImageFileCache(context);
mLruMemoryCache = new LruMemoryCache<String, Bitmap>(DEFAULT_MEM_CACHE_SIZE) {
/**
* Measure item size in bytes rather than units which is more practical
* for a bitmap cache
*/
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return getBitmapSize(bitmap);
}
};
}
private static int getBitmapSize(Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
/**
* release the memory of mLruMemoryCache
*/
public void release(){
mLruMemoryCache.clearCache();
}
/**
* delete the image files from sd
*/
public void deleteImagesOfSd(){
imageFileCache.deleteAll();
}
/**
* Download the specified image from the Internet and binds it to the provided ImageView. The
* binding is immediate if the image is found in the cache and will be done asynchronously
* otherwise. A null bitmap will be associated to the ImageView if an error occurs.
*
* @param url The URL of the image to download.
* @param imageView The ImageView to bind the downloaded image to.
*/
@Deprecated
public void download(BindHolder holder, Callback callback) {
String url = holder.getUrl();
if(null == url || "".equals(url)) {
return;
}
// TO DO : sbh : don't store bitmap object in cache , use local files (compressed cache)
//Bitmap bitmap = imageMemoryCache.getBitmapFromCache(url);
Bitmap bitmap = mLruMemoryCache.get(url);
if(bitmap == null) {
executorService.submit(new ImageTask(new ImageTaskHandler(callback), url, holder));
} else {
Logger.d(" return image from the cache, " + bitmap+" from url ="+url);
holder.setResource(bitmap);
callback.callback(holder);
}
}
/**
* get one image from three places, imageMemoryCache,imageFileCache,network
*
* @param url The URL of the image to download.
* @return Bitmap return a bitmap
*/
private Bitmap getBitmap(final String url) {
if(null == url || "".equals(url)) {
return null;
}
// get image from mLruMemoryCache
Bitmap result = null;
result = mLruMemoryCache.get(url);
if (result == null) {
// get image from imageFileCache
result = imageFileCache.getImage(url);
if (result == null) {
// get image from network
// result = getImageHttp(url);
result = getImageFromHttp(url);
if (result != null) {
if (mLruMemoryCache.get(url) == null) {
mLruMemoryCache.put(url, result);
}
imageFileCache.addImgToSDTask(url, result);
}
} else {
if (mLruMemoryCache.get(url) == null) {
mLruMemoryCache.put(url, result);
}
}
}
return result;
}
/**
* download image from url through the Internet
* @param url
* @return Bitmap from http
*/
private Bitmap getImageHttp(String url) {
//try to get image from file cache
// Logger.debug(this, "Load image from network " + url);
Bitmap bitmap;
int times = 0;
while (times < IMAGE_RETRY_TIMES) {
try {
URL u = new URL(url);
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream inputStream = conn.getInputStream();
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = new FlushedInputStream(inputStream);
bitmap = BitmapFactory.decodeStream(is, null, opt);
is.close();
inputStream.close();
return bitmap;
} catch (Exception e) {
Logger.w("getImageHttp=" + url + e);
times ++;
}
continue;
}
return null;
} // end of downloadBitmap
/**
* download image from url through the Internet
* @param fileName
* @return Bitmap from http
*/
private Bitmap getImageFromHttp(String fileName) {
//try to get image from file cache
// Logger.debug(this, "Load image from network " + fileName);
Bitmap bitmap = null;
int times = 0;
String urlString = ApiService.getDownloadPictureUrl();
InputStream in = null;
String[] strArray = fileName.split("_");
JSONObject request = ApiService.getDownloadPicJson(strArray[0], mContext);
// HttpClient client = new DefaultHttpClient();
HttpClient client = AsyncHttpClient.getDefaultHttpClient(); //https request
HttpPost req = new HttpPost(urlString);
while (times < IMAGE_RETRY_TIMES) {
try {
req.setHeader("Content-Type", "application/json;charset=UTF8");
req.setEntity(new StringEntity(request.toString()));
HttpResponse response = client.execute(req);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
in = new BufferedInputStream(response.getEntity().getContent());
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = new FlushedInputStream(in);
bitmap = BitmapFactory.decodeStream(is, null, opt);
is.close();
in.close();
} else {
Logger.d(LOG_TAG + "_" + "error: "
+ response.getStatusLine().getStatusCode());
}
return bitmap;
} catch (Exception e) {
Logger.w("getImageHttp=" + fileName + e);
times ++;
}
continue;
}
return null;
} // end of downloadBitmap
static class FlushedInputStream extends FilterInputStream {
public FlushedInputStream(InputStream inputStream) {
super(inputStream);
}
@Override
public long skip(long n) throws IOException {
long totalBytesSkipped = 0L;
while (totalBytesSkipped < n) {
long bytesSkipped = in.skip(n - totalBytesSkipped);
if (bytesSkipped == 0L) {
int b = read();
if (b < 0) {
break; // we reached EOF
} else {
bytesSkipped = 1; // we read one byte
}
}
totalBytesSkipped += bytesSkipped;
}
return totalBytesSkipped;
}
} // end of FlushedInputStream
private class ImageTaskHandler extends Handler {
BindDataIf.Callback callback;
public ImageTaskHandler(BindDataIf.Callback callback) {
this.callback = callback;
}
@Override
public void handleMessage(Message msg) {
if (msg.obj != null) {
BindHolder holder = (BindHolder) msg.obj;
callback.callback(holder);
}
}
}
private class ImageTask implements Callable<String> {
private String url;
private Handler handler;
private BindHolder holder;
public ImageTask(Handler handler, String url, BindHolder holder) {
this.url = url;
this.handler = handler;
this.holder = holder;
}
@Override
public String call() throws Exception {
Message msg = new Message();
if(holder != null) {
holder.setResource(getBitmap(url));
msg.obj = holder;
if (msg.obj != null) {
handler.sendMessage(msg);
}
}
return url;
}
}
public void deleteAllImages() {
imageFileCache.deleteAll();
}
/**
* used for ImageView to download images.
* <p>if you use "ViewHolder" and "convertView.getTag()" in listView or gridView, you must use this method'.</p>
* <p>for image, if you just use bitmap from this method, you don't need to recycle this bitmap.
* if you create a temporary bitmap, you must recycle it by yourself.</p>
* @param url
* @param defaultBitmap
* @param imageView
*/
public void download(String url, Bitmap defaultBitmap, ImageView imageView) {
if (null == url || "".equals(url) || "null".equals(url)) {
return ;
}
Bitmap bitmap = null;
//bitmap = imageMemoryCache.getBitmapFromSoftCache(url);
//bitmap = memoryCache.get(url);
bitmap = mLruMemoryCache.get(url);
if (bitmap != null) {
// Logger.debug(this,"get image from memory cache, " + bitmap + " from url =" + url);
imageView.setImageBitmap(bitmap);
} else if (checkImageTask(url, imageView)) {
final BitmapLoadAndDisplayTask task = new BitmapLoadAndDisplayTask(
imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(
mContext.getResources(), defaultBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.executeOnExecutor(executorService, url);
}
}
/**
* check whether the imageView has it's task.
*
* @param data
* @param imageView
* @return true no task
*/
private static boolean checkImageTask(Object data, ImageView imageView) {
final BitmapLoadAndDisplayTask bitmapWorkerTask = getBitmapTaskFromImageView(imageView);
if (bitmapWorkerTask != null) {
final Object bitmapData = bitmapWorkerTask.data;
if (bitmapData == null || !bitmapData.equals(data)) {
bitmapWorkerTask.cancel(true);
} else {
return false;
}
}
return true;
}
private static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapLoadAndDisplayTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap defaultBitmap, BitmapLoadAndDisplayTask bitmapWorkerTask) {
super(res, defaultBitmap);
bitmapWorkerTaskReference = new WeakReference<BitmapLoadAndDisplayTask>(
bitmapWorkerTask);
}
public BitmapLoadAndDisplayTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
private class BitmapLoadAndDisplayTask extends
AsyncTask<Object, Void, Bitmap> {
private Object data;
private final WeakReference<ImageView> imageViewReference;
public BitmapLoadAndDisplayTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(Object... params) {
data = params[0];
final String dataString = String.valueOf(data);
Bitmap bitmap = null;
/*synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
mPauseWorkLock.wait();
} catch (InterruptedException e) {
}
}
}*/
if (!isCancelled() && getAttachedImageView() != null) {
bitmap = getBitmap(dataString);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()/* || mExitTasksEarly*/) {
bitmap = null;
}
final ImageView imageView = getAttachedImageView();
if (bitmap != null && imageView != null) {
imageView.setImageBitmap(bitmap);
}/* else if (bitmap == null && imageView != null) {
}*/
}
@Override
protected void onCancelled(Bitmap bitmap) {
super.onCancelled(bitmap);
}
/**
* get ImageView that matched with it's thread. avoid repeated images and glint.
*
* @return
*/
private ImageView getAttachedImageView() {
final ImageView imageView = imageViewReference.get();
final BitmapLoadAndDisplayTask bitmapWorkerTask = getBitmapTaskFromImageView(imageView);
if (this == bitmapWorkerTask) {
return imageView;
}
return null;
}
}
private static BitmapLoadAndDisplayTask getBitmapTaskFromImageView(
ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
}