package in.srain.cube.image;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import in.srain.cube.image.iface.ImageLoadHandler;
import in.srain.cube.util.CLog;
import in.srain.cube.util.Debug;
import in.srain.cube.util.Encrypt;
import java.lang.ref.WeakReference;
/**
* A wrapper of the related information used in loading a bitmap
*
* @author http://www.liaohuqiu.net
*/
public class ImageTask {
protected static final String Log_TAG = "cube_image_task";
private static final Object sPoolSync = new Object();
private static ImageTask sTop;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 20;
private static boolean USE_POOL = false;
private static int sId = 0;
private final static String SIZE_SP = "_";
private final static int STATUS_PRE_LOAD = 0x01;
private final static int STATUS_LOADING = 0x02;
private final static int STATUS_DONE = 0x04;
private final static int STATUS_CANCELED = 0x08;
private int mFlag = 0;
protected int mId = 0;
// the origin request url for the image.
protected String mOriginUrl;
// In some situations, we may store the same image in some different servers. So the same image will related to some different urls.
private String mIdentityUrl;
// The key related to the image this ImageTask is requesting.
private String mIdentityKey;
// cache for toString();
private String mStr;
protected Point mRequestSize = new Point();
protected Point mBitmapOriginSize = new Point();
protected ImageReuseInfo mReuseInfo;
protected ImageViewHolder mFirstImageViewHolder;
protected ImageTaskStatistics mImageTaskStatistics;
ImageTask next;
protected void clearForRecycle() {
mFlag = 0;
mOriginUrl = null;
mIdentityUrl = null;
mIdentityKey = null;
mStr = null;
mRequestSize.set(0, 0);
mBitmapOriginSize.set(0, 0);
mReuseInfo = null;
mFirstImageViewHolder = null;
mImageTaskStatistics = null;
}
public static ImageTask obtain() {
if (!USE_POOL) {
return null;
}
// pop top, make top.next as top
synchronized (sPoolSync) {
if (sTop != null) {
ImageTask m = sTop;
sTop = m.next;
m.next = null;
sPoolSize--;
return m;
}
if (Debug.DEBUG_IMAGE) {
CLog.d(Log_TAG, "obtain, pool remain: %d", sPoolSize);
}
}
return null;
}
public void tryToRecycle() {
if (!USE_POOL) {
return;
}
clearForRecycle();
// mark top as the next of current, then push current as pop
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sTop;
sTop = this;
sPoolSize++;
}
if (Debug.DEBUG_IMAGE) {
CLog.d(Log_TAG, "recycle, pool remain: %d", sPoolSize);
}
}
}
public ImageTask renew() {
mId = ++sId;
if (ImagePerformanceStatistics.sample(mId)) {
mImageTaskStatistics = new ImageTaskStatistics();
}
return this;
}
public ImageTask setOriginUrl(String originUrl) {
mOriginUrl = originUrl;
return this;
}
public ImageTask setRequestSize(int requestWidth, int requestHeight) {
mRequestSize.set(requestWidth, requestHeight);
return this;
}
public ImageTask setReuseInfo(ImageReuseInfo imageReuseInfo) {
mReuseInfo = imageReuseInfo;
return this;
}
/**
* For accessing the identity url
*
* @return
*/
public String getIdentityUrl() {
if (null == mIdentityUrl) {
mIdentityUrl = generateIdentityUrl(mOriginUrl);
}
return mIdentityUrl;
}
/**
* In some situations, we may store the same image in some different servers. So the same image will related to some different urls.
* <p/>
* Generate the identity url according your situation.
* <p/>
* {@link #mIdentityUrl}
*
* @return
*/
protected String generateIdentityUrl(String originUrl) {
return originUrl;
}
/**
* Generate the identity key.
* <p/>
* This key should be related to the unique image this task is requesting: the size, the remote url.
*
* @return
*/
protected String generateIdentityKey() {
if (mReuseInfo == null) {
return joinSizeInfoToKey(getIdentityUrl(), mRequestSize.x, mRequestSize.y);
} else {
return joinSizeTagToKey(getIdentityUrl(), mReuseInfo.getIdentitySize());
}
}
public boolean isPreLoad() {
return (mFlag & STATUS_PRE_LOAD) == STATUS_PRE_LOAD;
}
public void setIsPreLoad() {
mFlag = mFlag | STATUS_PRE_LOAD;
}
public boolean isLoading() {
return (mFlag & STATUS_LOADING) != 0;
}
/**
* Check the given url is loading.
*
* @param url
* @return Identify the given url, if same to {@link #mIdentityUrl} return true.
*/
public boolean isLoadingThisUrl(String url) {
return getIdentityUrl().equals(generateIdentityUrl(url));
}
/**
* Bind ImageView with ImageTask
*
* @param imageView
*/
public void addImageView(CubeImageView imageView) {
if (null == imageView) {
return;
}
if (null == mFirstImageViewHolder) {
mFirstImageViewHolder = new ImageViewHolder(imageView);
return;
}
ImageViewHolder holder = mFirstImageViewHolder;
for (; ; holder = holder.mNext) {
if (holder.contains(imageView)) {
return;
}
if (holder.mNext == null) {
break;
}
}
ImageViewHolder newHolder = new ImageViewHolder(imageView);
newHolder.mPrev = holder;
holder.mNext = newHolder;
}
/**
* Remove the ImageView from ImageTask
*
* @param imageView
*/
public void removeImageView(CubeImageView imageView) {
if (null == imageView || null == mFirstImageViewHolder) {
return;
}
ImageViewHolder holder = mFirstImageViewHolder;
do {
if (holder.contains(imageView)) {
// Make sure entry is right.
if (holder == mFirstImageViewHolder) {
mFirstImageViewHolder = holder.mNext;
}
if (null != holder.mNext) {
holder.mNext.mPrev = holder.mPrev;
}
if (null != holder.mPrev) {
holder.mPrev.mNext = holder.mNext;
}
}
} while ((holder = holder.mNext) != null);
}
/**
* Check if this ImageTask has any related ImageViews.
*
* @return
*/
public boolean stillHasRelatedImageView() {
if (null == mFirstImageViewHolder || mFirstImageViewHolder.getImageView() == null) {
return false;
} else {
return true;
}
}
public void onLoading(ImageLoadHandler handler) {
mFlag = mFlag | STATUS_LOADING;
if (null == handler || null == mFirstImageViewHolder) {
return;
}
ImageViewHolder holder = mFirstImageViewHolder;
do {
final CubeImageView imageView = holder.getImageView();
if (null != imageView) {
handler.onLoading(this, imageView);
}
} while ((holder = holder.mNext) != null);
}
/**
* Will be called when begin load image data from dish or network
*
* @param drawable
*/
public void onLoadFinish(BitmapDrawable drawable, ImageLoadHandler handler) {
mFlag &= ~STATUS_LOADING;
mFlag |= STATUS_DONE;
if (null == handler || null == mFirstImageViewHolder) {
return;
}
if (null != mImageTaskStatistics) {
mImageTaskStatistics.showBegin();
}
ImageViewHolder holder = mFirstImageViewHolder;
do {
final CubeImageView imageView = holder.getImageView();
if (null != imageView) {
imageView.onLoadFinish();
handler.onLoadFinish(this, imageView, drawable);
}
} while ((holder = holder.mNext) != null);
if (null != mImageTaskStatistics) {
mImageTaskStatistics.showComplete(ImageProvider.getBitmapSize(drawable));
}
}
public void onCancel() {
mFlag &= ~STATUS_LOADING;
mFlag |= STATUS_CANCELED;
}
/**
* If you have a thumbnail web service which can return multiple size image according the url,
* <p/>
* you can implements this method to return the specified url according the request size.
*
* @return
*/
public String getRemoteUrl() {
return mOriginUrl;
}
/**
* Return the origin request url
*
* @return
*/
public String getOriginUrl() {
return mOriginUrl;
}
public void setBitmapOriginSize(int width, int height) {
mBitmapOriginSize.set(width, height);
}
public Point getBitmapOriginSize() {
return mBitmapOriginSize;
}
public Point getRequestSize() {
return mRequestSize;
}
/**
* Return the key which identifies this Image Wrapper object.
*/
public String getIdentityKey() {
if (mIdentityKey == null) {
mIdentityKey = generateIdentityKey();
}
return mIdentityKey;
}
/**
* Join the key and the size information.
*
* @param key
* @param w
* @param h
* @return "$key" + "_" + "$w" + "_" + "$h"
*/
public static String joinSizeInfoToKey(String key, int w, int h) {
if (w > 0 && h != Integer.MAX_VALUE && h > 0 && h != Integer.MAX_VALUE) {
return new StringBuilder(key).append(SIZE_SP).append(w).append(SIZE_SP).append(h).toString();
}
return key;
}
/**
* Join the tag with the key.
*
* @param key
* @param tag
* @return "$key" + "_" + "$tag"
*/
public static String joinSizeTagToKey(String key, String tag) {
return new StringBuilder(key).append(SIZE_SP).append(tag).toString();
}
/**
* Return the cache key for file cache.
*
* @return the cache key for file cache.
*/
public String getFileCacheKey() {
return Encrypt.md5(getIdentityKey());
}
/**
* @param sizeKey
* @return
*/
public String generateFileCacheKeyForReuse(String sizeKey) {
return Encrypt.md5(joinSizeTagToKey(getIdentityUrl(), sizeKey));
}
public ImageReuseInfo getImageReuseInfo() {
return mReuseInfo;
}
@Override
public boolean equals(Object object) {
if (object != null && object instanceof ImageTask) {
return ((ImageTask) object).getIdentityKey().equals(getIdentityKey());
}
return false;
}
@Override
public String toString() {
if (mStr == null) {
mStr = String.format("%s %sx%s", mId, mRequestSize.x, mRequestSize.y);
}
return mStr;
}
public ImageTaskStatistics getStatistics() {
return mImageTaskStatistics;
}
/**
* A tiny and light linked-list like container to hold all the ImageViews related to the ImageTask.
*/
private static class ImageViewHolder {
private WeakReference<CubeImageView> mImageViewRef;
private ImageViewHolder mNext;
private ImageViewHolder mPrev;
public ImageViewHolder(CubeImageView imageView) {
mImageViewRef = new WeakReference<CubeImageView>(imageView);
}
boolean contains(CubeImageView imageView) {
return mImageViewRef != null && imageView == mImageViewRef.get();
}
CubeImageView getImageView() {
if (null == mImageViewRef) {
return null;
}
return mImageViewRef.get();
}
}
}