Android 缓存策略

    对于一个应用来说,是否被用户接受、喜爱,除了其内容以外,对于流量的消耗也是一个重要原因。比如当一个页面需要展现很多图片,如果图片没有替换或者更新,当用户多次点击进入该界面,难道要一次一次从网络上加载图片吗?那样便会消耗很多流量,对于用户来说必定是一个糟糕的体验。这时候就需要缓存策略了:

    当程序第一次运行,当程序从网络上加载图片后,便将它缓存到储存设备上,往往也会把图片缓存到内存中,当下次需要使用这些图片时,就先从内存中去获取,如果没有就从储存设备中去获取,如果还是没有才会从网络上加载。

    缓存策略一般包括三个部分:缓存的添加、获取、删除。因为内存缓存和设备缓存的大小都是有限制的,当缓存满了的时候需要加入新的缓存就需要删除一些缓存了。怎么选择删除的缓存就需要使用缓存算法。

    缓存算法中有一种比较常见的就是 LRU,即 近期最少使用算法,它会优先删除最近最少使用的缓存来为新的缓存提供空间。其中 LruCache 就是基于 LRU 实现内存缓存的,DiskLruCache 就是基于 LRU 实现存储设备缓存。

  •     LruCache

    LruCache 是一个泛型类,内部使用了一个 LinkedHashMap 以强引用的方式存储缓存,通过 get() 和 put() 来进行获取和添加缓存。如下是 LruCache 的构造方法:


    构造方法会要求传入一个最大值,一般是指你当前进程能够使用的最大内存,可以按照自己要求进行更改,它会先判断能够使用的内存是否大于0,如果不大于就会抛出一个一个 IllegalArgumentException 告诉你没有可用内存了。

  一般来说如果要使用 LruCache 来进行缓存,就要重写它的 sizeOf() 方法,来计算缓存对象的大小。以图片为例:

int maxMemory = (int) ((Runtime.getRuntime().maxMemory())/1024);
    //取可用内存的1/8
    int cacheSize = maxMemory/8;
    LruCache<String,Bitmap> cache = new LruCache<String, Bitmap>(cacheSize){
        @Override
        protected int sizeOf(String key, Bitmap value) {
            return value.getRowBytes()*value.getHeight()/1024;
        }
    };

    上述代码中,先是使用 Runtime.getRuntime().getmaxMemory() 来获取当前进程可以使用的最大内存,单位是字节。然后重写 sizeOf() 方法返回当前图片的缓存需要的大小。

    下面是 LruCache 的 pet() 的源码:

扫描二维码关注公众号,回复: 914875 查看本文章


    下面是 LruCache 的 get() 的源码:


    下面是 LruCache 的 remove() 的源码:


    它的实现和使用相对来说都比较简单,都是基于 LinkedHashMap 的增、删、查。

  • DiskLruCache

    DiskLruCache 用于实现设备缓存,它将缓存写入文件系统实现缓存。和 Lrucache 还有一点不同就是 LruCache 从 Android 3.1 过后已经属于 Android 源码的一部分,但是 DiskLruCache 却是 JakeWharton 大神的作品。先附上 DiskLruCache 项目的 GitHub 地址:地址传送门

    在使用 DiskLruCache 之前我们需要先导入它的 jar 包,可以导入项目:

compile 'com.jakewharton:disklrucache:2.0.2'

  • DiskLruCache 的创建和缓存添加

    DiskLruCache 的构造方法是私有的,所以不能通过构造方法来创建:


    而是使用它的 open() 方法来创建:


    该方法有四个参数:第一个表示缓存路径、第二个表示版本号,一般为设为1,当版本号发生变化 DiskLruCache 会清空之前的缓存、第三个表示单个节点对应的数据个数,一般也设为1、第四个表示缓存的总大小,当缓存大小超过该值时,DiskLruCache 就会清除一些缓存。下面是是创建 DiskLruCache 的简单方法:

private Context context;
    //定义缓存总大小,如 10M
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 10;
    private DiskLruCache cache;
    private void creatDiskLruCache() throws IOException {
        //定义缓存目录
        File file = getDiskCacheDir(context,"bitmap");
        //如果目录不存在则创建此抽象路径指定目录
        if (!file.exists()){
            file.mkdirs();
        }
        cache = DiskLruCache.open(file,1,1,DISK_CACHE_SIZE);
    }
    //获取应用在 SD 卡上的缓存路径,该目录会随着应用删除而删除,也可以自定义缓存路径
    public static 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);
    }
    DiskLruCache 的 缓存添加是通过 Editor 来完成的,表示对一个缓存对象的编辑对象。首先获取图片的 rul 所对应的 key,根据 key 来通过 edit() 来获取 Editor 对象。DiskLruCache 重载了 edit() 方法,一般是调用第一个,不过底层其实第一个也是调用了第二个。



    edit() 方法使用了 synchronized,不允许同时编辑同一个缓存对象,否则就返回 null。还有因为图片的 url 可能包含特殊字符,影响在 Android 中使用,所以一般采用 url 的 md5 值作为 key。有了 key 之后我们就可以获取 Editor 对象,如果不存在就会返回一个新的 Editor 对象,通过 newOutputStream() 方法来得到一个文件输出流。newOutputStream() 方法需要传入一个参数,表示这个节点的第几个数据,因为一般只储存一个数据,所以可以直接设为0:

private void creatDiskLruCache() throws IOException {
        ...
        cache = DiskLruCache.open(file,1,1,DISK_CACHE_SIZE);
        DiskLruCache.Editor editor = cache.edit(key);
        if (editor != null){
            OutputStream stream = editor.newOutputStream(0);
        }
    }

    有了输出流,我们需要将它写入文件系统:

public boolean downloadUrlToStream(String urlString,OutputStream stream) throws IOException {
        HttpURLConnection connection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            URL url = new URL(urlString);
            //获取图片url连接
            connection = (HttpURLConnection) url.openConnection();
            //获取图片的输入流
            in = new BufferedInputStream(connection.getInputStream());
            out = new BufferedOutputStream(out);
            int b;
            //将输入流写入输出流
            while ((b=in.read())!=-1){
                out.write(b);
            }
            return true;
        } catch (MalformedURLException e) {

        } finally {
            //下载完成或者失败都要关闭,避免资源浪费
            if (connection!=null){
                connection.disconnect();
            }
            out.close();
            in.close();
        }
        return false;
    }

  我们还需要调用 Editor 的 commit() 方法来提交写入操作才算完成。如果图片下载发生异常,我们也可以使用 Editor 的 abort() 方法来回退整个操作。

private void creatDiskLruCache() throws IOException {
        ...
        OutputStream stream = editor.newOutputStream(0);
        if (downloadUrlToStream(url,stream)){
            editor.commit();
        }else {
            editor.abort();
        }
        cache.flush();
    }

  • DiskLruCache 的缓存查找

    DiskLruCache 也是通过 key 来查找,得到一个 Snapshot 对象,然后获取输入流。

private void getDiskLruCache() throws IOException {
        DiskLruCache.Snapshot snapshot = cache.get(key);
        if (snapshot != null){
            //这里传入的参数也是节点的第几个数据,所以也直接设置为0
            FileInputStream in = (FileInputStream) snapshot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(in);
        }
    }

    但是大量加载图片可能会导致 OOM,这里就需要了解一下图片的高效加载,大家可以看一下我上一篇博客:Android 图片的高效加载

    但是这里有一个问题,因为 FileInputStream 是一个有序输入流,而高效加载的两次 decode() 方法会影响文件流的位置属性,导致第二次 decode() 方法得到的是 null,我们可以使用 BitmapFactory 的 decodeFileDescriptor() 方法:

private void getDiskLruCache() throws IOException {
        DiskLruCache.Snapshot snapshot = cache.get(key);
        if (snapshot != null){
            //这里传入的参数也是节点的第几个数据,所以也直接设置为0
            FileInputStream in = (FileInputStream) snapshot.getInputStream(0);
            FileDescriptor descriptor = in.getFD();
            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(descriptor);
        }
    }

 然后就可以使用进行高效加载。

学习自《Android 艺术开发探索》

    




猜你喜欢

转载自blog.csdn.net/young_time/article/details/80299919
今日推荐