• 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

  • 相关阅读:
    解决:UIBarButtonItem size issue.尺寸
    flutter坑:Android license status unknown and also Android sdkmanager tool not found
    Ream--(objc)写事务精简方案
    SDMask(iOS蒙层遮罩弹出引导)
    HIFIMAN TWS600A听感小记——测评出街Man
    imageRectForContentRect,titleRectForContentRect,contentRectForBounds,imageRectForContentRect什么时候调用
    super performSelector: 解决调用父类私有方法的问题
    解决:target overrides the `GCC_PREPROCESSOR_DEFINITIONS`
    swift为什么不是do while?
    记录/objc2/object_setClass做了啥
  • 原文地址:https://www.cnblogs.com/coderwjq/p/6794775.html
Copyright © 2020-2023  润新知