package org.aisen.wen.component.bitmaploader; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.os.AsyncTask; import android.text.TextUtils; import android.widget.ImageView; import org.aisen.wen.component.bitmaploader.core.BitmapCache; import org.aisen.wen.component.bitmaploader.core.BitmapOwner; import org.aisen.wen.component.bitmaploader.core.BitmapProcess; import org.aisen.wen.component.bitmaploader.core.BitmapTask; import org.aisen.wen.component.bitmaploader.core.ImageConfig; import org.aisen.wen.component.bitmaploader.core.MyBitmap; import org.aisen.wen.component.bitmaploader.view.MyDrawable; import org.aisen.wen.support.utils.KeyGenerator; import org.aisen.wen.support.utils.Logger; import org.aisen.wen.support.utils.SystemUtils; import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * * @author Jeff.Wang * * @date 2014年9月17日 */ public class BitmapLoader { public static final String TAG = "BitmapLoader"; private Map<WeakReference<BitmapOwner>, List<WeakReference<MyBitmapLoaderTask>>> ownerMap; private Map<String, WeakReference<MyBitmapLoaderTask>> taskCache; private String imageCachePath;// 图片缓存路径 private BitmapProcess bitmapProcess; private BitmapCache mImageCache;// 图片缓存 private Context mContext; private BitmapLoader(Context mContext) { this.mContext = mContext; } private static BitmapLoader imageLoader; static BitmapLoader newInstance(Context mContext) { imageLoader = new BitmapLoader(mContext); return imageLoader; } public static BitmapLoader newInstance(Context mContext, String imageCachePath) { BitmapLoader loader = newInstance(mContext); if (TextUtils.isEmpty(imageCachePath)) imageCachePath = SystemUtils.getSdcardPath() + File.separator + "aisenImage" + File.separator; loader.imageCachePath = imageCachePath; loader.init(); return loader; } public static BitmapLoader getInstance() { return imageLoader; } public void destory() { } /** * 在设置完参数属性后,必须调用此方法进行初始化 * * @return */ BitmapLoader init() { ownerMap = new HashMap<WeakReference<BitmapOwner>, List<WeakReference<MyBitmapLoaderTask>>>(); taskCache = new HashMap<String, WeakReference<MyBitmapLoaderTask>>(); int memCacheSize = 1024 * 1024 * ((ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); memCacheSize = memCacheSize / 3; Logger.i(TAG, "memCacheSize = " + (memCacheSize / 1024 / 1024) + "MB"); bitmapProcess = new BitmapProcess(imageCachePath); mImageCache = new BitmapCache(memCacheSize); return this; } public void display(BitmapOwner owner, String url, ImageView imageView, ImageConfig imageConfig) { if (TextUtils.isEmpty(url)) { setImageFaild(imageView, imageConfig); return; } if (bitmapHasBeenSet(imageView, url)) return; MyBitmap myBitmap = mImageCache.getBitmapFromMemCache(url, imageConfig); // 内存缓存存在图片,且未释放 if (myBitmap != null && imageView != null) { imageView.setImageDrawable(new MyDrawable(mContext.getResources(), myBitmap, imageConfig, null)); } // 开启线程拉取图片 else { // 线程不存在,获取已经存在的线程不是加载当前的url,如果存在线程,则停止之前存在的线程 if (!checkTaskExistAndRunning(url, imageView, imageConfig)) { // 2014-08-29 修改为当视图在滚动的时候,不加载图片 boolean canLoad = owner == null || owner.canDisplay() ? true : false; if (!canLoad) { Logger.d(TAG, "视图在滚动,显示默认图片"); setImageLoading(imageView, null, imageConfig); } else { // 开启新的线程加载图片 MyBitmapLoaderTask newTask = new MyBitmapLoaderTask(url, imageView, this, imageConfig); WeakReference<MyBitmapLoaderTask> taskReference = new WeakReference<MyBitmapLoaderTask>(newTask); taskCache.put(KeyGenerator.generateMD5(getKeyByConfig(url, imageConfig)), taskReference); setImageLoading(imageView, url, imageConfig); newTask.executrOnImageExecutor(); // 添加到fragment当中,当fragment在Destory的时候,清除task列表 if (owner != null) getTaskCache(owner).add(new WeakReference<MyBitmapLoaderTask>(newTask)); newTask = null; } } } } /** * 当前加载的图片已经绑定在ImageView上了 * * @param imageView * @param url * @return */ public boolean bitmapHasBeenSet(ImageView imageView, String url) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable != null) { if (drawable instanceof TransitionDrawable) { TransitionDrawable transitionDrawable = (TransitionDrawable) drawable; drawable = transitionDrawable.getDrawable(1); } if (drawable instanceof MyDrawable) { MyDrawable myDrawable = (MyDrawable) drawable; if (myDrawable.getMyBitmap() != null && url.equals(myDrawable.getMyBitmap().getUrl())) { return true; } } } } return false; } public BitmapCache getImageCache() { return mImageCache; } public BitmapProcess getBitmapProcess() { return bitmapProcess; } private List<WeakReference<MyBitmapLoaderTask>> getTaskCache(BitmapOwner owner) { List<WeakReference<MyBitmapLoaderTask>> taskWorkInOwner = null; Set<WeakReference<BitmapOwner>> set = ownerMap.keySet(); for (WeakReference<BitmapOwner> key : set) if (key != null && key.get() == owner) taskWorkInOwner = ownerMap.get(key); if (taskWorkInOwner == null) { taskWorkInOwner = new ArrayList<WeakReference<MyBitmapLoaderTask>>(); ownerMap.put(new WeakReference<BitmapOwner>(owner), taskWorkInOwner); } return taskWorkInOwner; } private boolean checkTaskExistAndRunning(String url, ImageView imageView, ImageConfig config) { if (imageView == null) return false; // 线程缓存中是否已经有线程在运行,如果有,就将ImageView关联Task WeakReference<MyBitmapLoaderTask> loader = taskCache.get(KeyGenerator.generateMD5(BitmapLoader.getKeyByConfig(url, config))); if (loader != null) { MyBitmapLoaderTask task = loader.get(); if (task != null) { if (!task.isCancelled() && !task.isCompleted && task.imageUrl.equals(url)) { try { setImageLoading(imageView, url, config); task.imageViewsRef.add(new WeakReference<ImageView>(imageView)); Logger.d(TAG, String.format("ImageView加载的图片已有线程在运行,url = %s", url)); } catch (OutOfMemoryError e) { e.printStackTrace(); } return true; } } else { taskCache.remove(KeyGenerator.generateMD5(BitmapLoader.getKeyByConfig(url, config))); } } // 还没有线程,判断ImageView是否已经绑定了线程,如果绑定了,就将已存在的线程cancel(false)掉 MyBitmapLoaderTask task = getWorkingTask(imageView); if (task != null && !task.imageUrl.equals(url) && task.imageViewsRef.size() == 1) { Logger.d(TAG, String.format("停止一个图片加载,如果还没有运行 url = %s", url)); task.cancel(false); } return false; } public void cancelPotentialTask(BitmapOwner owner) { if (owner == null) return; List<WeakReference<MyBitmapLoaderTask>> taskWorkInFragment = getTaskCache(owner); if (taskWorkInFragment != null) for (WeakReference<MyBitmapLoaderTask> taskRef : taskWorkInFragment) { MyBitmapLoaderTask task = taskRef.get(); if (task != null) { task.cancel(true); Logger.d(TAG, String.format("fragemnt销毁,停止线程 url = %s", task.imageUrl)); } } for (WeakReference<BitmapOwner> key : ownerMap.keySet()) if (key != null && key.get() == owner) { ownerMap.remove(key); Logger.w(TAG, "移除一个owner --->" + owner.toString()); break; } Logger.w(TAG, "owner %d 个" + ownerMap.size()); } private MyBitmapLoaderTask getWorkingTask(ImageView imageView) { if (imageView == null) return null; Drawable drawable = imageView.getDrawable(); if (drawable != null && drawable instanceof MyDrawable) { WeakReference<MyBitmapLoaderTask> loader = ((MyDrawable) drawable).getTask(); if (loader != null && loader.get() != null) return loader.get(); } return null; } public File getCacheFile(String url) { return bitmapProcess.getOirgFile(url); } public File getCompressCacheFile(String url, String imageId) { return bitmapProcess.getCompressFile(url, imageId); } public Bitmap getBitmapFromMemory(String url, ImageConfig config) { MyBitmap bitmap = mImageCache.getBitmapFromMemCache(url, config); if (bitmap != null) return bitmap.getBitmap(); return null; } public MyBitmap getMyBitmapFromMemory(String url, ImageConfig config) { MyBitmap bitmap = mImageCache.getBitmapFromMemCache(url, config); if (bitmap != null) return bitmap; return null; } public static String getKeyByConfig(String url, ImageConfig config) { String path = url; if (config == null || TextUtils.isEmpty(config.getId())) return path; return path + config.getId(); } /** * 清除缓存 */ public void clearCache() { new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_CLEAR); } public void clearHalfCache() { new CacheExecutecTask().execute(CacheExecutecTask.MESSAGE_HALF_CLEAR); } public class MyBitmapLoaderTask extends BitmapTask<Void, Void, MyBitmap> { private final String imageUrl; private List<WeakReference<ImageView>> imageViewsRef; private final ImageConfig config; boolean isCompleted = false; public String getKey() { return KeyGenerator.generateMD5(getKeyByConfig(imageUrl, config)); } public MyBitmapLoaderTask(String imageUrl, ImageView imageView, BitmapLoader bitmapLoader, ImageConfig config) { this.imageUrl = imageUrl; imageViewsRef = new ArrayList<WeakReference<ImageView>>(); if (imageView != null) imageViewsRef.add(new WeakReference<ImageView>(imageView)); this.config = config; } @Override public MyBitmap workInBackground(Void... params) throws Exception { try { BitmapBytesAndFlag bitmapBytesAndFlag = doDownload(imageUrl, config); byte[] bitmapBytes = bitmapBytesAndFlag.bitmapBytes; int flag = bitmapBytesAndFlag.flag; if (!isCancelled() && checkImageBinding()) { // 如果图片不是拉取至二级缓存,判断是否需要处理 MyBitmap bitmap = bitmapProcess.compressBitmap(mContext, bitmapBytes, imageUrl, flag, config); if (bitmap != null && bitmap.getBitmap() != null) { mImageCache.addBitmapToMemCache(imageUrl, config, bitmap); return bitmap; } else { // 如果本地有缓存数据,解析图片失败,则删除图片文件 bitmapProcess.deleteFile(imageUrl, config); } } } catch (OutOfMemoryError e) { e.printStackTrace(); } throw new Exception("task canceled or failed, bitmap is null, url = " + imageUrl); } @Override protected void onTaskSuccess(MyBitmap bitmap) { super.onTaskSuccess(bitmap); setImageBitmap(bitmap); } @Override protected void onTaskFailed(Exception exception) { super.onTaskFailed(exception); if (config.getLoadfaildRes() > 0) setImageBitmap(new MyBitmap(mContext, config.getLoadfaildRes())); } private boolean checkImageBinding() { for (int i = 0; i < imageViewsRef.size(); i++) { ImageView imageView = imageViewsRef.get(i).get(); if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable != null && drawable instanceof MyDrawable) { MyDrawable aisenDrawable = (MyDrawable) drawable; if (imageUrl.equals(aisenDrawable.getMyBitmap().getUrl())) { return true; } } } } return false; } void setImageBitmap(MyBitmap bitmap) { for (int i = 0; i < imageViewsRef.size(); i++) { ImageView imageView = imageViewsRef.get(i).get(); if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable != null && drawable instanceof MyDrawable) { MyDrawable aisenDrawable = (MyDrawable) drawable; if (imageUrl.equals(aisenDrawable.getMyBitmap().getUrl())) { MyDrawable myDrawable = new MyDrawable(mContext.getResources(), bitmap, config, null); config.getDisplayer().loadCompletedisplay(imageView, myDrawable); } } } } } @Override protected void onTaskComplete() { super.onTaskComplete(); isCompleted = true; taskCache.remove(KeyGenerator.generateMD5(BitmapLoader.getKeyByConfig(imageUrl, config))); } } /** * 加载图片,该方法不是异步执行的方法 * * @param imageUrl * @param config * @return * @throws Exception */ public BitmapBytesAndFlag doDownload(String imageUrl, final ImageConfig config) throws Exception { byte[] bitmapBytes = null; int flag = 0x00; // 判断二级缓存数据 bitmapBytes = bitmapProcess.getBitmapFromCompDiskCache(imageUrl, config); if (bitmapBytes != null) { Logger.v(TAG, "load the picture through the compress disk, url = " + imageUrl); flag = flag | 0x01; } // 判断原始缓存数据 if (bitmapBytes == null) { bitmapBytes = bitmapProcess.getBitmapFromOrigDiskCache(imageUrl, config); if (bitmapBytes != null) { Logger.v(TAG, "load the data through the original disk, url = " + imageUrl); flag = flag | 0x02; } } // 网络加载 if (bitmapBytes == null) { bitmapBytes = config.getDownloaderClass().newInstance().downloadBitmap(mContext, imageUrl, config); if (bitmapBytes != null) { Logger.v(TAG, "load the data through the network, url = " + imageUrl); Logger.v(TAG, "downloader = " + config.getDownloaderClass().getSimpleName()); flag = flag | 0x04; } // 数据写入原始缓存 if (bitmapBytes != null && config.isCacheEnable()) bitmapProcess.writeBytesToOrigDisk(bitmapBytes, imageUrl); } if (bitmapBytes != null && config.getProgress() != null) config.getProgress().sendFinishedDownload(bitmapBytes); if (bitmapBytes != null) { BitmapBytesAndFlag bitmapBytesAndFlag = new BitmapBytesAndFlag(); bitmapBytesAndFlag.bitmapBytes = bitmapBytes; bitmapBytesAndFlag.flag = flag; return bitmapBytesAndFlag; } throw new Exception("download faild : " + imageUrl); } public static class BitmapBytesAndFlag { public byte[] bitmapBytes; public int flag; } private class CacheExecutecTask extends AsyncTask<Object, Void, Void> { public static final int MESSAGE_CLEAR = 0; public static final int MESSAGE_HALF_CLEAR = 4; @Override protected Void doInBackground(Object... params) { switch ((Integer) params[0]) { case MESSAGE_CLEAR: clearMemCacheInternal(); break; case MESSAGE_HALF_CLEAR: clearMemHalfCacheInternal(); break; } return null; } } private void clearMemCacheInternal() { Logger.d(TAG, "clearMemCacheInternal"); if (mImageCache != null) { mImageCache.clearMemCache(); } } public void clearMemHalfCacheInternal() { if (mImageCache != null) { mImageCache.clearMemHalfCache(); } } public static Drawable getLoadingDrawable(Context context, ImageView imageView) { Drawable drawable = imageView.getDrawable(); if (drawable != null && context != null) { if (drawable instanceof TransitionDrawable) { TransitionDrawable transitionDrawable = (TransitionDrawable) drawable; drawable = transitionDrawable.getDrawable(1); } if (drawable instanceof MyDrawable) { MyDrawable myDrawable = (MyDrawable) drawable; ImageConfig config = myDrawable.getConfig(); if (config != null) { if (config.getLoadingRes() > 0) return new BitmapDrawable(context.getResources(), new MyBitmap(context, config.getLoadingRes()).getBitmap()); } } } return new ColorDrawable(Color.parseColor("#fff2f2f2")); } private void setImageFaild(ImageView imageView, ImageConfig imageConfig) { if (imageView != null && imageConfig.getLoadfaildRes() > 0) imageView.setImageDrawable( new MyDrawable(mContext.getResources(), new MyBitmap(mContext, imageConfig.getLoadfaildRes()), imageConfig, null)); } private void setImageLoading(ImageView imageView, String url, ImageConfig imageConfig) { if (imageView != null && imageConfig.getLoadingRes() > 0) imageView.setImageDrawable( new MyDrawable(mContext.getResources(), new MyBitmap(mContext, imageConfig.getLoadingRes(), url), imageConfig, null)); } }