除了缓存图片意外,还有一些其余的方式来促进GC的效率和图片的复用.不一样的Android系统版本有不一样的处理策略.BitmapFun中就包含了这个类,可以使咱们高效地构建咱们的项目.html
为了开始如下教程,咱们须要先介绍一下Android系统对Bitmap管理的进化史.java
如下内容描述了如何为不一样的Android版本选择最佳的图片缓存管理方式.android
在Android 2.3.3以及更低版本中管理缓存缓存
在Android 2.3.3(API level 10)以及更低版本中,建议使用Bitmap.recycle()方法.若是你正在展现大量的图片,应用可能会内存溢出.这时候recycle()方法可使内存被尽量快地回收.并发
注意:你只能在肯定了Bitmap确实再也不被使用了以后才能调用recycle()方法.若是你调用recycle()方法释放了Bitmap,而稍后却又使用这个Bitmap,这时就会出现"Canvas: trying to use a recycled bitmap"错误.
ide
如下代码片断是recycle()方法调用的示例.这里使用了引用计数器的方式(经过变量
uimDisplayRefCount和mCacheRefCount
)来追踪一个Bitmap是否还在被使用或者存在于缓存中.当知足以下条件时,此处就会回收Bitmap:
引用计数器变量mDisplayRefCount和mCacheRefCount的值都为0时.
Bitmap不为null,并且没有被回收.
private int mCacheRefCount = 0; private int mDisplayRefCount = 0; ... // 通知当前Drawable,当前被使用的状态改变了 // 保持一个计数器,用来决定当前Drawable什么时候被回收 public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { // 当被显示时,则计数器mDisplayRefCount的值+1 mDisplayRefCount++; // 标志当前Drawable被显示了 mHasBeenDisplayed = true; } else { // 当一个引用被释放时,计数器mDisplayRefCount的值-1 mDisplayRefCount--; } } // 确认recycle()方法是否已经被调用了 checkState(); } // 通知当前Drawable缓存状态改变了 // 保持一个计数器用于决定什么时候这个Drawable再也不被缓存 public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // 确认recycle()方法是否已经被调用了 checkState(); } private synchronized void checkState() { // 若是当前Drawable的内存和展现计数器都为0,并且当前Drawable还可用 // 则释放掉它 if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } // 判断Drawable对应的Bitmap是否可用 private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }
在Android 3.0以及更高版本中管理缓存this
从Android 3.0(API level 11)开始,引入了BitmapFactory.Options.inBitmap字段,若是这个属性被设置了,拥有这个Options对象的方法在解析图片的时候会尝试复用一张已存在的图片.这意味着图片缓存被服用了,这意味着更流畅的用户体验以及更好的内存分配和回收.然而,要使用inBitmap有这必定的限制.尤为须要注意的是,在Android 4.4(API level 19)以前,只有相同大小的Btiamp才会被复用.更详细的用法,请参见inBitmap的文档.spa
保存一个Bitmap以备后来使用线程
下面的代码片断示范了如何保存一个Bitmap以备后来使用.当在Android 3.0以及更高版本平台中时,一个Bitmap被从LrcCache中移除后,它的软引用会被保存到一个HashSet中,以备inBitmap后来使用:
Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache; // 当程序运行在Honeycomb(Android 3.1)及以上版本的系统中时,建立一个 // 同步的HashSet用于存放可复用的Bitmap的引用 if (Utils.hasHoneycomb()) { mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>()); } mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { // Notify the removed entry that is no longer being cached. @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { // 若是被移除的Drawable是RecyclingBitmapDrawable,则通知它 // 已经被从内存缓存中移除 ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { // 若是被移除的Drawable是一个普通的BitmapDrawable if (Utils.hasHoneycomb()) { // 若是运行在Honeycomb(Android 3.1)及以上系统中,则为Bitmap添加 // 到一个保存软引用的集合中,以备后来被inBitmap使用 mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap())); } } } .... }
复用一个已经存在的Bitmap
在解析图片时,会检查是否已经存在一个可用的Bitmap,以下:
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) { final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // 若是应用运行在Honeycomb及以上系统中,则尝试使用inBitmap复用图片 if (Utils.hasHoneycomb()) { addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options); }
如下代码片断描述了如何查找一个已经存在的Bitmap并将它设置为inBitmap的值.注意,这个方法只有在找到了符合条件的Bitmap才会为inBitmap赋值(咱们不能乐观地假定图片会被找到):
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { // inBitmap仅能接受可编辑的Bitmap,因此此处须要强制解码器 // 返回一个可编辑的Bitmap options.inMutable = true; if (cache != null) { // 尝试查找知足条件的Bitmap给inBitmap赋值 Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null) { // 若是查找到了符合条件的Bitmap,则赋值给inBitmap options.inBitmap = inBitmap; } } } // 这个方法遍历了保存弱引用的集合,用于查找一个合适的Bitmap protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { Bitmap bitmap = null; if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) { synchronized (mReusableBitmaps) { final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator(); Bitmap item; while (iterator.hasNext()) { item = iterator.next().get(); if (null != item && item.isMutable()) { // 检查当前的Bitmap是否知足inBitmap的复用条件 // Check to see it the item can be used for inBitmap. if (canUseForInBitmap(item, options)) { // 若是知足复用条件,则给Bitmap赋值,并在方法结束时返回 bitmap = item; // 在给返回值赋值后,将当前Bitmap的引用从集合中移除 iterator.remove(); break; } } else { // 若是读取到的是空引用,则将该引用移除 iterator.remove(); } } } } return bitmap; }
最后,下面的这个方法可以检查哪一个Bitmap知足inBitmap的复用条件:
static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 从Android 4.4(KitKat)开始,若是当前须要的Bitmap尺寸(内存中占用的字节数)比缓存中的Bitmap尺寸小 // 而且同一张图片的Bitmap存在,咱们就能够复用它 int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; // 当前须要的Bitmap占用字节数 int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); // 若是须要的Bitmap尺寸小于原Bitmap,则返回true return byteCount <= candidate.getAllocationByteCount(); } // 在Android 4.4之前,尺寸必须彻底一致,而且inSampleSize为1时 // Bitmap才能被复用 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1; } /** * 一个用于判断在不一样的参数下,每一个像素占用字节数的方法 */ static int getBytesPerPixel(Config config) { if (config == Config.ARGB_8888) { return 4; } else if (config == Config.RGB_565) { return 2; } else if (config == Config.ARGB_4444) { return 2; } else if (config == Config.ALPHA_8) { return 1; } return 1; }