接上篇 BitMap高效显示策略(三):使用内存缓存技术和BitmapFactory.Options.inBitmap参数,在实现内存 缓存的基础上,再添加文件缓存。
由于Android上单个应用最大可用内存的限制,内存缓存的大小是有限的,这是使用内存缓存的第一个局限,第二个局限是,如果关闭应用,下次打开应用又需要重复网络下载-》加入内存缓存-》读取内存缓存的步骤,这样对离线应用来说,仍然不能满足需求,并且在加载效率、节省流量上依然有提升的空间。
针对以上的问题,可以使用文件缓存来弥补内存缓存的不足。
硬盘缓存的原理是,将加载好的图片存入SD卡,下载图片的url作为文件索引,统一存放在一个文件,下次取图片时,从索引文件中获取文件路径,再从该路径加载图片。文件缓存的实现有现成的方案,是DiskLruCache,这个类的使用方法在这个博主的文章中讲的很清楚了,参照:http://blog.youkuaiyun.com/guolin_blog/article/details/28863651
下面改写上一篇的代码,实现双缓存:
因为涉及多线程读取文件的问题,所以要对文件进行线程同步操作,首先在ImageCache中加上对象锁:
private final Object mDiskCacheLock = new Object();
为ImageCacheParams添加构造方法:
public static class ImageCacheParams {
// mem
public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
// disk
public File diskCacheDir;
public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;
public ImageCacheParams(Context context, String diskCacheSubDir) {
// 初始化缓存目录
diskCacheDir = getDiskCacheDir(context, diskCacheSubDir);
}
getDiskCacheDir:
public static File getDiskCacheDir(Context context,
String diskCacheSubDir) {
String cachePath = "";
// 先从sd卡上找空间
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())
|| Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
}
// 否则内部存储中找
else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + diskCacheSubDir);
}
getDiskCacheDir方法根据手机是否挂载SD卡,设定缓存目录。
接下来添加初始化硬盘缓存的方法:
public void initDiskCache() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache == null || mDiskLruCache.isClosed()) {
//
File diskCacheDir = mCacheParams.diskCacheDir;
if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs();
}
if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {
//init
try {
mDiskLruCache = DiskLruCache.open(
diskCacheDir, 1, 1, mCacheParams.diskCacheSize);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
mDiskCacheStarting = false;
mDiskCacheLock.notifyAll();
}
}
@TargetApi(VERSION_CODES.GINGERBREAD)
public static long getUsableSpace(File path) {
if (Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
return path.getUsableSpace();
}
final StatFs stats = new StatFs(path.getPath());
return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks();
}
给 addBitmapToCache加上硬盘缓存的部分:
//先判断是否允许文件缓存
if (mCacheParams.diskCacheEnabled) {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
final String key = hashKeyForDisk(url);
OutputStream out = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
//如果不存在则存入文件,否则关闭输入流
if (snapshot == null) {
final DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
//获取输出流
out = editor.newOutputStream(DISK_CACHE_INDEX);
//压缩后存入
bitmapDrawable.getBitmap().compress(
mCacheParams.compressFormat, mCacheParams.compressQuality, out);
editor.commit();
out.close();
}
} else {
snapshot.getInputStream(DISK_CACHE_INDEX).close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
由于url中可能有特殊字符,所以文件名key要经过处理,hashKeyForDisk方法负责这个工作:
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
从硬盘缓存中获取图片:
/**
* 从文件缓存中获取图片
* @param data
* @return
*/
public Bitmap getBitmapFromDiskCache(String url) {
String key = hashKeyForDisk(url);
Bitmap bitmap = null;
synchronized (mDiskCacheLock) {
while (mDiskCacheStarting) {
try {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
}
if (mDiskLruCache != null) {
InputStream inputStream = null;
try {
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Disk cache hit");
}
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
if (inputStream != null) {
FileDescriptor fd = ((FileInputStream) inputStream).getFD();
bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(
fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {}
}
}
return bitmap;
}
}
缓存操作的一些其他方法:
/**
* 清空缓存
*/
public void clearCache() {
if (mMemoryCache != null) {
mMemoryCache.evictAll();
}
synchronized (mDiskCacheLock) {
mDiskCacheStarting = true;
if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
try {
mDiskLruCache.delete();
} catch (IOException e) {
Log.e(TAG, "clearCache - " + e);
}
mDiskLruCache = null;
initDiskCache();
}
}
}
//stop时候调用
public void flush() {
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null) {
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//destroy时候调用
public void close() {
synchronized (mDiskCacheLock) {
try {
if (!mDiskLruCache.isClosed()) {
mDiskLruCache.close();
mDiskLruCache = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
关于代码中初始化硬盘缓存:因为是文件操作,为了避免ANR,所以需要使用异步任务进行文件操作:
ImageWorker.initImageCache:
public void initImageCache(FragmentManager fragmentManager,
ImageCache.ImageCacheParams cacheParams) {
mImageCacheParams = cacheParams;
mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
//硬盘缓存初始化任务
new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
}
CacheAsyncTask:
protected class CacheAsyncTask extends AsyncTask<Integer, Void, Void> {
@Override
protected Void doInBackground(Integer... params) {
switch (params[0]) {
case MESSAGE_INIT_DISK_CACHE:
initDiskCacheInternal();
break;
case MESSAGE_FLUSH:
flushCacheInternal();
case MESSAGE_CLEAR:
clearCacheInternal();
break;
case MESSAGE_CLOSE:
closeCacheInternal();
break;
}
return null;
}
}
protected void clearCacheInternal() {
if (mImageCache != null) {
mImageCache.clearCache();
}
}
protected void closeCacheInternal() {
if (mImageCache != null) {
mImageCache.close();
mImageCache = null;
}
}
protected void flushCacheInternal() {
if (mImageCache != null) {
mImageCache.flush();
}
}
protected void initDiskCacheInternal() {
if (mImageCache != null) {
mImageCache.initDiskCache();
}
}
public void clearCache() {
new CacheAsyncTask().execute(MESSAGE_CLEAR);
}
public void flushCache() {
new CacheAsyncTask().execute(MESSAGE_FLUSH);
}
public void closeCache() {
new CacheAsyncTask().execute(MESSAGE_CLOSE);
}
最后,修改ImageWork的BitmapWorkerTask的
doInBackground方法,加上读取硬盘缓存代码:
加载的最终逻辑为:根据下载url先从内存缓存中读取对应的图片,如果没有,则从硬盘缓存中寻找,如果再没有,则从网络上下载。
final String dataString = String.valueOf(mUrl);
//先从文件缓存中读取
if (mImageCache != null && !isCancelled() && getAttachedImageView() != null
&& !mExitTasksEarly) {
bitmap = mImageCache.getBitmapFromDiskCache(dataString);
}
if (bitmap == null && !isCancelled()
&& getAttachedImageView() != null && !mExitTasksEarly) {
bitmap = processBitmap(mUrl);
}
Demo下载地址:
http://download.youkuaiyun.com/detail/ohehehou/8151069