package org.android.loader; 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; import org.android.loader.core.BitmapCache; import org.android.loader.core.BitmapOwner; import org.android.loader.core.BitmapProcess; import org.android.loader.core.BitmapTask; import org.android.loader.core.ImageConfig; import org.android.loader.core.MyBitmap; import org.android.loader.view.MyDrawable; import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; 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 com.m.R; import com.m.common.context.GlobalContext; import com.m.common.utils.BitmapUtil; import com.m.common.utils.BitmapUtil.BitmapType; import com.m.common.utils.FileUtility; import com.m.common.utils.KeyGenerator; import com.m.common.utils.Logger; import com.m.common.utils.SystemUtility; /** * BitmapLoader改自Afinal1.0<br/> * 之前的项目需要加载大量的图片导致频繁的OOM,偶遇Afinal1.0,当时拿来用还是有很多的不便, * 之后在它的基础上根据需求不断修改,就有了现在的BitmapLoader。感谢Afinal的无私奉献, * 分享是一种美德,共享自己的经验和成果也是让有着共同需求的人少走很多弯路! * * @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 void newInstanceAndInit(Context mContext, String imageCachePath) { BitmapLoader loader = newInstance(mContext); if (TextUtils.isEmpty(imageCachePath)) imageCachePath = SystemUtility.getSdcardPath() + File.separator + "aisenImage" + File.separator; loader.imageCachePath = imageCachePath; loader.init(); } public static BitmapLoader getInstance() { return imageLoader; } public void destory() { } /** * 在设置完参数属性后,必须调用此方法进行初始化 * * @return */ public 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)) 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, "视图在滚动,显示默认图片"); if (imageView != null && ImageConfig.getLoadingBitmapRes() > 0) imageView.setImageDrawable(new MyDrawable(mContext.getResources(), new MyBitmap(getBitmapByRes(ImageConfig.getLoadingBitmapRes()), BitmapType.jpg, null), ImageConfig, null)); else if (imageView != null && !TextUtils.isEmpty(ImageConfig.getLoadingBitmap())) imageView.setImageDrawable(new MyDrawable(mContext.getResources(), new MyBitmap(getBitmapByName(ImageConfig.getLoadingBitmap()), BitmapType.jpg, null), ImageConfig, null)); } else { // 开启新的线程加载图片 MyBitmapLoaderTask newTask = new MyBitmapLoaderTask(url, imageView, this, ImageConfig); WeakReference<MyBitmapLoaderTask> taskReference = new WeakReference<BitmapLoader.MyBitmapLoaderTask>(newTask); taskCache.put(KeyGenerator.generateMD5(getKeyByConfig(url, ImageConfig)), taskReference); try { if (imageView != null && ImageConfig.getLoadingBitmapRes() > 0) imageView.setImageDrawable(new MyDrawable(mContext.getResources(), new MyBitmap(getBitmapByRes(ImageConfig.getLoadingBitmapRes()), BitmapType.jpg, url), ImageConfig, taskReference)); else if (imageView != null && !TextUtils.isEmpty(ImageConfig.getLoadingBitmap())) imageView.setImageDrawable(new MyDrawable(mContext.getResources(), new MyBitmap(getBitmapByName(ImageConfig.getLoadingBitmap()), BitmapType.jpg, url), ImageConfig, taskReference)); } catch (OutOfMemoryError e) { e.printStackTrace(); } newTask.executrOnImageExecutor(); // newTask.execute(); // 添加到fragment当中,当fragment在Destory的时候,清除task列表 if (owner != null) getTaskCache(owner).add(new WeakReference<BitmapLoader.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 BitmapDrawable getDrawableByName(String name, String url, ImageConfig config, MyBitmapLoaderTask task) { WeakReference<MyBitmapLoaderTask> taskReference = task == null ? null : new WeakReference<MyBitmapLoaderTask>(task); return new MyDrawable(mContext.getResources(), new MyBitmap(getBitmapByName(name), BitmapType.jpg, url), config, taskReference); } public BitmapDrawable getDrawableByRes(int resId, String url, ImageConfig config, MyBitmapLoaderTask task) { WeakReference<MyBitmapLoaderTask> taskReference = task == null ? null : new WeakReference<MyBitmapLoaderTask>(task); return new MyDrawable(mContext.getResources(), new MyBitmap(getBitmapByRes(resId), BitmapType.jpg, url), config, taskReference); } private Bitmap getBitmapByName(String name) { MyBitmap myBitmap = mImageCache.getBitmapFromMemCache(name, null); if (myBitmap == null) { Bitmap bitmap = null; // 首先从drawable加载 if (!TextUtils.isEmpty(name) && name.startsWith("sdcard:")) { byte[] bytes = FileUtility.readFileToBytes(new File(name.replaceAll("sdcard:", ""))); bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } else { bitmap = BitmapUtil.getFromDrawableAsBitmap(name); } // TODO 加载assets if (bitmap == null) bitmap = BitmapFactory.decodeResource(GlobalContext.getInstance().getResources(), R.drawable.bg_timeline_loading); myBitmap = new MyBitmap(bitmap, BitmapType.jpg, name); mImageCache.addBitmapToMemCache(name, null, myBitmap); } return myBitmap.getBitmap(); } private Bitmap getBitmapByRes(int resId) { MyBitmap myBitmap = mImageCache.getBitmapFromMemCache(String.valueOf(resId), null); if (myBitmap == null) { Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), resId); myBitmap = new MyBitmap(bitmap, BitmapType.jpg, String.valueOf(resId)); mImageCache.addBitmapToMemCache(String.valueOf(resId), null, myBitmap); } return myBitmap.getBitmap(); } 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.state != AisenTaskState.stoped && task.imageUrl.equals(url)) { if (!task.isCancelled() && !task.isCompleted && task.imageUrl.equals(url)) { try { if (imageView != null && config.getLoadingBitmapRes() > 0) imageView.setImageDrawable(getDrawableByRes(config.getLoadingBitmapRes(), url, config, task)); else if (!TextUtils.isEmpty(config.getLoadingBitmap())) imageView.setImageDrawable(getDrawableByName(config.getLoadingBitmap(), url, config, task)); 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)); } } } 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 static String getKeyByConfig(String url, ImageConfig config) { String path = url; // 去掉BaseURL // if (url.indexOf("/upload") != -1) { // path = url.substring(url.indexOf("/upload"), url.length()); // } else { // 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(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); try { if (config.getLoadingBitmapRes() > 0) setImageBitmap(new MyBitmap(getBitmapByRes(config.getLoadfaildBitmapRes()), BitmapType.jpg, "")); else if (!TextUtils.isEmpty(config.getLoadfaildBitmap())) setImageBitmap(new MyBitmap(getBitmapByName(config.getLoadfaildBitmap()), BitmapType.jpg, "")); } catch (OutOfMemoryError e) { e.printStackTrace(); } } 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(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) 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"); } 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(); } // bitmapCache.clear(); } public void clearMemHalfCacheInternal() { if (mImageCache != null) { mImageCache.clearMemHalfCache(); } } public static Drawable getLoadingDrawable(ImageView imageView) { 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; ImageConfig config = myDrawable.getConfig(); if (config != null) { try { if (config.getLoadingBitmapRes() > 0) return BitmapLoader.getInstance().getDrawableByRes(config.getLoadingBitmapRes(), null, config, null); else return BitmapLoader.getInstance().getDrawableByName(config.getLoadingBitmap(), null, config, null); } catch (OutOfMemoryError e) { e.printStackTrace(); } } } } return new ColorDrawable(Color.parseColor("#fff2f2f2")); } }