android之LruCache源码解析

移动设备开发中,由于移动设备(手机等)的内存有限,所以使用有效的缓存技术是必要的.android提供来一个缓存工具类LruCache,开发中我们会经常用到,下面来他是如何实现的.

在package android.util包里面有对LruCache定义的java文件.为了能准确的理解LruCache,我们先来看看原文的说明:

 * A cache that holds strong references to a limited number of values. Each time
 * a value is accessed, it is moved to the head of a queue. When a value is
 * added to a full cache, the value at the end of that queue is evicted and may
 * become eligible for garbage collection.

简单翻译:LruCache缓存数据是采用持有数据的强引用来保存一定数量的数据的.每次用到(获取)一个数据时,这个数据就会被移动(一个保存数据的)队列的头部,当往这个缓存里面加入一个新的数据时,如果这个缓存已经满了,就会自动删除这个缓存队列里面最后一个数据,这样一来使得这个删除的数据没有强引用而能够被gc回收.

从上面的翻译,可以知道LruCache的工作原理.下面来一步一步说明他的具体实现:

(1)如何实现保存的数据是有一定顺序的,并且使用过一个存在的数据,这个数据就会被移动到数据队列的头部.这里采用的是LinkedHashMap.

我们知道LinkedHashMap是保存一个键值对数据的,并且可以维护这些数据相应的顺序的.一般可以保证存储的数据按照存入的顺序或者使用的顺序的.下面来看看LruCache的构造方法:

    public LruCache(int maxSize) {//指定缓存数据的数量
        if (maxSize <= 0) {//必须大于0
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);//创建一个LinkedHashMap,并且按照访问数据的顺序排序
    }

(2)如上面的构造方法可以知道,在创建一个LruCache就需要指定其缓存数据的数量.这里要详细解释一下这个缓存数据的"数量"到底是指什么:是指缓存数据对象的个数呢,还是缓存数据所占用的内存总量呢?

答案是:都是. 可以是缓存数据的个数,也可以使缓存数据所占用内存总量,当然也可以是其他.到底是什么,需要看你的LruCache如何重写这个方法:sizeOf(K key, V value)

    protected int sizeOf(K key, V value) {//子类覆盖这个方法来计算出自己的缓存对于每一个保存的数据所占用的量
        return 1;//默认返回1,这说明:默认情况下缓存的数量就是指缓存数据的总个数(每一个数据都是1).
    }

那如果我使用LruCache来保存bitmap的图片,并且希望缓存的容量是4M那这么做?在原文的说明中,android给来这样一个实例:

 * <p>By default, the cache size is measured in the number of entries. Override
 * {@link #sizeOf} to size the cache in different units. For example, this cache
 * is limited to 4MiB of bitmaps:
 * <pre>   {@code
 *   int cacheSize = 4 * 1024 * 1024; // 4MiB
 *   LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {//保存bitmap的LruCache,容量是4M
 *       protected int sizeOf(String key, Bitmap value) {
 *           return value.getByteCount();//计算每一个缓存的图片所占用内存大小
 *       }
 *   }}</pre>

(3)那么LruCache如何,何时判断是否缓存已经满来,并且需要移除不常用的数据呢?

其实在LruCache里面有一个方法:trimToSize()就是用来检测一次当前是否已经满,如果满来就自动移除一个数据,一直到不满为止:

    public void trimToSize(int maxSize) {//默认情况下传入是上面说的最大容量的值 this.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);//用来通知这个数据已经被移除,如果你需要知道一个数据何时被移除你需要从写这个方法entryRemoved
        }
    }

上面的源码中我给出了说明,很好理解.这里要注意的是trimToSize这个方法是public的,说明其实我们自己可以调用这个方法的.这一点很重要.记住他,你会用到的.

下面的问题是:trimToSize这个方法何时调用呢?

trimToSize这个方法在LruCache里面多个方法里面会被调用来检测是否已经满了,比如在往LruCache里面加入一个新的数据的方法put里面,还有在通过get(K key)这个方法获取一个数据的时候等,都会调用trimToSize来检测一次.下了来看看put里面如何调用:

    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;
    }

(4)细心的人在看了上面的源码可以发现,原来对 LruCache的操作都加了synchronized来保证线程安全,是的,LruCache就是线程安全的,其他的方法也都使用来synchronized

(5)其实你应该马上有一个疑问:如果LruCache中已经删除了一个数据,可是现在又调用LruCache的get方法获取这个数据怎么办?来看看源码是否有解决这个问题:

    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;
        }
    }

从上面的分析可以知道,我们可以从写create方法来重新创建已经不存在的数据.这个方法默认情况是什么也不做的,所以需要你自己做

    protected V create(K key) {
        return null;
    }

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-10 20:05:41

android之LruCache源码解析的相关文章

Android MIFARE NFCA源码解析

Android MIFARE NFCA源码解析TagTechnology定义了所有标签的共有接口类BasicTagTechnology 实现了TagTechnology的一些接口 再有具体的标签协议继承BasicTagTechnologyNFC-A 遵循ISO 14443-3A协议. 关键字ATQA Answer To Request acc. to ISO/IEC 14443-4ATS Answer To Select acc. to ISO/IEC 14443-4DIF Dual Inter

Android 开源项目源码解析(第二期)

Android 开源项目源码解析(第二期) 阅读目录 android-Ultra-Pull-To-Refresh 源码解析 DynamicLoadApk 源码解析 NineOldAnimations 源码解析 SlidingMenu 源码解析 Cling 源码解析 BaseAdapterHelper 源码分析 Side Menu.Android 源码解析 DiscreteSeekBar 源码解析 CalendarListView 源码解析 PagerSlidingTabStrip 源码解析 公共

Android的ViewDragHelper源码解析

其实我想看的是DrawerLayout, 但发现DrawerLayout里面是使用了ViewDragHelper去实现. 谷歌比较早就放出这个类了,但ViewDragHelper是开发中很少用到一个类.顾名思义这是一个和拖曳触摸有关的类. 本着追根溯源的想法, 加上ViewDragHelper的源码也不算多,就决定将ViewDragHelper的源码看一遍.对实现原理了解下. 代码一千多行,看完还是需要点时间的. 因此不会逐一讲完, 当然下面也会放出该类源码的解析,注释中也有一些个人理解的点写在

【Android】EventBus 源码解析

EventBus 源码解析 本文为 Android 开源项目实现原理解析 中 EventBus 部分项目地址:EventBus,分析的版本:ccc2771,Demo 地址:EventBus Demo分析者:Trinea,校对者:扔物线,校对状态:未完成 1. 功能介绍 1.1 EventBus EventBus 是一个 Android 事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递,这里的事件可以理解为消息,本文中统一称为事件.事件传递既可用于 Android 四大组件

Android开发——Volley源码解析

0. 前言   其实写这篇文章只为一个目的,虽然Volley用起来很爽,但是面试官问你人家内部是怎么实现呢,没看过源码的话,在面试官眼里你和一个拿着一本Volley使用手册的高中生没啥区别.还是那句话说得好,会用一回事,深入理解又是另一回事了. 1.  Volley源码解析 1.1  Volley入口 Volley首先获取到的是RequestQueue实例.源码中则直接调用了newRequestQueue方法. public static RequestQueue newRequestQueue

Android常用库源码解析

图片加载框架比较 共同优点 都对多级缓存.线程池.缓存算法做了处理 自适应程度高,根据系统性能初始化缓存配置.系统信息变更后动态调整策略.比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等. 支持多种数据源支持多种数据源,网络.本地.资源.Assets 等 不同点 Picasso所能实现的功能,Glide都能做,无非是所需的设置不同.但是Picasso体积比起Glide小太多. Glide 不仅是一个图片缓存,它支持 Gif.WebP.缩略图.Gl

LruCache源码解析

前言 最近项目要用到Picasso,所以就看了一下Picasso里面的源码,发现里面的内存缓存主要用的LruCache这个类,就去看了一下它的相关的东西,还是挺有收获的. 正文 我一般看类源码喜欢以构造方法作为突破口,然后从它暴露出来的我们使用的最多的那些方法切入,一点一点的把它捋清除,这次基本上也是这个思路. 构造方法 /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maxi

Android 属性动画 源码解析 深入了解其内部实现

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42056859,本文出自:[张鸿洋的博客] 1.概述 Android中想做很炫酷的动画效果,相信在很多时候你都可以选择使用属性动画,关于属性动画如何使用,我们已经很详细的写过两篇博客讲解.如果你还不了解,请参考: Android 属性动画(Property Animation) 完全解析 (上) Android 属性动画(Property Animation) 完全解析 (下)

Android LayoutInflater&amp;LayoutInflaterCompat源码解析

本文分析版本: Android API 23,v4基于 23.2.1 1 简介 实例化布局的XML文件成相应的View对象.它不能被直接使用,应该使用getLayoutInflater()或getSystemService(Class)来获取已关联了当前Context并为你正在运行的设备正确配置的标准LayoutInflater实例对象. 例如: LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.