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

Android下的缓存策略的更多相关文章

  1. LruCache的缓存策略

    一.Android中的缓存策略 一般来说,缓存策略主要包含缓存的添加.获取和删除这三类操作.如何添加和获取缓存这个比较好理解,那么为什么还要删除缓存呢?这是因为不管是内存缓存还是硬盘缓存,它们的缓存大 ...

  2. Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/26810303),请尊重他人的辛勤劳动成果,谢谢! 本篇文章 ...

  3. Android 开源框架Universal-Image-Loader全然解析(二)--- 图片缓存策略具体解释

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/26810303),请尊重他人的辛勤劳动成果,谢谢! 本篇文章 ...

  4. 腾讯QQ你的缓存策略应该改下了

    缓存策略基本原则大家都怎么考虑的? 缓存好友数量这个也是醉了,这个数字好像变化频率有点低吧,ok,就算你企鹅用户量大,需要缓存,那肉肉的问一句你这更新策略也不能只管网上涨的,不管往下降的吧?难不成你是 ...

  5. Android Volley框架的使用(四)图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

    在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量.对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理 ...

  6. Android中图片的三级缓存策略

    在开发过程中,经常会碰到进行请求大量的网络图片的样例.假设处理的不好.非常easy造成oom.对于避免oom的方法,无非就是进行图片的压缩.及时的回收不用的图片.这些看似简单可是处理起来事实上涉及的知 ...

  7. 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...

  8. Android的图片缓存ImageCache(转)

    为什么要做缓存?       在UI界面加载一张图片时很简单,然而如果需要加载多张较大的图像,事情就会变得更加复杂.在许多情况下(如ListView.GridView或ViewPager等的组件),屏 ...

  9. 安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

    在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量.对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理 ...

随机推荐

  1. 「雕爷学编程」Arduino动手做(12)——霍尔磁力模块

    37款传感器和模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器与模块,依照实践出真知(动手试试)的理念,以学习和交流为目的,这里准备 ...

  2. vue中mixins的使用方法和注意点(详2)(异步请求的情况)

    当混合里面包含异步请求函数,而我们又需要在组件中使用异步请求函数的返回值时,我们会取不到此返回值,如下: mixin中 组件中 控制台 解决方案:不要返回结果而是直接返回异步函数 mixin中 组件中 ...

  3. JSP中System.out.println()与out.println()区别

    1.out.println()输出到客户端. 在out.println()中,out是response的实例,是以response为对象进行流输出的,即将内容输出到客户端. 如果在JSP页面中使用Sy ...

  4. Oracle 大数据查询优化方法

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  5. HttpServletResponse和HttpServletRequest的简单实用

    1.HttpServletResponse web服务器接收到客户端的http请求,针对这个请求,分别创建一一个代表请求的HttpServletRequest 对象,代表响应的- -个HttpServ ...

  6. SpringBoot2.x【一】从零开始环境搭建

    SpringBoot2.x[一]从零开始环境搭建 对于之前的Spring框架的使用,各种配置文件XML.properties一旦出错之后错误难寻,这也是为什么SpringBoot被推上主流的原因,Sp ...

  7. Golang源码学习:调度逻辑(一)初始化

    本文所使用的Golang为1.14,dlv为1.4.0. 源代码 package main import "fmt" func main() { fmt.Println(" ...

  8. 封装 private

    封装表现: 1.方法就是一个最基本封装体. 2.类其实也是一个封装体. 从以上两点得出结论,封装的好处: 1.提高了代码的复用性. 2.隐藏了实现细节,还要对外提供可以访问的方式.便于调用者的使用.这 ...

  9. PHP文件上传案例和函数

    $_FILES参数详解: $_FILES["file"]["name"] – 被上传文件的名称 $_FILES["file"][" ...

  10. php实用正则

    1 Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ 2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z ...