Android下的缓存策略

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

posted @ 2017-05-02 09:58  喳喳的夏天  阅读(475)  评论(0编辑  收藏  举报