Android下的缓存策略

内存缓存

常用的内存缓存是软引用和弱引用,大部分的使用方式是Android提供的LRUCache缓存策略,本质是个LinkedHashMap(会根据使用次数进行排序)

磁盘缓存

DiskLruCache:非谷歌官方编写,但是获得官方认证

  • 不限制数据缓存的位置,可自由的设置,通常情况下会选择:/sdcard/Android/data/{packageName}/cache这个路径
选择这个位置的好处
  1. 这是存储在sdcard上的,只要sdcard空间足够,不会对手机内置存储有任何影响
  2. 该路径被Android系统认定为应用程序的缓存路径,当app被卸载时,这里的数据会被一起清除掉,而不会出现删除app后,还残留数据的问题

getCacheDir()是获取app在手机内部存储的cache目录

getFilesDir()是获取app在手机内部存储的files目录

通过Context.getExternalFilesDir()可以获取到app在sdcard上的files目录,通常用于存放要长时间保存的数据

通过Context.getExternalCacheDir()可以获取到app在sdcard上的cache目录,通常用于存放一些临时数据

使用上面两个api,在app被卸载的时候,在sdcard上对应的所有文件也会自动被删除,不会留下垃圾信息

而且上面两个目录在设置里的应用详情里,可以使用清除数据和清除缓存来清理临时文件

LruCache——内存缓存策略

LRU(Least Recently Used)缓存算法,近期最少使用的算法

LruCache是Android 3.1以后提供的一个缓存类

LruCache的介绍

  • LruCache是 一个泛型类,主要原理是把最近使用的对象用强引用的方式存储在LinkedHashMap中,把最近最少使用的对象从内存中移除,并提供get和put方法来完成缓存的获取和添加操作

LruCache的缓存大小一般为当前进程可用容量的1/8

重写sizeOf方法,计算每个缓存对象的大小

注意:缓存的总容量和每个缓存对象的大小所用的单位要一致

LruCahe的实现原理

  • 维护一个缓存对象列表,按照访问顺序进行排序
  • 一直没有访问的对象放在队尾,即将被淘汰
  • 最近访问的对象放在队首,最后被淘汰
  • 这个队列由LinkedHashMap来维护

LinkedHashMap

  • 是由数组+双向链表的数据结构来实现
  • 双向链表的结构可以实现访问顺序和插入顺序,使得队列中的对象按照一定的顺序排列起来

在构造函数中,可以使用accessOrder参数来控制双向链表的结构是访问顺序还是插入顺序

其中accessOrder设置为true则为访问顺序,为false,则为插入顺序。

设置LinkedHashMap的accessOrder为true,并向里面插入数据后随机访问数据,将访问数据后的LinkedHashMap输出,最近访问数据的最后输出

LruCache的源码分析

LruCache内部使用LinkedHashMap的访问顺序特性,来缓存数据

当调用put()方法时,就会在集合中添加元素,并调用trimToSize()来判断缓存是否已满,如果满了就删除队尾元素

当调用get()方法时,就会调用LinkedHashMap的get()方法获得对应的元素,同时会更新该元素到队首

DiskLruCache——磁盘缓存策略

DiskLruCache目前还不是Android SDK的一部分,但是Android官方文档推荐使用该算法来实现磁盘缓存

DiskLruCache的使用方法

打开

DiskLruCache不能new出实例,需要调用它的open()方法

open()方法接收四个参数:

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  • directory:数据的缓存地址
  • appVersion:当前应用程序的版本号
  • valueCount:同一个key可以对应多少个缓存文件,基本都是传1
  • maxSize:最多可以缓存多少字节的数据
  • 其中缓存地址前面已经说过了,通常都会存放在 /sdcard/Android/data//cache 这个路径下面,但同时我们又需要考虑如果这个手机没有SD卡,或者SD正好被移除了的情况,因此比较优秀的程序都会专门写一个方法来获取缓存地址
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);
}
  • 可以看到,当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。前者获取到的就是 /sdcard/Android/data//cache 这个路径,而后者获取到的是 /data/data//cache 这个路径。

  • 接着是应用程序版本号,我们可以使用如下代码简单地获取到当前应用程序的版本号:

public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
  • 需要注意的是,每当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
插入缓存

插入使用DiskLruCache.Editor这个类来完成,同样,它也不能new出实例,需要使用edit()方法

public Editor edit(String key) throws IOException
  • 可以看到,edit()方法接收一个参数key,这个key将会成为缓存文件的文件名,并且必须要和图片的URL是一一对应的。那么怎样才能让key和图片的URL能够一一对应呢?直接使用URL来作为key?不太合适,因为图片URL中可能包含一些特殊字符,这些字符有可能在命名文件时是不合法的。其实最简单的做法就是将图片的URL进行MD5编码,编码后的字符串肯定是唯一的,并且只会包含0-F这样的字符,完全符合文件的命名规则。
读取缓存
// get()方法要求传入一个key来获取到相应的缓存数据,而这个key毫无疑问就是将图片URL进行MD5编码后的值了
public synchronized Snapshot get(String key) throws IOException
  • 通过get()方法,会获取到一个DiskLruCache.Snapshot对象,通过调用它的getInputStream()方法就可以得到缓存文件的输入流
移除缓存
public synchronized boolean remove(String key) throws IOException
  • 这个方法我们并不应该经常去调用它。因为你完全不需要担心缓存的数据过多从而占用SD卡太多空间的问题,DiskLruCache会根据我们在调用open()方法时设定的缓存最大值来自动删除多余的缓存。只有你确定某个key对应的缓存内容已经过期,需要从网络获取最新数据的时候才应该调用remove()方法来移除缓存。
其他api
  • size():返回当前缓存路径下所有缓存数据的总字节数数,用于在app上显示当前缓存的总大小
  • flush():用于将内存中的操作同步到日志文件中(也就是journal文件),因此DiskLruCache能够正常工作的前提是要依赖于journal文件中的内容。频繁的调用会增加同步journal文件的时间,比较标准的做法就是爱Activity的onPause()方法中调用一次即可
  • close():用于将DiskLruCache关闭,和open()方法相对应。关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法,通常只应该在Activity的onDestroy()方法中去调用close()方法。
  • delete():这个方法用于将所有的缓存数据全部删除,用于让用户清除缓存

参考文档:

http://blog.csdn.net/guolin_blog/article/details/28863651

http://blog.csdn.net/guolin_blog/article/details/34093441

最新文章

  1. 【WP开发】不同客户端之间传输加密数据
  2. RedHat下apache\ftp\mysql 4.0 的安装方法
  3. 关于拉格朗日乘子法和KKT条件
  4. Eclipse开发Android程序如何在手机上运行
  5. 状态压缩 UVALive 6068 The Little Girl who Picks Mushrooms (12长春C)
  6. Image.FromFile 方法锁住文件解决方法
  7. homework-1
  8. 《ruby编程语言》笔记2 对象
  9. 【IPC进程间通信之四】数据复制消息WM_COPYDATA
  10. 微信小程序-未接入app.json错误
  11. Effective Modern C++ Item 37:确保std::thread在销毁时是unjoinable的
  12. java-随机生成用户名(中文版及英文版)
  13. ietester下ie6.0停止工作问题的修复
  14. java----鲁棒性
  15. ContentProvider插件化解决方案
  16. centos找不到环境变量 -bash: ls: command not found
  17. Hibernate or 的用法
  18. ES6 let和const 的相同点与区别
  19. python多线程、多进程相关知识
  20. Jmeter(七)Jmeter脚本优化(数据与脚本分离)

热门文章

  1. Spring MVC 函数式编程进阶
  2. 笨办法学习python之hashmap
  3. 最近关于pc 组装总结
  4. Django之ORM中事务和锁
  5. EF Join连接查询的坑
  6. fastDFS多线程并发执行出现的问题
  7. B. Sleepy Game 博弈搜索
  8. vue实现对文章列表的点赞
  9. SpringMVC笔记总结
  10. Istio ServiceEntry 引入外部服务