android DiskCacheDir 硬盘缓存

简介: android DiskCacheDir 硬盘缓存

介绍


DiskLruCache 是硬盘缓存,他并没有显示 数据的缓存位置,可以自由的设置,但是通常情况下 程序员都会将缓存的位置选为 /sdcard/Android/data//cache 这个路径。在这个路径有两个好处,第一:这是存储在 SD 卡上的,因此不会对内存有什么影响,第二:这个路径被Android 认定为 应用程序的缓存路径,当程序卸载时,这里的我数据也会被 清除掉,这样就不会出现程序卸载后还有残留数据的问题。


举个例子,如果应用程序的包名是 com.netease.newsreader.activity ,那么他的缓存地址就是 /sdcard/Android/data/com.netease.newsreader.activity/cache 。这个目录下的文件待会在看,


对DiskLruCache 有了大概了解后,下面就学一下他的用法


首先 添加一下依赖


api 'com.jakewharton:disklrucache:2.0.2'


打开缓存


DiskLruCache 是不能创建实例的,如果需要创建实例,则需要调用他的 open 方法,如下所示:


// 1,数据的缓存地址,2,指定当前应用程序的版本号
 // 3,指定同一个 key 可以对应多少个缓存文件,基本都是1
 // 4,指定据图可以穿出多少字节的数据
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)


自定义缓存地址,通常是 /sdcard/Android/data//cache 这个路径下


/**
     * @return 获取 缓存的路径
     */
    private File getCacheDir(Context context, String uniqueName) {
        String cachePath;
        // 判断 SD 卡是否可用
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            //获取 有sd 卡时的路径
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            // 获取 无sd 卡时的路径
            cachePath = context.getCacheDir().getPath();
        }
        //File.separator 分隔符 /
        return new File(cachePath + File.separator + uniqueName);
    }


getExternalCacheDir()方法来获取缓存路径 为 /sdcard/Android/data//cache


getCacheDir() 方法获取的路径为 /data/data//cache


uniqueName 是为了对不同类型数据进行区分 而设置的一个唯一值,


获取版本号:


private int getVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }


需要注意的是 当版本号发生改变后,缓存路径下的所有数据都会被清掉,所有的数据都要从网上获取


后面两个参数就没什么需要解释的了,第三个参数传1,第四个参数通常传入10M的大小就够了,这个可以根据自身的情况进行调节


因此一个open 方法的写法就可以这样写


public void open(Context context, String uniqueName) {
        File cacheDir = getCacheDir(context,uniqueName);
        //路径是否存在,不存在则创建
        if (!cacheDir.exists()){
            cacheDir.mkdirs();
        }
        try {
            DiskLruCache.open(cacheDir, getVersion(context),
                    1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


写入缓存:


比如现在有一个图片,为了将图片下载,可以这样写


public class MyOkhttp {
    public interface onRequestListener {
        void onSuccess(List<Object> data);
    }
    private Handler handler = new Handler(Looper.myLooper());
    private OkHttpClient client = new OkHttpClient();
    private ArrayList<Object> list;
    public void request(final String url, final onRequestListener listener) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Request request = new Request.Builder()
                        .url(url)
                        .get()
                        .build();
                InputStream is = null;
                try {
                    final ResponseBody body = client.newCall(request).execute().body();
                    if (body != null) {
                        list = new ArrayList<>();
                        byte[] buff = new byte[2048];
                        int len;
                        is = body.byteStream();
                        while ((len = is.read(buff)) != -1) {
                            for (int i = 0; i < len; i++) {
                                list.add(buff[i]);
                            }
                        }
                    }
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            listener.onSuccess(list);
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}


public void writeData(DiskLruCache diskLruCache, String url, int index, final onCacheDataListener listener) {
        dowloadData(diskLruCache, url, index, listener);
    }
    private void dowloadData(final DiskLruCache diskLruCache, String url, final int index, final onCacheDataListener listener) {
        try {
            String key = hashKeyForDisk(url);
            final DiskLruCache.Editor editor = diskLruCache.edit(key);
            if (editor != null) {
                MyOkhttp myOkhttp = new MyOkhttp();
                myOkhttp.request(url, new MyOkhttp.onRequestListener() {
                    @Override
                    public void onSuccess(List<Object> data) {
                        //获取缓存文件的输入流,将缓存文件写入到本地
                        OutputStream ops = null;
                        if (data != null) {
                            byte[] buff = new byte[data.size()];
                            for (int i = 0; i < data.size(); i++) {
                                buff[i] = (byte) data.get(i);
                            }
                            try {
                                ops = editor.newOutputStream(index);
                                ops.write(buff);
                                editor.commit();
                                diskLruCache.flush();
                                if (listener != null) {
                                    listener.onData(buff);
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                                try {
                                    editor.abort();
                                } catch (IOException e1) {
                                    e1.printStackTrace();
                                }
                            } finally {
                                if (ops != null) {
                                    try {
                                        ops.close();
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


在上面 通过okhttp 将文件缓存到集合,并且通过一个接口将下载的数据返回在调用者。注意缓存完了以后一定要调用 editor.commit(); 提交一下,否则缓存的文件无法加载和删除。在缓存到本地的时候 通过DiskLruCache.Editor 来进行写入,这个类同样不能 new ,需要调用 DiskLruCache 的 edit(key)来获取实例,这个key就是文件的名字,并且这个图片的 url 和 可key 必须对应,所以这里使用了 MD5 编码,编码后的字符串是 唯一的,并且只会包含 0-F 这些字符,完全符合命名规则,如下所示:


private 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[] digest) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < digest.length; i++) {
        String hex = Integer.toHexString(0xFF & digest[i]);
        if (hex.length() == 1) {
            sb.append('0');
        }
        sb.append(hex);
    }
    return sb.toString();
}


只需要调用一下 hashKeyForDisk() 将url 传入 就可以得到对应的 key 了。


因此一个完整的 写入如下所示:


DataCache dataCache = new DataCache();
final DiskLruCache disk = dataCache.open(MainActivity.this, "image");
String url = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
dataCache.writeData(disk, url, 0, new DataCache.onCacheDataListener() {
   @Override
  public void onData(byte[] bytes) {
     if (bytes != null) {
       Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        imageView.setImageBitmap(bitmap);
    }
  }
});


调用一下 writeData 方法 就会自动缓存,并且将缓存的 数据以字节数组的形式回调过来。


进入到目录 下看一下


0a2653c851af460fa595bd959398a8f1.png


文件名字很长的 那个文件就是缓存的文件。


读取缓存


读取缓存主要使用 DiskLruCache 的 get 方法来读取的。方法如下所示:


public synchronized Snapshot get(String key) throws IOException


很明显 需要传入一个 key,这个可以 就是 我们在缓存时 使用的key了。


下面看一下使用:


public byte[] readCache(DiskLruCache diskLruCache, String url) {
    try {
        List<Byte> data = new ArrayList<>();
        String key = hashKeyForDisk(url);
        DiskLruCache.Snapshot snapShot = diskLruCache.get(key);
        if (snapShot != null) {
            InputStream is = snapShot.getInputStream(0);
            byte[] bytes = new byte[2048];
            int len;
            while ((len = is.read(bytes)) != -1) {
                for (int i = 0; i < len; i++) {
                    data.add(bytes[i]);
                }
            }
            bytes = new byte[data.size()];
            for (int i = 0; i < bytes.length; i++) {
                bytes[i] = data.get(i);
            }
            return bytes;
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}


上面 就是 读取缓存,通过 getInputStream 获取到 输入流,就可以读取数据了,同样的 ,这里也需要传入一个参数,这里传入 0 就好了。


使用如下:


DataCache dataCache = new DataCache();
final DiskLruCache disk = dataCache.open(MainActivity.this, "image");
String url = "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
byte[] bytes = dataCache.readCache(disk, url);
if (bytes != null) {
    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    image.setImageBitmap(bitmap);
}else {
    Toast.makeText(MainActivity.this, "空", Toast.LENGTH_SHORT).show();
}


直接调用方法就 会返回一个字节数组,然后将数组转为 bitmap对象,最后 设置到 Imageview 就可以了。


移除缓存


移除非常简单,他是使用DiskLruCache的remove()方法实现的 ,使用如下


public boolean removeCache(DiskLruCache diskLruCache, String key) {
    String md5Key = hashKeyForDisk(key);
    boolean remove = false;
    try {
        remove = diskLruCache.remove(md5Key);
        return remove;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return remove;
}


boolean b = dataCache.removeCache(disk, url);
if (b){
    Toast.makeText(MainActivity.this, "成功", Toast.LENGTH_SHORT).show();
}else {
    Toast.makeText(MainActivity.this, "失败", Toast.LENGTH_SHORT).show();
}


其他的方法


1,size()


这个方法会返回 缓存路径下所有缓存数据的总字节数,以 byte 为单位


2,flush()


这个方法用于将内存中操作记录同步到日志文件(也就是 journal 文件),这个方法非常重要,DiskLruCache 能够正常工作的 前提条件就是依赖于 journal文件中的内容,在前面写入缓存的时候 我调用了一次这个方法,但是并不是写入缓存就要调这个方法,频繁调用这个方法没有任何好处,比较标准的做法就是在 Activity 的 onPause 方法中调用一次 flush 方法就可以了。


3,close()


这个方法用于将 DiskLruCache 关掉,和open 对应的一个方法,关了之后就不能进行任何的操作了,同常在 onDestory 中关闭即可。


4,delete()


这个方法会将 缓存的全部数据删除。


5,getDirectory()


获取换出数据的目录


解读 journal


2d65d23f6d4748949b924e4057485923.png


journal 是 DiskLruCache 能够正常使用的前提,因此 理解 journal 就是非常重要的了,


第一行 是 libcore.io.DiskLruCache” ,表示我们使用了 DiskLruCache


第二行 是 DiskLruCache 的版本号,这个值是恒为 1 的,


第三行 是 应用程序的版本号。


第四行 是 valueCount ,这个值是open 方法中传入的,通常情况都是 1


第五行 是 空格,第五行 往上被称为 journal 的 文件头


第六行 是 DIRTY 开始的,后面是 图片的key,通常 DIRTY 代表着这是一条脏数据。每当我们调用一次 edit 方法后 ,都会在 journal 文件中写入一条 DIRTY 的记录,但是不知道结果如何,然后调用 commit()方法表示缓存成功,这是会添加一个 CLEAN 的记录,意味着 这条 “脏” 数据被洗干净了。调用abort 方法表示缓存失败,这是 会添加一条 REMOVE 的记录。也就是说 每一行 的 DIRTY 的key 后面都要 有 一行对应的 CLEAN 或者 REMOVE 的记录,否则这个数据就是 “脏”的,会被自动删除掉


CLEAN 的最后面 跟了一个 数子,这个数字的意识 就是 缓存数据的大小,以字节为单位。


还有一种你READ 开头的记录,每当 通过get 去读取 一掉缓存时,就会有 一条 READ 记录。


DiskLruCache 中使用了一个 redundantOpCount 的遍历 来记录用户的操作次数,每执行一次 写入,读取 或者 移除,这个变量就会 加1,当 变量值 达到 2000 的时候 就会 对 journal 进行重构,删除不必要的记录,保证 journal文件 的大小始终 保持在一个合理的范围内。


相关文章
|
5月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
缓存 Android开发
Android Studio中如何清理gradle缓存
Android Studio中如何清理gradle缓存
|
3月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
41 2
|
26天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
50 15
Android 系统缓存扫描与清理方法分析
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
70 8
|
4月前
|
缓存 编解码 安全
Android经典面试题之Glide的缓存大揭秘
Glide缓存机制包括内存和硬盘缓存。内存缓存使用弱引用的ActiveResources和LRU策略,硬盘缓存利用DiskLruCache。Engine.load方法首先尝试从内存和弱引用池加载,然后从LRU缓存中加载图片,增加引用计数并移出LRU。若缓存未命中,启动新任务或加入现有任务。内存大小根据设备内存动态计算,限制在0.4以下。DiskLruCache使用自定义读写锁,保证并发安全,写操作通过锁池管理,确保高效。
121 0
|
6月前
|
XML 缓存 Java
Android App开发之利用Glide实现图片的三级缓存Cache讲解及实战(附源码 超详细必看 简单易懂)
Android App开发之利用Glide实现图片的三级缓存Cache讲解及实战(附源码 超详细必看 简单易懂)
323 0
|
缓存 Java Android开发
Android C++ 系列:JNI 调用时缓存字段和方法 ID
通常我们通过 FindClass 、GetFieldID、GetMethodID 去找到对应的信息也是耗时操作,如果方法被频繁调用(特别是像音视频处理时循环的调用JNI方法传递音视频数据),每次都去查找对应的类和方法ID会很耗性能,所以我们必须将它们缓存起来,达到只创建一次,后面直接使用缓存内容的效果。
155 0
|
缓存 Java Android开发
Android使用LruCache、DiskLruCache实现图片缓存+图片瀑布流
**本文仅用于学习利用LruCache、DiskLruCache图片缓存策略、实现瀑布流和Matix查看大图缩放移动等功能,如果想用到项目中,建议用更成熟的框架,如[glide]
171 0
|
存储 缓存 Java
Android使用磁盘缓存DiskLruCache
不同于LruCache,LruCache是将数据缓存到内存中去,而DiskLruCache是外部缓存,例如可以将网络下载的图片永久的缓存到手机外部存储中去,并可以将缓存数据取出来使用,DiskLruCache不是google官方所写,但是得到了官方推荐
187 0

热门文章

最新文章