Android Training Caching Bitmaps 翻译
原文:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
图片缓存
在Android开发中,加载一个图片到界面很容易,但如果一次加载大量图片就复杂多了。在很多情况下(比如:ListView,GridView或ViewPager),能够滚动的组件需要加载的图片几乎是无限多的。
有些组件的child view在不显示时会回收,并循环使用,如果没有任何对bitmap的持久引用的话,垃圾回收器会释放你加载的bitmap。这没什么问题,但当这些图片再次显示的时候,要想避免重复处理这些图片,从而达到加载流畅的效果,就要使用内存缓存和本地缓存了,这些缓存可以让你快速加载处理过的图片。
这些缓存,就是本章要讨论的内容。
使用内存缓存
内存缓存以牺牲内存的代价,带来快速的图片访问。LruCache类(API Level 4之前可以使用Support Library)非常适合图片缓存任务,在一个LinkedHashMap中保存着对Bitmap的强引用,当缓存数量超过容器容量时,删除最近最少使用的成员(LRU)。
注意:在过去,非常流行用SoftReference或WeakReference来实现图片的内存缓存,但现在不再推荐使用这个方法了。因为从Android 2.3 (API Level 9)之后,垃圾回收器会更积极的回收soft/weak的引用,这将导致使用soft/weak引用的缓存几乎没有缓存效果。顺带一提,在Android3.0(API Level 11)以前,bitmap是储存在native 内存中的,所以系统以不可预见的方式来释放bitmap,这可能会导致短时间超过内存限制从而造成崩溃。
为了给LruCache一个合适的容量,需要考虑很多因素,比如:
- 你其它的Activity 和/或 Application是怎样使用内存的?
- 屏幕一次显示多少图片?需要多少图片为显示到屏幕做准备?
- 屏幕的大小(size)和密度(density)是多少?像Galaxy Nexus这样高密度(xhdpi)的屏幕在缓存相同数量的图片时,就需要比低密度屏幕Nexus S(hdpi)更大的内存。
- 每个图片的尺寸多大,相关配置怎样的,占用多大内存?
- 图片的访问频率高不高?不同图片的访问频率是否不一样?如果是,你可能会把某些图片一直缓存在内存中,或需要多种不同缓存策略的LruCache。
- 你能平衡图片的质量和数量吗?有时候,缓存多个质量低的图片是很有用的,而质量高的图片应该(像下载文件一样)在后台任务中加载。
这里没有适应所有应用的特定大小或公式,只能通过分析具体的使用方法,来得出合适的解决方案。缓存太小的话没有实际用处,还会增加额外开销;缓存太大的话,会再一次造成OutOfMemory异常,并给应用的其他部分留下很少的内存。
下面是一个图片的LruCache的配置例子:
private LruCache<String, Bitmap> mMemoryCache; @Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.转换单位
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.转换单位
return bitmap.getByteCount() / 1024;
}
};
...
} public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
} public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
注意:在这个例子中,将应用最大可使用内存的八分之一分配给了缓存,在一个普通的或者hdpi设备上,这个值大约至少是4MB(32/8)。在一个800x480分辨率的设备上全屏显示一个填满图片的GridView大约要1.5MB(800*480*4 bytes),因此,这个LruCache至少能够缓存2.5页的图片。
当往ImageView中加载图片时,先检查以下LruCache中是否已经缓存。如果已经缓存,则直接取出并更新ImageView,否则要启动个后台任务来处理图片:
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
} else {
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
task.execute(resId);
}
}
这个BitmapWorkerTask也需要将新处理完的图片添加进缓存:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
...
}
使用磁盘缓存
内存缓存能够加快对最近显示过的图片的访问速度,然而你不能认为缓存中的图片全是有效的。像GridView这样需要大量数据的组件是很容易填满内存缓存的。你的应用可能会被别的任务打断(比如一个来电),它可能会在后台被杀掉,其内存缓存当然也被销毁了。当用户恢复你的应用时,应用将重新处理之前缓存的每一张图片。
在这个情形中,使用磁盘缓存可以持久的储存处理过的图片,并且缩短加载内存缓存中无效的图片的时间。当然从磁盘加载图片比从内存中加载图片要慢的多,并且由于磁盘读取的时间是不确定的,所以要在后台线程进行磁盘加载。
注意:如果以更高的频率访问图片,比如图片墙应用,使用ContentProvider可能更适合储存图片缓存。
下面这个例子除了之前的内存缓存,还添加了一个磁盘缓存,这个磁盘缓存实现自Android源码中的DiskLruCache:
private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override
protected void onCreate(Bundle savedInstanceState) {
...
// Initialize memory cache
...
// Initialize disk cache on background thread
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
new InitDiskCacheTask().execute(cacheDir);
...
} class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
@Override
protected Void doInBackground(File... params) {
synchronized (mDiskCacheLock) {
File cacheDir = params[0];
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
mDiskCacheStarting = false; // Finished initialization
mDiskCacheLock.notifyAll(); // Wake any waiting threads
}
return null;
}
} class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread
Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache
// Process as normal
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
} // Add final bitmap to caches
addBitmapToCache(imageKey, bitmap); return bitmap;
}
...
} public void addBitmapToCache(String key, Bitmap bitmap) {
// Add to memory cache as before
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
} // Also add to disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
mDiskLruCache.put(key, bitmap);
}
}
} public Bitmap getBitmapFromDiskCache(String key) {
synchronized (mDiskCacheLock) {
// Wait while disk cache is started from background thread
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
return mDiskLruCache.get(key);
}
}
return null;
} // Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName);
}
注意:磁盘缓存的初始化会涉及磁盘操作,因此不应该在UI线程做初始化操作。然而,这样做可能会出现初始化完成前,就访问缓存的情况。为了解决这个问题,上面的例子使用了一个锁对象,确保在磁盘缓存初始化完成前,不会有其他对象使用它。
检查内存缓存可以在UI线程,检查磁盘缓存最好在后台线程。永远不要在UI线程做磁盘操作。当图片处理完成,应该将其添加进内存缓存和磁盘缓存中,以备将来不时之需。
Android Training Caching Bitmaps 翻译的更多相关文章
- Android Training
Building Apps with Content Sharing Simple Data --> Intent && ActionProvider 介绍如何让应用程序共享简单 ...
- Android训练课程(Android Training) - 高效的显示图片
高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...
- Android Training精要(六)如何防止Bitmap对象出现OOM
1.使用AsyncTask異步加載bitmap圖片避免OOM: class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> ...
- android性能小贴士 翻译
转自http://developer.android.com/training/articles/perf-tips.html 性能小贴士: 这篇文档主要一些微优化可以提升应用程序性能,但是这些改变不 ...
- Android训练课程(Android Training) - 添加活动栏(使用action bar)
2014-10-28 张云飞VIR 翻译自:https://developer.android.com/training/basics/actionbar/index.html 添加活动栏(Addin ...
- Android训练课程(Android Training) - 构建你的第一个应用
本文翻译自:https://developer.android.com/training/basics/firstapp/index.html 构建你的第一个应用(app) 欢迎来到安卓开发! 这个课 ...
- Android Training - 管理应用的内存
http://hukai.me/android-training-managing_your_app_memory/ Random Access Memory(RAM)在任何软件开发环境中都是一个很宝 ...
- android 6.0特性翻译 --渣渣
所有关于Android 6.0 棉花糖的知识 上下文帮助 1.现在按压:不需要离开你正在运行的app或者访问的网站就可 获取帮助,仅仅触摸和按下Home按钮.(长按Home键,可以在 android ...
- Android Training精要(七)内存管理
在2.3.3及以下版本: 通過定義兩個整形變量來檢測bitmap是否display過或者已經在緩存中 下面的代碼當bitmap滿足兩個條件就被回收掉: 1. 兩個整形變量都變為0 2. bitmap不 ...
随机推荐
- linux C++ 获取服务器外网IP地址(使用系统调用system)
废话不多说,直接贴代码: #include<string.h> #include<stdlib.h> #include<stdio.h> #include<s ...
- Go语言之高级篇beego框架之模型(Models)
一.模型(Models) 1.beego-orm的相关特性 支持 Go 的所有类型存储 -轻松上手,采用简单的 CRUD 风格 -自动 Join 关联表 跨数据库兼容查询 允许直接使用 SQL 查询/ ...
- SSH框架搭建详细图文教程(转)
这篇文章看的我醍醐灌顶的感觉,比之前本科时候学习的SSH架构 要清晰数倍 非常感觉这篇博主的文章 文章链接为:http://blog.sina.com.cn/s/blog_a6a6b3cd01017 ...
- [Canvas]Bombman v1.00
爆破小人Canvas版,请点此下载,并用浏览器打开试玩. 图例: 源码: <!DOCTYPE html> <html lang="utf-8"> <m ...
- 函数func_splitString:将字符串按指定方式分割,获取指定位置的数
CREATE FUNCTION `func_splitString` ( f_string varchar(1000),f_delimiter varchar(5),f_order int) RETU ...
- Android性能优化-减小APK大小
前言 用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备.这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app. 一 理解APK的结构 在讨论如何减小ap ...
- maven超级pom内容
1.位置 2.内容 <?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the A ...
- sqlserver修改主键为自增
使用PowerDesigner创建一张表, 拷贝建表语句发现ID不是自增的, 以下是修改语句: ALTER TABLE USER_JOB_EXE_REC DROP COLUMN id; , ); 注: ...
- [Aaronyang] 写给自己的WPF4.5 笔记8[复杂数据处理三步曲,数据视图精讲1/3]
真的好累了 ,笑了.做回自己吧 ------------- Aaronyang技术分享 www.ayjs.net 博文摘要: 详细介绍了WPF中视图的种类和开始学之前的准备工作 ...
- 【转】iPhone X
iPhone X 在 CIIA 第一期报告中,我剖析了 iPhone 从诞生以来就存在的,以及后来产生的一些设计问题.昨天在苹果店里玩了一下 iPhone X,发现它不但继承了以往的 iPhone 的 ...