在 Android加载网络图片 一章里,滚动列表会反复触发异步任务重新下载,既费流量体验也差。显示前先查缓存:内存缓存用 LruCache,文件缓存用 DiskLruCache。
一、内存缓存 LruCache
Lru 是最近最少使用。先定缓存上限,空间不够时按 Lru 回收最久没用的 Bitmap。Google 已提供 LruCache,常用最大可用内存的 1/8。
private LruCache<String, Bitmap> mMemoryCache;
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
写入前先查,避免覆盖;读取不存在则返回 null。key 用图片 URL。
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemoryCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemoryCache(String key) {
return mMemoryCache.get(key);
}
二、文件缓存 DiskLruCache
内存有限、图多时再加磁盘。查找顺序:内存 → 文件 → 网络。DiskLruCache 源码在 Google Source:android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java
open() 四个参数:缓存目录、应用 versionCode、同一 key 对应几个文件(几乎都传 1)、最大字节数。
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, 1001, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
缓存路径:外置存储可用则用 getExternalCacheDir(),否则内部 getCacheDir()。
public File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
三、写入与读取
URL 里常有不能当文件名的字符,key 先做 MD5。
public 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 String bytesToHexString(byte[] bytes) {
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();
}
完整写入:edit(key) → newOutputStream(0) 把网络图写进去 → 成功 commit(),失败 abort(),最后 flush()。
new Thread(new Runnable() {
@Override
public void run() {
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
读取:get(key) 拿到 Snapshot,再 decodeStream。
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
mImage.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
| 层 |
实现 |
要点 |
| 内存 |
LruCache<String, Bitmap> |
maxMemory/8,sizeOf 用 getByteCount |
| 磁盘 |
DiskLruCache |
URL 先 MD5;commit / abort / flush |
| 网络 |
下载写入 OutputStream |
前两层都 miss 才走 |
注:Android DiskLruCache完全解析,硬盘缓存的最佳方案 对文件缓存有更详细介绍,可以参考学习。
一句话总结:先 LruCache 再 DiskLruCache,都没有才下载;磁盘 key 用 MD5,写入用 commit/abort。