【Android Developers Training】 59. 管理图片存储
注:本文翻译自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;
}
【Android Developers Training】 59. 管理图片存储的更多相关文章
- 【Android Developers Training】 80. 管理网络使用
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 43. 序言:管理音频播放
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 14. 序言:管理Activity生命周期
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 55. 序言:高效显示位图
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 2. 运行你的应用
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 108. 使用模拟定位进行测试
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 94. 创建一个空内容提供器(Content Provider)
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 93. 创建一个空验证器
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
- 【Android Developers Training】 92. 序言:使用同步适配器传输数据
注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...
随机推荐
- JAVA基础知识面试题
一个JAVA文件可以定义多个类,但是只能有一个是public(也可以没有public类),并且该public的类的名称和JAVA文件名称相同.同时一个java文件可以有多个main方法,只有和java ...
- (转)static 变量
一. static 变量 static变量大致分为三种用法1. 用于局部变量中,成为静态局部变量. 静态局部变量有两个用法,记忆功能和全局生存期.2. 用于全局变量,主要作用是限制此全局变量被其他的文 ...
- Spring+SpringMVC+MyBatis深入学习及搭建(三)——MyBatis全局配置文件解析
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6874672.html 前面有写到Spring+SpringMVC+MyBatis深入学习及搭建(二)——My ...
- C#的内存管理原理解析+标准Dispose模式的实现
本文内容是本人参考多本经典C#书籍和一些前辈的博文做的总结 尽管.NET运行库负责处理大部分内存管理工作,但C#程序员仍然必须理解内存管理的工作原理,了解如何高效地处理非托管的资源,才能在非常注重性能 ...
- Spark实战之读写HBase
1 配置 1.1 开发环境: HBase:hbase-1.0.0-cdh5.4.5.tar.gz Hadoop:hadoop-2.6.0-cdh5.4.5.tar.gz ZooKeeper:zooke ...
- python求职Top10城市,来看看是否有你所在的城市
前言 从智联招聘爬取相关信息后,我们关心的是如何对内容进行分析,获取用用的信息. 本次以上篇文章“5分钟掌握智联招聘网站爬取并保存到MongoDB数据库”中爬取的数据为基础,分析关键词为“python ...
- iOS tableView移除某一行的分割线 让分割线宽度为整个cell的宽度
移除tableViewCell中某一行的分割线 有2种方法 1. 移除系统的分割线,自己定义每行的分割线 self.tableView.separatorStyle = UITableViewCell ...
- javaWeb学习总结(8)- JSP基础语法(2)
任何语言都有自己的语法,JAVA中有,JSP虽然是在JAVA上的一种应用,但是依然有其自己扩充的语法,而且在JSP中,所有的JAVA语句都可以使用. 一.JSP模版元素 JSP页面中的HTML内容称之 ...
- 开涛spring3(6.9) - AOP 之 6.9 代理机制
Spring AOP通过代理模式实现,目前支持两种代理:JDK动态代理.CGLIB代理来创建AOP代理,Spring建议优先使用JDK动态代理. JDK动态代理:使用java.lang.reflect ...
- java虚拟机学习-JVM调优总结(6)
1.Java对象的大小 基本数据的类型的大小是固定的,这里就不多说了.对于非基本类型的Java对象,其大小就值得商榷. 在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个 ...