注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接: http://developer.android.com/training/displaying-bitmaps/manage-memory.html
接着上一节课的步伐,还有很多特定的事情可以让垃圾回收和位图重用变得容易。根据你的目标Android系统的不同版本,推荐的策略也会有所不同。这系列课程的样例代码: BitmapFun (链接: http://www.cnblogs.com/jdneo/p/3512517.html )向你展示了你的应用如何在不同的Android版本间有效率地工作。
在开始这堂课之前,我们先来看一下Android对于位图存储管理的演变:
- 在Android 2.2(API Level 8)之前,当垃圾回收发生时,你应用的线程会停止。由此导致的延迟会降低应用的性能表现。 在Android 2.3开始加入了并发式的垃圾回收,这意味着当一个位图不再被引用时,对应的内存会被迅速回收 。
- 在Android 2.3.3(API Level 10)之前,一副位图的依托像素数据(backing pixel data)存储于本机内存中。它和位图本身是分离开的,位图存储于 Dalvik堆中。本机内存中的像素数据并不会以一种可预测的形式进行释放,因此可能会导致一个应用超过它的内存限制而崩溃。 从Android 3.0(API Level 11)开始,像素数据和与其关联的位图都存储于Dalvik堆中了 。
下面的章节将会描述如何对不同的Android版本,优化位图存储管理。
一). 在Android 2.3.3及之前的系统版本上管理内存
在Android 2.3.3(API Level 10)及以前,推荐使用 recycle() 。如果你在你的应用中要显示大量的位图数据,你极有可能引起 OutOfMemoryError 错误。 recycle() 可以让应用尽快地释放内存。
Caution:
只有当你确定对应的位图将不再被使用的情况下,你才应该使用 recycle() 。如果你使用了 recycle() ,并在之后尝试绘制该位图,那么你将会得到这样的错误提示:“ Canvas: trying to use a recycled bitmap. ”
下面的代码是调用 recycle() 的样例。它使用引用计数(变量“ mDisplayRefCount ”以及“ mCacheRefCount ”)来追踪位图是否是当前正在显示或是在缓存中。当下列情况发生时,代码会回收位图:
- “ mDisplayRefCount ”以及“ mCacheRefCount ”的引用计数都为0。
- 位图不是“ null ”,同时它还未被回收。
private
int
mCacheRefCount = 0
;
private
int
mDisplayRefCount = 0
;
...
//
Notify the drawable that the displayed state has changed.
//
Keep a count to determine when the drawable is no longer displayed.
public
void
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.
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() {
//
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();
}
}
private
synchronized
boolean
hasValidBitmap() {
Bitmap bitmap
=
getBitmap();
return
bitmap !=
null
&& !
bitmap.isRecycled();
}
二). 在Android 3.0及以后的系统版本上管理内存
Android 3.0(API Level 11)引入了
BitmapFactory.Options.inBitmap
域。如果配置了该选项,接受该
Options
对象的解码方法会在加载内容时尝试去重用已经存在的位图。这意味着位图内存被重用了,从而提高了性能表现,同时免去了内存分配和回收的工作。然而,对于如何使用
inBitmap
会有一些限制。特别地,在Android 4.4(API Level 19)之前,只有尺寸吻合的位图才被支持。更多详细信息,可以阅读:
inBitmap
。
保存一幅位图以备将来使用
下面的代码样例展示了在相同的应用中,一个已经存在的位图是如何为了将来可能被再次使用到而保存的。当一个应用在Android 3.0及以上的设备上运行,且一个位图从 LruCache 中移除,该位图的软引用会放置在一个 HashSet 中,以备之后 inBitmap 使用:
Set<SoftReference<Bitmap>>
mReusableBitmaps;
private
LruCache<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(
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)) {
//
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()));
}
}
}
....
}
使用一个存在的位图
在运行的程序中,解码方法检查是否有可用的位图。例如:
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() 方法的实现。它寻找一个存在的位图,并将它作为 inBitmap 的值。注意,该方法仅仅在它找到了一个合适的匹配时,才将位图设置为 inBitmap 的值(你的代码不可以假定这个匹配一定能找到):
private
static
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:
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()) {
//
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;
}
最后,此方法决定备选的位图是否符合 inBitmap 的尺寸标准:
static
boolean
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.
int
width = targetOptions.outWidth /
targetOptions.inSampleSize;
int
height = targetOptions.outHeight /
targetOptions.inSampleSize;
int
byteCount = width * height *
getBytesPerPixel(candidate.getConfig());
return
byteCount <=
candidate.getAllocationByteCount();
}
//
On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
return
candidate.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.
*/
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
;
}

