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. 【Jenkins学习】【第二节】 jenkins构建触发器定时任务

    一.定时构建 Build periodically:定时执行构建任务,不管远程代码分支上的代码是否发生变化,都执行一次构建. 语法:* * * * *(五颗星,中间用空格隔开) 第一个:分钟,取值0~ ...

  2. DRF视图组件

    DRF视图组件: CVB模式继承----五层 from django.views import View # Django的View from rest_framework.views import ...

  3. 如何覆盖elementUI样式

    question: 在某个组件里面更改element-Ui的样式,而不影响全局. solution: 在需要更改的组件里新增一个style标签[重点:不要加scoped],然后直接获取class设置样 ...

  4. 30分钟快速上手Docker,看这篇就对了!

    一.历史演化 1.演化史 2.物理机时代 2.1.图解 一个物理机上安装操作系统,然后直接运行我们的软件.也就是说你电脑上直接跑了一个软件,并没有开虚拟机什么的,资源极其浪费. 2.2.缺点 部署慢 ...

  5. AVL树的创建--C语言实现

    AVL树是一种自平衡(Self-balancing)二叉查找树(Binary Search Tree),要求任何一个节点的左子树和右子树的高度之差不能超过1. AVL树的插入操作首先会按照普通二叉查找 ...

  6. 三、$JavaScript(1)

    1.闭包 闭包就是能够读取其他函数内部变量的函数 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以 ...

  7. indetityserver4-implicit-grant-types-请求流程叙述-下篇

    上一篇将请求流程描述一遍,这篇将描述一下相关的源码. 1 访问客户端受保护的资源 GET /Home/Secure HTTP/1.1HTTP/1.1 302 Found Date: Tue, 23 O ...

  8. [jQuery插件]手写一个图片懒加载实现

    教你做图片懒加载插件 那一年 那一年,我还年轻 刚接手一个ASP.NET MVC 的 web 项目, (C#/jQuery/Bootstrap) 并没有做 web 的经验,没有预留学习时间, (作为项 ...

  9. sql注入讲解

    1.输入1' 发现数据库报错,原因是我们的输入直接被代入到数据库查询语句里面. 2.有没有办法可以不让他报错呢?可以尝试一下构造正确的数据库语法,使之不报错.比如输入 1 and 1=1 试试 sel ...

  10. gopher 协议初探

    Gopher 协议初探 最近两天看到了字节脉搏实验室公众号上有一篇<Gopher协议与redis未授权访问>的文章,其中对gopher协议进行了比较详细的介绍,所以打算跟着后面复现学习一下 ...