文档章节

【安卓中的缓存策略系列】安卓缓存之内存缓存LruCache

htq
 htq
发布于 2016/07/26 09:38
字数 1970
阅读 3
收藏 0
点赞 0
评论 0

缓存策略在移动端设备上是非常重要的,尤其是在图片加载这个场景下,因为图片相对而言比较大会花费用户较多的流量,因此可用缓存方式来解决,即当程序第一次从网络上获取图片的时候,就将其缓存到存储设备上,这样在用户下次使用这张图片时就不用从网络上再次获取,这样就能为用户节省一定的流量。这个功能目前绝大部分主流APP都会使用,如腾讯QQ,微信。但很多时候为了提高APP的用户体验,我们还需要把图片在内存中缓存一份,比如ListView,我们知道LIstView会在用户将某些图片移出屏幕后将其进行回收,此时垃圾回收器会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。可是为了能让程序快速运行,在界面上迅速地加载图片,必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况,而这种情况在ListView,GridView这种控件中出现是非常频繁的。采用内存缓存技术可以很好的解决上述问题,内存缓存技术允许控件可以快速地重新加载那些处理过的图片。内存缓存技术主要是通过LruCache这个类来完成的,下面从源码的角度详细讲解LruCache这个类,然后在此基础上讲解如何使用LruCache,让读者知其然更知其所以然。


一LruCache类:

首先我们来看一下类的定义及其构造函数:

public class LruCache<K, V>

 public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
可以看到LruCache类使用泛型参数,其构造器参数为int型,该参数用来指定LruCache的大小,另外从其构造函数的实现过程来看,可以知道LruCache的底层是使用LinkedHashMap<K, V>来实现的,即LruCache使用一个强引用(strong referenced)的LinkedHashMap保存最近引用的对象。(A cache that holds strong references to a limited number of values.)

然后我么来看一下其重要的方法:

public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }



 public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
 /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.
         */


        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }


        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);


            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }


        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

 protected int sizeOf(K key, V value) {
        return 1;
    }

之所以把这三个方法拿出来讲解,是因为这三个方法它对应着我们实现的LruCache内存缓存策略的创建缓存,添加key到缓存,从缓存中获取V这三个最重要的功能,通常我们在创建缓存时会重写上述的sizeOf(K key, V value),在该方法中返回我们要创建的内存缓存的大小。而put与get则相对复杂些,我们首先来分析一下put方法。

可以看到put方法作用是缓存key,同时会将key移动到队列头部Caches {@code value} for {@code key}. The value is moved to the head of the queue.,另外它会返回与的key对应的先前的V,(return the previous value mapped by {@code key})。采用的是同步方式来实现的。

接下来我们看一下get方法。

从get方法中可以看到get包括两种情况:

1取的时候命中:直接返回与key对应的V。

2取的时候未命中:根据key产生一个V,然后调用put方法将其放入。

当get方法会将返回的值移动到队列头部。If a value was returned, it is moved to the  head of the queue。

从put与get方法可以看到,put与get时会将该元素放到队列的头部,因为无论是put还是get都表示该元素目前被使用过,所以会将其放到队列的头部,当缓存中的元素超过maxSize时,会通过trimToSize函数来去除缓存中最久的元素( Map.Entry<K, V> toEvict = map.eldest();),这就是所谓的LRU算法,即最近最少使用算法,即当当缓存中的元素超过maxSize时会淘汰最近最少使用的元素。下面是trimToSize的源码:

public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();//首先获取最久的元素
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//去除最久的元素(也是未被使用的最久的元素)
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

下面是LinkedHashMap中eldest()函数的代码:

public Entry<K, V> eldest() {
        LinkedEntry<K, V> eldest = header.nxt;
        return eldest != header ? eldest : null;
    }

通过上述代码的分析相信看官对LruCache的思想已经非常熟悉了,LruCache的底层是通过LinkedHashMap来实现的,当创建一个LruCache的对象时会让我们传入一个int型的maxSize,当我们向LruCache中put与get元素时会将该元素放到缓存队列的对头,当put元素超过maxSize时(这也是为何要传入maxSize参数的原因),会通过trimToSize函数来去除缓存中最久的元素,具体是通过Map.Entry<K, V> toEvict = map.eldest();来获取最久的元素,然后通过remove(key)的方式将其移除。


二LruCache的使用:

这个类是3.1版本中提供的,如果要在更早的Android版本中使用,则需要导入android-support-v4的jar包。
我们以BitMap对象来创建LruCache缓存为例,首先正如我在前面讲述的,一个LruCache缓存策略至少包含三个功能,即创建缓存,向缓存中添加元素,从缓存中获取元素。

代码如下:

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}
我们说过缓存的目的就是为了当加载某个图片时首先从LruCache 中检查是否存在这个Bitmap。如果确实存在,它会立即被用来显示到ImageView上,如果不存在,则会开启一个后台线程去处理显示该Bitmap任务。所以我们还需要为其添加一个loadBitmap的功能,代码如下:

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}
其中的BitmapWorkerTask 需要把解析好的Bitmap添加到内存缓存中:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}


好了,以上就是本人理解的关于LruCache的知识,看官如果觉得不错,请点击下方的”顶“或“赞”按钮给我一点小小的鼓励哦! 微笑,另外看官还可以看看我的其它博客的文章哦! 微笑



本文转载自:http://blog.csdn.net/htq__/article/details/51375525

共有 人打赏支持
htq

htq

粉丝 19
博文 67
码字总数 1007
作品 3
武汉
Android源码解析——LruCache

我认为在写涉及到数据结构或算法的实现类的源码解析博客时,不应该急于讲它的使用或马上展开对源码的解析,而是要先交待一下这个数据结构或算法的资料,了解它的设计,再从它的设计出发去讲如...

浩码农 ⋅ 2016/05/13 ⋅ 0

Glide高级详解—缓存与解码复用

Glide 使用简明的流式语法API,大多数情况下,可能完成图片的设置你只需要: 在调用into之后,Glide会首先从Active Resources查找当前是否有对应的活跃图片,没有则查找内存缓存,没有则查找...

codeGoogle ⋅ 05/07 ⋅ 0

2018 Android 面试心得,已拿到 offer

code小生,一个专注于 Android 领域的技术分享平台 作者:huisonma 链接:https://www.jianshu.com/p/855ff21e0a13 声明:本文是 huisonma 原创,转发等请联系原作者授权。 从 16 年毕业至今...

h176nhx7 ⋅ 04/16 ⋅ 0

Java编程语言:java中四种引用!欢迎补充

Java内存管理包括内存分配和内存回收。 内存分配:程序员通过new对象,JVM会自动为该对象分配内存。 内存回收:Java在JVM虚拟机上增加了垃圾回收(Garbage Collection)机制,用以在合适的时间...

启示录是真的 ⋅ 05/22 ⋅ 0

Android中图片优化之webp使用

博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 有关图片的优化,通常我们会用到LruCache(使用强引用、强制回收的办法),会用到SoftReference(使用...

liuzxgeek ⋅ 2015/06/18 ⋅ 0

腾讯技术分享:Android版手机QQ的缓存监控与优化实践

本文内容整理自公众号腾讯Bugly,感谢原作者的分享。 1、问题背景 对于Android应用来说,内存向来是比较重要的性能指标。内存占用过高,会影响应用的流畅度,甚至引发OOM,非常影响用户体验。...

JackJiang2011 ⋅ 04/08 ⋅ 0

OpenResty 1.13.6.2,基于 Nginx 与 Lua 的 Web 平台

OpenResty 1.13.6.2 已发布,从这个版本开始,官方将会提供 64 位 Windows 原生二进制包,也为 Ubuntu 18.04 Bionic 提供新的 yum 包。 下一个 OpenResty 版本将基于 nginx core 1.13.12 或即...

淡漠悠然 ⋅ 05/23 ⋅ 9

Android性能优化:手把手教你如何让App更快、更稳、更省(含内存、布局优化等)

前言 在 开发中,性能优化策略十分重要 因为其决定了应用程序的开发质量:可用性、流畅性、稳定性等,是提高用户留存率的关键 本文全面讲解性能优化中的所有知识,献上一份 性能优化的详细攻...

Carson_Ho ⋅ 05/30 ⋅ 0

Android 性能优化:手把手教你优化Bitmap图片资源的使用

前言 在 开发中,性能优化策略十分重要 本文主要讲解性能优化中的Bitmap 使用优化,希望你们会喜欢 目录 1. 优化原因 即 为什么要优化图片资源,具体如下图:

Carson_Ho ⋅ 04/24 ⋅ 0

Design Patterns in Android:责任链模式

前言 非常抱歉,本系列博客长达半年没更新了,今日偶得灵感,更新一波《设计模式Android篇:责任链模式》。点击此处查看《Design Patterns in Android》系列其他文章。 本文原创作者MichaelX。...

MichaelX ⋅ 05/14 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

中标麒麟(龙芯版)7.0优盘安装

########################################## 制作U盘安装盘: 1.准备U盘: PMON环境下U盘必须格式化成ext3; 昆仑固件环境下可以格式化成ext3,ext4 2.把整个镜像 xxx.iso 复制到U盘下面 3....

gugudu ⋅ 16分钟前 ⋅ 0

老司机写的大数据建模五步走

本文将尝试来梳理一下数据建模的步骤,以及每一步需要做的工作。 01 第一步:选择模型或自定义模式 这是建模的第一步,我们需要基于业务问题,来决定可以选择哪些可用的模型。 比如,如果要预...

gulf ⋅ 25分钟前 ⋅ 0

PacificA 一致性协议解读

PacificA 的 paper 在 08 年左右发出来的,比 Raft 早了 6,7 年。 在 PacificA 论文中,他们强调该算法使用范围是 LAN (Local Area Network),讲白了就是对跨机房不友好。 不管是 ZAB,Raf...

黑客画家 ⋅ 27分钟前 ⋅ 0

盘符图标个性化

设置自己的专属盘符图标 准备ico格式的图片文件一个,在根目录下创建autorun.inf文件 文件内容 [Autorun]icon=logo.ico 重新启动或者插拔U盘即可看到结果...

阿豪boy ⋅ 28分钟前 ⋅ 0

Windows下QQ聊天记录中图片的默认存放位置

Windows下QQ聊天记录中图片的默认存放位置在设置中是没有说明的。 实测位置在:D:\Documents\Tencent Files\974101467\Image 其中: “974101467”为对应的QQ号; “C2C”为个人之间的聊天图...

临江仙卜算子 ⋅ 34分钟前 ⋅ 0

GC 的三种基本实现方式

参考资料《代码的未来》(作者: [日] 松本行弘)。 由于并非本人原著(我只是个“搬运工“),SO 未经本人允许请尽情转载。 另外个人像说明一下这里所说的GC指泛指垃圾回收机制,而单指Jav...

xixingzhe ⋅ 35分钟前 ⋅ 0

Android双击退出

/** * 菜单、返回键响应 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub if(keyCode......

王先森oO ⋅ 39分钟前 ⋅ 0

idea 整合 vue 启动

刚学习Vue 搭建了一个项目 只能命令启动 Idea里面不会启动 尝试了一下修改启动的配置 如下: 1.首先你要保证你的package.json没有修改过 具体原因没有看 因为我改了这个name的值 就没办法启动...

事儿爹 ⋅ 45分钟前 ⋅ 0

redis在windows环境的后台运行方法

在后台运行,首先需要安装redis服务,命令为 redis-server.exe --service-install redis.windows.conf --loglevel verbose 启动,命令为 redis-server --service-start 停止,命令为 redis-...

程序羊 ⋅ 48分钟前 ⋅ 0

比特币现金开发者提出新的交易订单规则

本周,四位比特币现金的四位开发者和研究员:Joannes Vermorel(Lokad),AmaurySéchet(比特币ABC),Shammah Chancellor(比特币ABC)和Tomas van der Wansem(Bitcrust)共同发表了一篇关...

lpy411 ⋅ 52分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部