Android使用磁盘缓存DiskLruCache

简介: 不同于LruCache,LruCache是将数据缓存到内存中去,而DiskLruCache是外部缓存,例如可以将网络下载的图片永久的缓存到手机外部存储中去,并可以将缓存数据取出来使用,DiskLruCache不是google官方所写,但是得到了官方推荐

写在前面

DiskLruCache 不同于LruCache,LruCache是将数据缓存到内存中去,而DiskLruCache是外部缓存,例如可以将网络下载的图片永久的缓存到手机外部存储中去,并可以将缓存数据取出来使用,DiskLruCache不是google官方所写,但是得到了官方推荐,DiskLruCache没有编写到SDK中去,如需使用可直接copy这个类到项目中去。

DiskLruCache地址:
https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java

或者可以在Jake大神的Github上找到:
https://github.com/JakeWharton/DiskLruCache

DiskLruCache常用方法

方法 备注
DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) 打开一个缓存目录,如果没有则首先创建它,directory:指定数据缓存地址 appVersion:APP版本号,当版本号改变时,缓存数据会被清除 valueCount:同一个key可以对应多少文件 maxSize:最大可以缓存的数据量
Editor edit(String key) 通过key可以获得一个DiskLruCache.Editor,通过Editor可以得到一个输出流,进而缓存到本地存储上
void flush() 强制缓冲文件保存到文件系统
Snapshot get(String key) 通过key值来获得一个Snapshot,如果Snapshot存在,则移动到LRU队列的头部来,通过Snapshot可以得到一个输入流InputStream
long size() 缓存数据的大小,单位是byte
boolean remove(String key) 根据key值来删除对应的数据,如果该数据正在被编辑,则不能删除
void delete() 关闭缓存并且删除目录下所有的缓存数据,即使有的数据不是由DiskLruCache 缓存到本目录的
void close() 关闭DiskLruCache,缓存数据会保留在外存中
boolean isClosed() 判断DiskLruCache是否关闭,返回true表示已关闭
File getDirectory() 缓存数据的目录

如何使用DiskLruCache

1、因为要操作外部存储,所以必须要先加上权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

另外要从网络下载图片,还要加上权限:

 <uses-permission android:name="android.permission.INTERNET" />

2、DiskLruCache是在外部存储上(如SD卡),所以首先判断外部存储是否存在:

  /**
   * Get a usable cache directory (external if available, internal otherwise).
   * external:如:/storage/emulated/0/Android/data/package_name/cache
   * internal 如:/data/data/package_name/cache
   *
   * @param context    The context to use
   * @param uniqueName A unique directory name to append to the cache dir
   * @return The cache dir
   */
  public static File getDiskCacheDir(Context context, String uniqueName) {
      final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() 
              ? context.getExternalCacheDir().getPath()
              : context.getCacheDir().getPath();
      return new File(cachePath + File.separator + uniqueName);
  }

(1)、首先判断外部缓存是否被移除或已存满,如果已存满或者外存储被移除,则缓存目录=context.getCacheDir().getPath(),即存到 /data/data/package_name/cache 这个文件系统目录下;
(2)、反之缓存目录=context.getExternalCacheDir().getPath(),即存到 /storage/emulated/0/Android/data/package_name/cache 这个外部存储目录中,PS:外部存储可以分为两种:一种如上面这种路径 (/storage/emulated/0/Android/data/package_name/cache), 当应用卸载后,存储数据也会被删除,另外一种是永久存储,即使应用被卸载,存储的数据依然存在,存储路径如:/storage/emulated/0/mDiskCache,可以通过Environment.getExternalStorageDirectory().getAbsolutePath() + "/mDiskCache" 来获得路径。

3、根据URL下载一个在线图片并把它写到输出流outputstream中:

  /**
     * Download a bitmap from a URL and write the content to an output stream.
     *
     * @param urlString The URL to fetch
     * @return true if successful, false otherwise
     */
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;

        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (Exception e) {
            Log.e(TAG, "Error in downloadBitmap - " + e);
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
            }
        }
        return false;
    }

4、上面已经下载了图片,接着初始化DiskLruCache,并使用DiskLruCache.Editor准备缓存:

  private static final int MAX_SIZE = 10 * 1024 * 1024;//10MB
    private DiskLruCache diskLruCache;
    private void initDiskLruCache() {
        if (diskLruCache == null || diskLruCache.isClosed()) {
            try {
                File cacheDir = CacheUtil.getDiskCacheDir(this, "CacheDir");
                if (!cacheDir.exists()) {
                    cacheDir.mkdirs();
                }
                //初始化DiskLruCache
                diskLruCache = DiskLruCache.open(cacheDir, 1, 1, MAX_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

下载图片时需要放在异步线程里,这里放在了AsyncTask的doInBackground中:

@Override
protected Boolean doInBackground(Object... params) {
    try {
        String key = Util.hashKeyForDisk(Util.IMG_URL);
        DiskLruCache diskLruCache = (DiskLruCache) params[0];
         //得到DiskLruCache.Editor
        DiskLruCache.Editor editor = diskLruCache.edit(key);
        if (editor != null) {
            OutputStream outputStream = editor.newOutputStream(0);
            if (downloadUrlToStream(Util.IMG_URL, outputStream)) {
                publishProgress("");
                //写入缓存
                editor.commit();
            } else {
                 //写入失败
                editor.abort();
            }
        }
        diskLruCache.flush();
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
    return true;
}

上面代码中有个hashKeyForDisk()方法,其作用是把图片URL经过MD5加密生成唯一的key值,避免了URL中可能含有非法字符问题,hashKeyForDisk()代码如下:

 /**
  * A hashing method that changes a string (like a URL) into a hash suitable for using as a
  * disk filename.
  */
 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();
 }

经过上面的代码,我们已经可以看到图片已经缓存到 /storage/emulated/0/Android/data/package_name/cache/CacheDir 这个目录下了:
cacheDir.png

第一个标识为110.78kb大小的就是我们缓存下来的图片,它的名字正是由图片的URL经过MD5加密得到的,它下面的journal文件是用来记录的,来看里面的内容:

journal.png

第一行:libcore.io.DiskLruCache固定写死
第二行:DiskLruCache版本号
第三行:APP版本号,由open()方法的参数appVersion传入
第四行:同一个key可以对应多少文件,由open()方法的参数valueCount传入,一般为1
第五行:空格
第六行:以DIRTY开头,后面跟着的是图片的key值,表示准备缓存这张图片,当调用DiskLruCache的edit()时就会生成这行记录
第七行: 以CLEAN开头,后面跟着的是图片的Key值和大小,当调用editor.commit()时会生成这条记录,表示缓存成功;如果调用editor.abort()表示缓存失败,则会生成REMOVE开头的表示删除这条数据。

5、通过diskLruCache.get(key)得到DiskLruCache.Snapshot,key是经过MD5加密后那个唯一的key,接着使用Snapshot.getInputStream()可以得到输入流InputStream ,进而得到缓存图片:

private Bitmap getCache() {
     try {
         String key = Util.hashKeyForDisk(Util.IMG_URL);
         DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
         if (snapshot != null) {
             InputStream in = snapshot.getInputStream(0);
             return BitmapFactory.decodeStream(in);
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
     return null;
 }
Bitmap bitmap = getCache();
     if (bitmap != null) {
         iv_img.setImageBitmap(bitmap);
     }

效果图:
cache.png

完整Demo地址:
http://download.csdn.net/detail/u013700502/9883037

相关文章
|
5月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
3月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
41 2
|
23天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
46 15
Android 系统缓存扫描与清理方法分析
|
1月前
|
消息中间件 存储 缓存
大数据-71 Kafka 高级特性 物理存储 磁盘存储特性 如零拷贝、页缓存、mmp、sendfile
大数据-71 Kafka 高级特性 物理存储 磁盘存储特性 如零拷贝、页缓存、mmp、sendfile
50 3
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
67 8
|
4月前
|
缓存 编解码 安全
Android经典面试题之Glide的缓存大揭秘
Glide缓存机制包括内存和硬盘缓存。内存缓存使用弱引用的ActiveResources和LRU策略,硬盘缓存利用DiskLruCache。Engine.load方法首先尝试从内存和弱引用池加载,然后从LRU缓存中加载图片,增加引用计数并移出LRU。若缓存未命中,启动新任务或加入现有任务。内存大小根据设备内存动态计算,限制在0.4以下。DiskLruCache使用自定义读写锁,保证并发安全,写操作通过锁池管理,确保高效。
121 0
|
6月前
|
存储 缓存 Windows
探秘磁盘的奥秘:物理结构、缓存和虚拟内存的作用
本文介绍了磁盘的物理结构、磁盘缓存和虚拟内存的重要性,并解释了它们在计算机系统中的作用。了解磁盘的物理结构可以帮助我们更好地理解数据的存储和访问过程,从而进行系统优化和性能提升。磁盘缓存和虚拟内存则能够提高数据读取速度和运行程序的能力。深入了解这些知识,将为您的计算机系统带来更好的性能和效率。
302 1
|
6月前
|
XML 缓存 Java
Android App开发之利用Glide实现图片的三级缓存Cache讲解及实战(附源码 超详细必看 简单易懂)
Android App开发之利用Glide实现图片的三级缓存Cache讲解及实战(附源码 超详细必看 简单易懂)
319 0
|
存储 缓存 Windows
磁盘概述、磁盘缓存、虚拟内存及虚拟内存与内存的交换方式
磁盘概述、磁盘缓存、虚拟内存及虚拟内存与内存的交换方式
278 0
Netty - 探究PageCache磁盘高速缓存
Netty - 探究PageCache磁盘高速缓存
39 0