图片缓存机制

    |     2015年8月14日   |   网络通讯   |     0 条评论   |    2158

在 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。

转载请注明来源:图片缓存机制
本文链接地址:https://ai.zhousir.top/?p=964
回复 取消