您的位置:首页 > 移动开发 > Android开发

Android官网培训:管理位图内存

2013-05-29 07:15 405 查看
在《缓存位图》一课中,我们用缓存的方法提高了位图的重用,除了cache缓存的应用以外,你还可以做其他几个事情来帮助虚拟机进行内存回收和位图重用。根据你的app的目标android版本的不同,我们所推荐的策略也不相同。BitmapFun样例说明了如何在跨越不同版本android的情况下设计app,使app高效的工作。

在Android2.2(API level 8)以及更低版本,当内存回收发生时,你的app的线程会被停止,导致一定延迟,降低执行效率。android2.3添加了并发的垃圾回收,当位图不在被引用的时候,内存就被回收重用。
在Android2.3.3(API level 10)以及更低版本,位图的pixel data被存储在native memory中,而位图本身是存储在Dalvik heap中。native memory中的pixel data不会以可预知的方式进行释放,因此可能会让一个app超出内存限制而崩溃。Android3.0(API level 11)中pixel data和其关联的位图均存放在Dalvik
heap中。
下面章节描述了在不同的Android版本下,如何优化位图内存的管理。

Android2.3.3及更低版本内存管理

在Android2.3.3(API level 10)及更低版本,推荐用recycle()。如果在你的app中展示大量的位图数据,很可能导致OutOfMemoryError错误。recycle()方法允许app尽可能回收内存。
注:仅当你确定位图数据不会再被使用时,你才可以调用recycle()。如果你先调用了recycle()然后试图重绘这个位图,你会收到错误”Canvas:trying to use a recycled bitmap"。
以下代码片段给出了一个调用recycle()的例子。这个例子用引用计数(mDisplayRefCount和mCacheRefCount)来跟踪一个位图是正在被显示还是在缓存之中。当遇到以下情况这段代码会recycle位图。
1.mDisplayRefCount和mCacheRefCount都是0
2.bitmap不是null,并且它还未被recycle。

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();
}

在Android3.0及以上版本管理内存

Android3.0(API level 11)引入了BitmapFactory.Options.inBitmap。如果设置了这个选项,解码方法会在加载内容时尝试重用已经存在的bitmap。这意味着bitmap的内存被重用,带来的结果是性能的提升,并且减少了内存分配和内存释放。inBitmap选项有几点注意事项:
1.被重用的bitmap必须和试图加载的源内容的尺寸相同(为了保证内存大小一样),并且必须是JPEG或者PNG格式(无论是以resoucre形式还是stream形式)。
2.被重用的bitmap的configuration会覆盖对inPreferredConfig的设定。
3.你应该坚持使用解码方法返回的bitmap,因为bitmap的重用不保证一定有效(例如,尺寸不匹配)。

为不久将来的使用而保存一个位图
以下的代码片段阐释了如何为不久将来的使用而存储一个bitmap。当一个app是运行在Andorid3.0及更高的版本上,当一个位图从LruCache被剔除出来时,对该位图的一个soft引用会被置于一个HashSet中,以备将来inBitmap的重用。

HashSet<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;

// 如果要运行在Honeycomb或更新的版本上,创建bitmap的引用的HashSet
if (Utils.hasHoneycomb()) {
mReusableBitmaps = new HashSet<SoftReference<Bitmap>>();
}

mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {

// 不再被缓存的条目.
@Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// 被移除的条目是一个正在recycle的图片,所以通知其已经被从缓存中移除
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// 被移除条目是standard BitmapDrawable.
if (Utils.hasHoneycomb()) {
// 运行在Honeycomb及更高版本,所以把位图加到HashSet集合
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。注意只有当它找到一个合适的匹配时(你的代码应当假设这个匹配可能不会发生)这个方法才会设置一个值。

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()) {
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;
}


接下来,canUseForInBitmap()决定是否一个候选bitmap能够满足被inBitmap使用的尺寸标准。

private static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;

// Returns true if "candidate" can be used for inBitmap re-use with
// "targetOptions".
return candidate.getWidth() == width && candidate.getHeight() == height;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Bitmap android Cache