package in.srain.cube.image;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import in.srain.cube.app.lifecycle.LifeCycleComponent;
import in.srain.cube.concurrent.SimpleTask;
import in.srain.cube.image.iface.ImageLoadHandler;
import in.srain.cube.image.iface.ImageResizer;
import in.srain.cube.image.iface.ImageTaskExecutor;
import in.srain.cube.util.Debug;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
/**
* @author http://www.liaohuqiu.net
*/
public class ImageLoader implements LifeCycleComponent {
private static final String MSG_ATTACK_TO_RUNNING_TASK = "%s attach to running: %s";
private static final String MSG_TASK_DO_IN_BACKGROUND = "%s doInBackground";
private static final String MSG_TASK_FINISH = "%s onFinish";
private static final String MSG_TASK_CANCEL = "%s onCancel";
private static final String MSG_TASK_HIT_CACHE = "%s hit cache %s %s";
protected static final boolean DEBUG = Debug.DEBUG_IMAGE;
protected static final String Log_TAG = "cube_image";
protected ImageTaskExecutor mImageTaskExecutor;
protected ImageResizer mImageResizer;
protected ImageProvider mImageProvider;
protected ImageLoadHandler mImageLoadHandler;
protected boolean mPauseWork = false;
protected boolean mExitTasksEarly = false;
private final Object mPauseWorkLock = new Object();
private HashMap<String, LoadImageTask> mLoadWorkList;
protected Context mContext;
protected Resources mResources;
public enum ImageTaskOrder {
FIRST_IN_FIRST_OUT, LAST_IN_FIRST_OUT
}
public ImageLoader(Context context, ImageProvider imageProvider, ImageTaskExecutor imageTaskExecutor, ImageResizer imageResizer, ImageLoadHandler imageLoadHandler) {
mContext = context;
mResources = context.getResources();
mImageProvider = imageProvider;
mImageTaskExecutor = imageTaskExecutor;
mImageResizer = imageResizer;
mImageLoadHandler = imageLoadHandler;
mLoadWorkList = new HashMap<String, LoadImageTask>();
}
public void setImageLoadHandler(ImageLoadHandler imageLoadHandler) {
mImageLoadHandler = imageLoadHandler;
}
public ImageLoadHandler getImageLoadHandler() {
return mImageLoadHandler;
}
public void setImageResizer(ImageResizer resizer) {
mImageResizer = resizer;
}
public ImageResizer getImageResizer() {
return mImageResizer;
}
public ImageProvider getImageProvider() {
return mImageProvider;
}
/**
* Load the image in advance.
*/
public void preLoadImages(String[] urls) {
int len = urls.length;
len = 10;
for (int i = 0; i < len; i++) {
final ImageTask imageTask = createImageTask(urls[i], 0, 0, null);
addImageTask(imageTask, null);
}
}
/**
* Create an ImageTask.
* <p/>
* You can override this method to return a customized ImagetTask.
*
* @param url
* @param requestWidth
* @param requestHeight
* @param imageReuseInfo
* @return
*/
public ImageTask createImageTask(String url, int requestWidth, int requestHeight, ImageReuseInfo imageReuseInfo) {
ImageTask imageTask = ImageTask.obtain();
if (imageTask == null) {
imageTask = new ImageTask();
}
imageTask.renew().setOriginUrl(url).setRequestSize(requestWidth, requestHeight).setReuseInfo(imageReuseInfo);
return imageTask;
}
/**
* Detach the ImageView from the ImageTask.
*
* @param imageTask
* @param imageView
*/
public void detachImageViewFromImageTask(ImageTask imageTask, CubeImageView imageView) {
imageTask.removeImageView(imageView);
if (imageTask.isLoading()) {
if (!imageTask.isPreLoad() && !imageTask.stillHasRelatedImageView()) {
LoadImageTask task = mLoadWorkList.get(imageTask.getIdentityKey());
if (task != null) {
task.cancel(true);
}
if (DEBUG) {
Log.d(Log_TAG, String.format("%s previous work is cancelled.", imageTask));
}
}
}
if (!imageTask.stillHasRelatedImageView()) {
imageTask.tryToRecycle();
}
}
/**
* Add the ImageTask into loading list.
*
* @param imageTask
* @param imageView
*/
public void addImageTask(ImageTask imageTask, CubeImageView imageView) {
LoadImageTask runningTask = mLoadWorkList.get(imageTask.getIdentityKey());
if (runningTask != null) {
if (imageView != null) {
if (DEBUG) {
Log.d(Log_TAG, String.format(MSG_ATTACK_TO_RUNNING_TASK, imageTask, runningTask.getImageTask()));
}
runningTask.getImageTask().addImageView(imageView);
}
return;
} else {
imageTask.addImageView(imageView);
}
imageTask.onLoading(mImageLoadHandler);
LoadImageTask loadImageTask = new LoadImageTask(imageTask);
mLoadWorkList.put(imageTask.getIdentityKey(), loadImageTask);
mImageTaskExecutor.execute(loadImageTask);
}
/**
* Check weather this imageTask has cache Drawable data.
*/
public boolean queryCache(ImageTask imageTask, CubeImageView imageView) {
if (null == mImageProvider) {
return false;
}
BitmapDrawable drawable = mImageProvider.getBitmapFromMemCache(imageTask);
if (imageTask.getStatistics() != null) {
imageTask.getStatistics().afterMemoryCache(drawable != null);
}
if (drawable == null) {
return false;
}
if (DEBUG) {
Log.d(Log_TAG, String.format(MSG_TASK_HIT_CACHE, imageTask, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()));
if (drawable.getIntrinsicWidth() == 270) {
Log.d(Log_TAG, String.format(MSG_TASK_HIT_CACHE, imageTask, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()));
}
}
imageTask.addImageView(imageView);
imageTask.onLoadFinish(drawable, mImageLoadHandler);
return true;
}
public void setTaskOrder(ImageTaskOrder order) {
if (null != mImageTaskExecutor) {
mImageTaskExecutor.setTaskOrder(order);
}
}
/**
* Inner class to process the image loading task in background threads.
*
* @author http://www.liaohuqiu.net
*/
private class LoadImageTask extends SimpleTask {
private ImageTask mImageTask;
private BitmapDrawable mDrawable;
public LoadImageTask(ImageTask imageTask) {
this.mImageTask = imageTask;
}
public ImageTask getImageTask() {
return mImageTask;
}
@Override
public void doInBackground() {
if (DEBUG) {
Log.d(Log_TAG, String.format(MSG_TASK_DO_IN_BACKGROUND, mImageTask));
}
if (mImageTask.getStatistics() != null) {
mImageTask.getStatistics().beginLoad();
}
Bitmap bitmap = null;
// Wait here if work is paused and the task is not cancelled
synchronized (mPauseWorkLock) {
while (mPauseWork && !isCancelled()) {
try {
if (DEBUG) {
Log.d(Log_TAG, String.format("%s wait to begin", mImageTask));
}
mPauseWorkLock.wait();
} catch (InterruptedException e) {
}
}
}
// If this task has not been cancelled by another
// thread and the ImageView that was originally bound to this task is still bound back
// to this task and our "exit early" flag is not set then try and fetch the bitmap from
// the cache
if (!isCancelled() && !mExitTasksEarly && (mImageTask.isPreLoad() || mImageTask.stillHasRelatedImageView())) {
try {
bitmap = mImageProvider.fetchBitmapData(mImageTask, mImageResizer);
if (mImageTask.getStatistics() != null) {
mImageTask.getStatistics().afterDecode();
}
mDrawable = mImageProvider.createBitmapDrawable(mResources, bitmap);
mImageProvider.addBitmapToMemCache(mImageTask.getIdentityKey(), mDrawable);
if (mImageTask.getStatistics() != null) {
mImageTask.getStatistics().afterCreateBitmapDrawable();
}
} catch (Exception e) {
e.printStackTrace();
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
}
}
@Override
public void onFinish() {
if (DEBUG) {
Log.d(Log_TAG, String.format(MSG_TASK_FINISH, mImageTask));
}
if (mExitTasksEarly) {
return;
}
mLoadWorkList.remove(mImageTask.getIdentityKey());
if (!isCancelled() && !mExitTasksEarly) {
mImageTask.onLoadFinish(mDrawable, mImageLoadHandler);
}
mImageTask.tryToRecycle();
}
@Override
public void onCancel() {
if (DEBUG) {
Log.d(Log_TAG, String.format(MSG_TASK_CANCEL, mImageTask));
}
mLoadWorkList.remove(mImageTask.getIdentityKey());
mImageTask.onCancel();
mImageTask.tryToRecycle();
}
}
private void setPause(boolean pause) {
synchronized (mPauseWorkLock) {
mPauseWork = pause;
if (!pause) {
mPauseWorkLock.notifyAll();
}
}
}
/**
* Temporarily hand up work, you can call this when the view is scrolling.
*/
public void pauseWork() {
mExitTasksEarly = false;
setPause(true);
if (DEBUG) {
Log.d(Log_TAG, String.format("work_status: pauseWork %s", this));
}
}
/**
* Resume the work
*/
public void resumeWork() {
mExitTasksEarly = false;
setPause(false);
if (DEBUG) {
Log.d(Log_TAG, String.format("work_status: resumeWork %s", this));
}
}
/**
* Recover the from the work list
*/
public void recoverWork() {
if (DEBUG) {
Log.d(Log_TAG, String.format("work_status: recoverWork %s", this));
}
mExitTasksEarly = false;
setPause(false);
Iterator<Entry<String, LoadImageTask>> it = (Iterator<Entry<String, LoadImageTask>>) mLoadWorkList.entrySet().iterator();
while (it.hasNext()) {
Entry<String, LoadImageTask> item = it.next();
LoadImageTask task = item.getValue();
task.restart();
mImageTaskExecutor.execute(task);
}
}
/**
* Drop all the work, and leave it in the work list.
*/
public void stopWork() {
if (DEBUG) {
Log.d(Log_TAG, String.format("work_status: stopWork %s", this));
}
mExitTasksEarly = true;
setPause(false);
if (null != mImageProvider) {
mImageProvider.flushFileCache();
}
}
/**
* Drop all the work, clear the work list.
*/
public void destroy() {
mExitTasksEarly = true;
setPause(false);
Iterator<Entry<String, LoadImageTask>> it = (Iterator<Entry<String, LoadImageTask>>) mLoadWorkList.entrySet().iterator();
while (it.hasNext()) {
Entry<String, LoadImageTask> item = it.next();
final LoadImageTask task = item.getValue();
it.remove();
if (task != null) {
task.cancel(true);
}
}
mLoadWorkList.clear();
}
/**
* The UI becomes partially invisible.
* like {@link android.app.Activity#onPause}
*/
@Override
public void onBecomesPartiallyInvisible() {
pauseWork();
}
/**
* The UI becomes visible from partially invisible.
* like {@link android.app.Activity#onResume}
*/
@Override
public void onBecomesVisible() {
resumeWork();
}
/**
* The UI becomes totally invisible.
* like {@link android.app.Activity#onStop}
*/
@Override
public void onBecomesTotallyInvisible() {
stopWork();
}
/**
* The UI becomes visible from totally invisible.
* like {@link android.app.Activity#onRestart}
*/
@Override
public void onBecomesVisibleFromTotallyInvisible() {
recoverWork();
}
/**
* like {@link android.app.Activity#onDestroy}
*/
@Override
public void onDestroy() {
destroy();
}
}