02.磁盘缓存之DiskLruCache.md 9.1 KB

目录介绍

  • 01.磁盘缓存作用和说明
  • 02.磁盘缓存提问答疑
  • 03.做了那些细节优化

01.磁盘缓存作用和说明

1.1 DiskLruCache的创建

  • DiskLruCache 并不能通过构造方法来创建,它提供了 open() 方法用于创建自身

    File path = DiskFileUtils.getFilePath(context);
    String pathString = path.getPath();
    try {
        diskLruCache = DiskLruCache.open(path,1,1,1000);
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  • open方法的四个参数说明

    • 第一个参数表示磁盘缓存在文件系统中的存储路径。缓存路径可以选择 SD 卡上的缓存目录,具体是指 /sdcard/Android/data//cache 目录,其中 表示当前应用的包名,当应用被卸载后,此目录会一并被删除。当然也可以选择 SD 卡上的其他指定目录,还可以选择 data 下的当前应用的目录,具体可根据需要灵活设定。这里给出一个建议:如果应用卸载后就希望删除缓存文件,那么就选择 SD 卡上的缓存目录,如果希望保留缓存数据那就应该选择 SD 卡上的其他特定目录。
    • 第二个参数表示应用的版本号,一般设为 1 即可。当版本号发生改变时 DiskLruCache 会清空之前所有的缓存文件,而这个特性在实际开发中作用并不大,很多情况下即使应用的版本号发生了改变缓存文件却仍然是有效的,因此这个参数设为 1 比较好。
    • 第三个参数表示同一个 key 可以对应多少个缓存文件,一般设为 1 即可。
    • 第四个参数表示缓存的总大小,比如 50MB,当缓存大小超出这个设定值后,DiskLruCache 会清除一些缓存从而保证总大小不大于这个设定值。

    1.2 DiskLruCache添加数据

    • DiskLruCache 的缓存添加的操作是通过 Editor 完成的
      • Editor 表示一个缓存对象的编辑对象。这里仍然以图片缓存举例,首先需要获取图片url 所对应的 key,然后根据 key 就可以通过 edit() 来获取 Editor 对象,如果这个缓存正在被编辑,那么 edit() 会返回 null,即 DiskLruCache 不允许同时编辑一个缓存对象。
      • 之所以要把 url 转换成 key,是因为图片的 url 中很可能有特殊字符,这将影响 url 在 Android 中直接使用,一般采用 url 的 md5 值作为 key。
    • 将图片的 url 转成 key 后,就可以获取 Editor 对象了。

      • 对于这个 key 来说,如果当前不存在其他 Editor 对象,那么 edit() 就会返回一个新的 Editor 对象,通过它就可以得到一个文件输出流。需要注意的是,由于前面在 DiskLruCache 的 open 方法中设置了一个节点只能有一个数据,因此下面的 DISK_CACHE_INDEX 常量直接设为 0 即可,如下所示

        String key = hashKeyFormUrl(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if (editor ! = null) {
        OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
        }
        
    • 当从网络下载图片时,图片就可以通过文件输出流写入到文件系统上,这个过程的实现如下所示

      public boolean downloadUrlToStream(String urlString, utputStream 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 (IOException e) {
              Log.e(TAG, "Download bitmap failed. " + e);
          } finally {
              if (urlConnection ! = null) {
              urlConnection.disconnect();
          }
          return false;
      }
      
    • 之后,还必须通过 Editor 的 commit() 来提交写入操作,真正地将图片写入文件系统。

      • 如果图片下载过程发生了异常,那么还可以通过 Editor 的 abort() 来回退整个操作,这个过程如下所示

        OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
        if (downloadUrlToStream(url, outputStream)) {
        editor.commit();
        } else {
        editor.abort();
        }
        mDiskLruCache.flush();
        
    • 经过上面的几个步骤,图片已经被正确地写入到文件系统了,接下来图片获取的操作就不需要请求网络了。

    2.3 DiskLruCache 的查找

    • 和缓存的添加过程类似

      • 缓存查找过程也需要将 url 转换为 key,然后通过 DiskLruCache 的 get() 方法得到一个 Snapshot 对象,接着再通过 Snapshot 对象即可得到缓存的文件输入流,有了文件输入流,自然就可以得到 Bitmap 对象了。
      • 为了避免加载图片过程中导致的 OOM 问题,一般不建议直接加载原始图片。可以通过 BitmapFactory.Options 对象来加载一张缩放后的图片,但是那种方法对 FileInputStream 的缩放存在问题,原因是 FileInputStream 是一种有序的文件流,而两次 decodeStream 调用影响了文件流的位置属性,导致了第二次 decodeStream 时得到的是 null。
      • 为了解决这个问题,可以通过文件流来得到它所对应的文件描述符,然后再通过 BitmapFactory.decodeFileDescriptor() 方法来加载一张缩放后的图片,这个过程的实现如下所示

        Bitmap bitmap = null;
        String key = hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
        if (snapShot ! = null) {
        FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(DISK_CACHE_INDEX);
        FileDescriptor fileDescriptor = fileInputStream.getFD();
        bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor, reqWidth, reqHeight);
        if (bitmap ! = null) {
            addBitmapToMemoryCache(key, bitmap);
        }
        }
        
    • 每个实体都是文件,你可以利用 fileInputStream 读取出里面的内容,然后做其他操作。

      • 上面介绍了 DiskLruCache 的创建、添加和查找过程,除此之外,DiskLruCache 还提供了 remove() 、delete() 等方法用于磁盘缓存的删除操作。

    2.4 DiskLruCache 的移除

    • remove() 方法中要求传入一个 key,然后会删除这个 key 对应的缓存。示例代码如下

      try {
          String imageUrl = "https://chittyo/img.jpg";  
          String key = hashKeyForDisk(imageUrl);  
          mDiskLruCache.remove(key);
      } catch (IOException e) {
          e.printStackTrace();
      }
      
    • remove() 方法我们并不经常去调用它。

      • 只有当你确定某个 key 对应的缓存内容已经过期,需要从网络获取最新的数据时才应该调用 remove() 方法来移除缓存。
    • 我们完全不用担心因缓存数据过多而占用太多 SD 卡空间的问题,DiskLruCache 会根据我们在调用 open() 方法时设定的缓存最大值来自动删除多余的缓存。

    02.磁盘缓存提问答疑

    • 存或取key如何取值?
      • key将会成为缓存文件的文件名,并且必须要和图片的URL是一一对应的。那么怎样才能让key和图片的URL能够一一对应呢?直接使用URL来作为key?
      • 不太合适,因为图片URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。其实最简单的做法就是将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。
    • 这个key为何不用字符串hash而用md5值?
      • hash可能会出现碰撞,而md5为代表的杂凑函数几乎不可能找到碰撞。看glide源码,发现源码中使用磁盘缓存图片时,对链接也是采用md5加密得到key?
    • 读写数据完后需要flush吗?
      • 这个方法用于将内存中的操作记录同步到日志文件(也就是journal文件)当中。这个方法非常重要,因为DiskLruCache能够正常工作的前提就是要依赖于journal文件中的内容。
      • 在写入缓存操作的时候有调用过一次这个方法,但其实并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处,只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了。
    • 移除缓存remove需要手动调用吗?
      • remove()方法中要求传入一个key,然后会删除这个key对应的缓存图片。用法虽然简单,但是你要知道,这个方法我们并不应该经常去调用它。
      • 因为你完全不需要担心缓存的数据过多从而占用SD卡太多空间的问题,DiskLruCache会根据我们在调用open()方法时设定的缓存最大值来自动删除多余的缓存。只有你确定某个key对应的缓存内容已经过期,需要从网络获取最新数据的时候才应该调用remove()方法来移除缓存。

    03.做了那些细节优化