研究Android LruCache源码发现的bug(其实不是bug,是对的)

一开始研究Android LruCache源码的时候,我是在Android studio中直接看源码的,当时的配置compileSdkVersion 28,也就是9.0版本的,看到驱逐逻辑(trimToSize)的时候,发现一个问题,具体看看代码

    /**
     * @param maxSize the maximum size of the cache before returning. May be -1
     *     to evict even 0-sized elements.
     */
    private 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;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                Map.Entry<K, V> toEvict = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

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

trimToSize本身的逻辑很简单,就是在一个死循环中,如果已缓存大小size大于设置的最大值maxSize,就把最近最少使用的元素删除掉,直到size小于等于maxSize就退出循环,trimToSize执行结束。

问题的“最近最少使用元素”是怎么获取到的,上面的代码是遍历LinkedHashMap,获取最后一个元素,认为此元素就是lru,这个是不对的,我们知道最近最少使用元素是放在链表头的,而不是尾部。后来我翻了一下9.0 的Android源码

187    /**
188     * Remove the eldest entries until the total of remaining entries is at or
189     * below the requested size.
190     *
191     * @param maxSize the maximum size of the cache before returning. May be -1
192     *            to evict even 0-sized elements.
193     */
194    public void trimToSize(int maxSize) {
195        while (true) {
196            K key;
197            V value;
198            synchronized (this) {
199                if (size < 0 || (map.isEmpty() && size != 0)) {
200                    throw new IllegalStateException(getClass().getName()
201                            + ".sizeOf() is reporting inconsistent results!");
202                }
203
204                if (size <= maxSize) {
205                    break;
206                }
207
208                Map.Entry<K, V> toEvict = map.eldest();
209                if (toEvict == null) {
210                    break;
211                }
212
213                key = toEvict.getKey();
214                value = toEvict.getValue();
215                map.remove(key);
216                size -= safeSizeOf(key, value);
217                evictionCount++;
218            }
219
220            entryRemoved(true, key, value, null);
221        }
222    }

是通过map.eldest()获取最近最少使用元素的,看看eldest源码

491    // Android-added: eldest(), for internal use in LRU caches
492    /**
493     * Returns the eldest entry in the map, or {@code null} if the map is empty.
494     * @hide
495     */
496    public Map.Entry<K, V> eldest() {
497        return head;
498    }

eldest就是直接获取链表头,说明是正确的。

所以,我分析,本身9.0的源码是正确的,只是Android studio中28的辅助编译库android.jar中的源码有问题。

我们看看23(6.0)是什么情况,我们把compileSdkVersion 改成23,分别看看android.jar和6.0中LruCache的源码

我们发现android.jar和6.0中LruCache的trimToCache源码是一样的,都是采用的map.eldest()。我们看看6.0 LinkedHashMap eldest源码

    /**
     * Returns the eldest entry in the map, or {@code null} if the map is empty.
     * @hide
     */
    public Entry<K, V> eldest() {
        LinkedEntry<K, V> eldest = header.nxt;
        return eldest != header ? eldest : null;
    }

发现6.0和9.0中LinkedHashMap源码居然不一样,虽然实现有些出入,但是功能是一样的。

 

网上也有人发现了LruCache这一个奇怪现象(Android LruCache 的 Bug),不过不是他说的5.0之前不对,5.0之后就对了,人家Android本身源码没问题,只是提供编译的android.jar中有些问题,不会在项目中导致bug的。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值