之前讲的[Android分享] Memory cache。
在需要快速的获取之前显示的图片,Memory cache机制是非常有用的。然而你不能保证图片是否存在Memory cache中。像
GridView 这种控件可能具有很多图片需要显示,很快图片数据就填满了缓存容量。你的App可能会被另一个任务中断。如打进来电话,你的App进入到后台,在这期间很可能被杀死,Memory cache也会销毁。一旦用户返回回来,你的App又要从新开始下载图片。
在这种情况下,可以使用磁盘缓存来保存这些已经处理过的图片,当这些图片在内存缓存中不可用的时候,可以从磁盘缓存中加载从而省略了图片处理过程。当然,从磁盘中获取图片要比在内存中获取图片慢,从磁盘中读取图片的时间是不可预期的,你应当将这个操作放到后台线程中。
注意:如果一些图片使用很频繁,可以把它们存储在ContentProvider中,Android的Gallery App就是这么做的。
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);
}
上面代码中,初始化Disk Cache会有一些磁盘操作,因此不要把初始化操作放在主线程。这样做会有一个问题:在初始化还没有完成,App就会到DiskCache中去取数据。为了解决这个问题,上面代码使用了对象锁。从而保证了只有初始化完后,才可以读取Disk cache。
DiskLruCache类没有对外公开,如果你有Android源码,可以在源码中找到。
在实际的开发中,Memory cache 和 Disk cache很多情况下都是混合使用。首先从Memory cache读取,如果不存在再从Disk cache读取。。。
|