Managing Bitmap Memory

来源:互联网 发布:编程猫的网址 编辑:程序博客网 时间:2024/06/10 18:17

In addition to the steps described in Caching Bitmaps, there are specific things you can do to facilitate garbage collection and bitmap reuse. The recommended strategy depends on which version(s) of Android you are targeting. The DisplayingBitmaps sample app included with this class shows you how to design your app to work efficiently across different versions of Android.

To set the stage for this lesson, here is how Android’s management of bitmap memory has evolved:

  • On Android Android 2.2 (API level 8) and lower, when garbage collection occurs, your app’s threads get stopped. This causes a lag that can degrade performance. Android 2.3 adds concurrent garbage collection, which means that the memory is reclaimed soon after a bitmap is no longer referenced.
  • On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.

The following sections describe how to optimize bitmap memory management for different Android versions.

译:

作为缓存Bitmaps的进一步延伸, 为了促进GC与bitmap的重用,你还有一些特定的事情可以做. 推荐的策略会根据Android的版本不同而有所差异. DisplayingBitmaps的示例程序会演示如何设计你的程序使得能够在不同的Android平台上高效的运行.

为了给这节课奠定基础,我们首先要知道Android管理bitmap memory的演变进程:

  • 在Android 2.2 (API level 8)以及之前, 当GC发生时, 你的应用的线程是会stopped的. 这导致了一个滞后,它会降低效率. 在Android 2.3上,添加了并发GC的机制, 这意味着在一个bitmap不再被引用到之后,内存会被立即回收.
  • 在Android 2.3.3 (API level 10)以及之前, 一个bitmap的像素级数据是存放在native内存中的. 这些数据与bitmap本身是隔离的, bitmap本身是被存放在Dalvik heap中。在native内存中的pixel数据的释放是不可预测的,这意味着有可能导致一个程序容易超过它的内存限制并Crash。 自Android 3.0 (API Level 11)起, pixel数据则是与bitmap本身一起存放在dalvik heap中。

下面会介绍如何在不同的Android版本上优化bitmap内存使用。

Manage Memory on Android 2.3.3 and Lower


On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. If you’re displaying large amounts of bitmap data in your app, you’re likely to run into OutOfMemoryError errors. Therecycle() method allows an app to reclaim memory as soon as possible.

Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap".

The following code snippet gives an example of calling recycle(). It uses reference counting (in the variables mDisplayRefCount and mCacheRefCount) to track whether a bitmap is currently being displayed or in the cache. The code recycles the bitmap when these conditions are met:

  • The reference count for both mDisplayRefCount and mCacheRefCount is 0.
  • The bitmap is not null, and it hasn’t been recycled yet.

译:

在Android 2.3.3 (API level 10) 以及更低版本上,推荐使用recycle(). 如果在你的程序中显示了大量的bitmap数据,你很可能会遇到OutOfMemoryError错误. recycle()方法可以使得程序尽快的释放内存.

Caution:只有你确保这个bitmap不再需要用到的时候才应该使用recycle(). 如果你执行recycle(),然后尝试绘制这个bitmap, 你将得到错误:"Canvas: trying to use a recycled bitmap".

下面的代码片段演示了使用recycle()的例子. 它使用了引用计数的方法(mDisplayRefCount 与mCacheRefCount)来追踪一个bitmap目前是否有被显示或者是在缓存中. 当下面条件满足时回收bitmap:

  • mDisplayRefCount 与 mCacheRefCount 的引用计数均为 0.
  • bitmap不为null, 并且它还没有被回收.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
privateint mCacheRefCount = 0;
privateint mDisplayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
publicvoid setIsDisplayed(boolean isDisplayed) {
synchronized(this) {
if(isDisplayed) {
mDisplayRefCount++;
mHasBeenDisplayed =true;
}else {
mDisplayRefCount--;
}
}
// Check to see if recycle() can be called.
checkState();
}
 
// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
publicvoid setIsCached(boolean isCached) {
synchronized(this) {
if(isCached) {
mCacheRefCount++;
}else {
mCacheRefCount--;
}
}
// Check to see if recycle() can be called.
checkState();
}
 
privatesynchronized void checkState() {
// If the drawable cache and display ref counts = 0, and this drawable
// has been displayed, then recycle.
if(mCacheRefCount <= 0&& mDisplayRefCount <= 0&& mHasBeenDisplayed
&& hasValidBitmap()) {
getBitmap().recycle();
}
}
 
privatesynchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
returnbitmap != null&& !bitmap.isRecycled();
}

Manage Memory on Android 3.0 and Higher


Android 3.0 (API level 11) introduces the BitmapFactory.Options.inBitmap field. If this option is set, decode methods that take the Options object will attempt to reuse an existing bitmap when loading content. This means that the bitmap’s memory is reused, resulting in improved performance, and removing both memory allocation and de-allocation. However, there are certain restrictions with how inBitmap can be used. In particular, before Android 4.4 (API level 19), only equal sized bitmaps are supported. For details, please see the inBitmap documentation.

Save a bitmap for later use

The following snippet demonstrates how an existing bitmap is stored for possible later use in the sample app. When an app is running on Android 3.0 or higher and a bitmap is evicted from theLruCache, a soft reference to the bitmap is placed in a HashSet, for possible reuse later withinBitmap:

译:

在Android 3.0 (API Level 11) 引进了BitmapFactory.Options.inBitmap. 如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 (API level 19)之前,只支持同等大小的位图。详情请查看inBitmap文档.

保存bitmap供以后使用(Save a bitmap for later use)

下面演示了一个已经存在的bitmap是如何被存放起来以便后续使用的. 当一个应用运行在Android 3.0或者更高的平台上并且bitmap从LruCache中移除时, bitmap的一个软引用会被存放在Hashset中,这样便于之后可能被inBitmap重用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Set<SoftReference<Bitmap>> mReusableBitmaps;
privateLruCache<String, BitmapDrawable> mMemoryCache;
 
// If you're running on Honeycomb or newer, create a
// synchronized HashSet of references to reusable bitmaps.
if(Utils.hasHoneycomb()) {
mReusableBitmaps =
Collections.synchronizedSet(newHashSet<SoftReference<Bitmap>>());
}
 
mMemoryCache =new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
 
// Notify the removed entry that is no longer being cached.
@Override
protectedvoid entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if(RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// 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
(newSoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
....
}

Use an existing bitmap

In the running app, decoder methods check to see if there is an existing bitmap they can use. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
publicstatic Bitmap decodeSampledBitmapFromFile(String filename,
intreqWidth, intreqHeight, ImageCache cache) {
 
finalBitmapFactory.Options options = newBitmapFactory.Options();
...
BitmapFactory.decodeFile(filename, options);
...
 
// If we're running on Honeycomb or newer, try to use inBitmap.
if(Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
...
returnBitmapFactory.decodeFile(filename, options);
}

The next snippet shows the addInBitmapOptions() method that is called in the above snippet. It looks for an existing bitmap to set as the value for inBitmap. Note that this method only sets a value forinBitmap if it finds a suitable match (your code should never assume that a match will be found):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
privatestatic void addInBitmapOptions(BitmapFactory.Options options,
ImageCache cache) {
// inBitmap only works with mutable bitmaps, so force the decoder to
// return mutable bitmaps.
options.inMutable =true;
 
if(cache != null) {
// Try to find a bitmap to use for 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:
protectedBitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap =null;
 
if(mReusableBitmaps != null&& !mReusableBitmaps.isEmpty()) {
synchronized(mReusableBitmaps) {
finalIterator<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();
}
}
}
}
returnbitmap;
}

Finally, this method determines whether a candidate bitmap satisfies the size criteria to be used for inBitmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
staticboolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
 
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use if the byte size of
// the new bitmap is smaller than the reusable bitmap candidate
// allocation byte count.
intwidth = targetOptions.outWidth / targetOptions.inSampleSize;
intheight = targetOptions.outHeight / targetOptions.inSampleSize;
intbyteCount = width * height * getBytesPerPixel(candidate.getConfig());
returnbyteCount <= candidate.getAllocationByteCount();
}
 
// On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
returncandidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize ==1;
}
 
/**
* A helper function to return the byte usage per pixel of a bitmap based on its configuration.
*/
staticint getBytesPerPixel(Config config) {
if(config == Config.ARGB_8888) {
return4;
}else if (config == Config.RGB_565) {
return2;
}else if (config == Config.ARGB_4444) {
return2;
}else if (config == Config.ALPHA_8) {
return1;
}
return1;
}

 

0 0