下面是Android对bitmap的内存管理的进化过程:
在Android2.2之前的版本中,当垃圾回收线程开始时,你的app的线程就会挂起。这就会导致用户体验降级。Android2.3之后使得垃圾回收机制可以并发执行。这也就意味着当一个bitmap没有指向自己的引用时,可以被垃圾回收机制回收,但是当别的地方申请引用内存时。这一块内存也可以被生声明,而并非要等到整个垃圾回收线程结束。
而在Android 2.3.3(API级别10)和之前的版本中,支持一个bitmap的像素的数据是存储在native heap中内存。和存储在Dalvik堆的bitmap是分开存的。(可以简单的理解为一部分存在c中,一部分存在Java中。)我们并不知道存储在c 中的那些像素数据什么时候被释放,这样就可能导致app超出内存限制而崩溃。而Android3.0的版本则开始把像素数据和bitmap一起放在了Dalvik中(即java中,这样就方便我们用垃圾回收机制来管理)。
下面的部分则是讲解对于不同的版本,怎样优化bitmap的内存管理
在Android2.3.3以及之前的版本上的内存管理
在2.3.3以及之前,推荐用recycle()方法(调用底层c的方法将图片释放)。如果你的app中有大量图片,那么你就有可能报错oom.而recycle()方法则允许app可以尽快的将不用的内存重新声明以加以利用。
注意:只有你在确定该bitmap没有用时才能调用recycle()方法。如果你回收了这张图而你在后面利用这张图,那么就会报错: "Canvas:trying to use a recycled bitmap".
接下来的代码段(上节异步加载图片程序源码中可找到该代码段)就是一个利用recycle()的例子。见源码中RecyclingBitmapDrawable.java文件。它利用两个变量来追踪一个图片是否显示或者是还是否有必要呆在内存中。
当下面的条件都被满足时那么该图片就要回收:
· 两个计数变量都是零.
· 图片不为空而且还没有被回收.
• private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
...
• //通知该drawable(此处为bitmap形式)展示状态(已经展示或未展示)已经改变
• //用一个变量mDisplayRefCount来标记展示次数。
public void setIsDisplayed(boolean isDisplayed) {
synchronized (this) {
if (isDisplayed) {//被展示,通常是被加载在了imageview上
mDisplayRefCount++;
mHasBeenDisplayed = true;
} else {//不被展示,通常是被新的bitmap覆盖。
mDisplayRefCount--;
}
}
//检查是否有回收的bitmap
checkState();
}
//通知该drawable(此处为bitmap形式)展示状态(已经展示或未展示)已经改变
//用一个变量mDisplayRefCount来标记展示次数。
public void setIsCached(boolean isCached) {
synchronized (this) {
if (isCached) {//被加入缓存,通常是后台下载图片后存入缓存
mCacheRefCount++;
} else {//移出缓存,比如在存储空间到达上限时移出
mCacheRefCount--;
}
}
// Check to see if recycle() can be called.
checkState();
}
private synchronized void checkState() {
//如果一个bitmap缓存和展示的次数为零,即曾经展示过,如今要被移出内存而且 //无需再展示了那么这个bitmap就可以回收了。
if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
&& hasValidBitmap()) {
getBitmap().recycle();// }
}
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
在Android3.0以及之后的版本上的内存管理
在3.0以上,多了一个变量: BitmapFactory.Options.inBitmap
。如果这个option被设置了inBitmap,那么配置了这个option的解码方法就会在下载像素的时候重复利用已存在的bitmap。这就是说那个bitmap所占的内存被重新利用。这样就会使得程序表现更好。
但是,在怎样使用inBitmap方面,有一定的限制条件。尤其是在4.4之前。只有同等大小的bitmap才可以使用。
HashSet<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;
// If you're running on Honeycomb or newer, create
// a HashSet of references to reusable bitmaps.
if (Utils.hasHoneycomb()) {//3.0以上
mReusableBitmaps = 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)) {//2.3.3之前
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache.
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable.
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later.
mReusableBitmaps.add
(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
....
}
Use an existing bitmap
在程序运行中,decode方法会寻找有没有已经存在的bitmap.比如:
public static Bitmap decodeSampledBitmapFromFile(String filename,
int reqWidth, int reqHeight, ImageCache cache) {
final BitmapFactory.Options options = new BitmapFactory.Options();
...
BitmapFactory.decodeFile(filename, options);
...
// If we're running on Honeycomb or newer, try to use inBitmap.
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
...
return BitmapFactory.decodeFile(filename, options);
}
下面代码段中的addInBitmapOptions()就是上面提到的。这个方法寻找一个已经存在的bitmap作为inBitmap配置option。必须注意只有当这个方法找到合适的匹配者时才会给option的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) {
// If a suitable bitmap has been found, set it as the value of
// inBitmap.
options.inBitmap = inBitmap;
}
}
}
// This method iterates through the reusable bitmaps, looking for one
// to use for inBitmap:
//这个方法遍历reusable bitmaps以寻找一个可以作为inBitmap值的bitmap
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
final Iterator<SoftReference<Bitmap>> iterator
= mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
// Check to see it the item can be used for inBitmap.
if (canUseForInBitmap(item, options)) {
bitmap = item;
// Remove from reusable set so it can't be used again.
iterator.remove();
break;
}
} else {
// Remove from the set if the reference has been cleared.
iterator.remove();
}
}
}
return bitmap;
}
最终是这个方法来决定是否每个待用的bitmap满足inBitmap的size标准
static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//如果在4.4以上的平台中,只要是新的位图比准备reuse的位图的字节少,那么这个代用的准备reuse的位图就可以重新利用。
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
//在早期的版本中,这个尺寸大小必须严格一致,而且inSamplesize必须是1才可以
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;
}
总结:在3.0之前当超出内存时的处理是直接在内存中recycle掉,而在3.0之后则是先放在hashset中,如果hashset中存的bitmap不能被之后的解析图片所利用时那么就会被remove掉,同样会被回收。