首页 短视频

Android LruCache 源码深度剖析:内存优化与性能调优实战

分类:短视频
字数: (2052)
阅读: (9143)
内容摘要:Android LruCache 源码深度剖析:内存优化与性能调优实战,

在 Android 开发中,处理图片、网络请求结果等需要缓存的数据时,android.util.LruCache 是一个非常实用的工具。它基于 Least Recently Used (LRU) 算法实现,能够有效地管理内存,防止 OOM (Out of Memory) 异常。本文将深入分析 LruCache 的源码,并结合实际场景,探讨如何利用它来提升应用的性能。

LruCache 的基本原理

LruCache 的核心思想是:当缓存容量达到上限时,优先移除最近最少使用的数据。它内部使用 LinkedHashMap 来存储缓存数据,并利用 LinkedHashMap 的特性来实现 LRU 算法。

简单来说,LinkedHashMap 默认情况下按照元素的插入顺序维护元素的顺序,但可以通过构造函数中的 accessOrder 参数设置为 true,使其按照元素的访问顺序维护元素的顺序。当一个元素被访问时(getput 操作),它会被移动到链表的末尾,表示最近被访问过。当缓存满时,链表的头部元素(最久未被访问的元素)会被移除。

Android LruCache 源码深度剖析:内存优化与性能调优实战

LruCache 源码分析

我们来看一下 LruCache 的主要源码实现:

public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;
    private int size;
    private int maxSize;
    private int putCount;
    private int createCount;
    private int evictionCount;
    private int hitCount;
    private int missCount;

    // 构造函数
    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); // accessOrder = true,启用 LRU
    }

    // 获取缓存数据
    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++;
        }

        // 如果缓存未命中,则尝试创建
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

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

            if (mapValue != null) {
                // 如果已经存在,则还原
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            } // 增加当前缓存大小
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize); // 调整缓存大小
            return createdValue;
        }
    }

    // 放入缓存数据
    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;
    }

    // 调整缓存大小
    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 || map.isEmpty()) {
                    break;
                }

                Map.Entry<K, V> toEvict = null;
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                    break; // LinkedHashMap 的迭代器按照插入顺序(或者访问顺序,如果 accessOrder=true)迭代
                }

                if (toEvict == null) {
                    break;
                }

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

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

    // 计算单个缓存对象的大小,默认返回 1
    protected int sizeOf(K key, V value) {
        return 1;
    }

    // 可重写的,当缓存被移除时调用
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

    // 可重写的,当缓存未命中时调用,用于创建缓存对象
    protected V create(K key) {
        return null;
    }

    // 清空缓存
    public final void evictAll() {
        trimToSize(-1); // 设置 maxSize 为 -1,强制移除所有缓存
    }

    // ... 省略其他辅助方法
}

实战避坑经验总结

  1. 合理设置 maxSize: maxSize 的设置至关重要。如果设置过小,会导致缓存频繁被移除,降低缓存命中率;如果设置过大,则可能导致 OOM。需要根据应用的实际情况,合理估算缓存数据的大小,并留有一定的余量。可以使用 sizeOf() 方法来精确计算每个缓存对象的大小。

    Android LruCache 源码深度剖析:内存优化与性能调优实战
  2. 重写 sizeOf() 方法: 默认情况下,sizeOf() 方法返回 1,表示每个缓存对象的大小相同。但实际上,不同对象的占用内存大小可能差异很大。因此,需要根据缓存对象的类型,重写 sizeOf() 方法,返回对象实际占用的内存大小。例如,对于图片缓存,可以返回图片的像素大小(宽度 * 高度 * 像素深度)。

  3. 处理 Bitmap 对象: 当缓存 Bitmap 对象时,需要注意及时回收不再使用的 Bitmap 对象,避免内存泄漏。可以在 entryRemoved() 方法中调用 bitmap.recycle() 方法来回收 Bitmap 对象。

    Android LruCache 源码深度剖析:内存优化与性能调优实战
  4. 线程安全: LruCache 本身是线程安全的,但需要注意在多线程环境下对缓存数据的并发访问。如果多个线程同时访问同一个缓存对象,可能会导致竞争条件。可以使用锁或其他同步机制来保护缓存数据。

  5. 谨慎使用 create() 方法: create() 方法在缓存未命中时被调用,用于创建缓存对象。如果创建过程比较耗时,可能会阻塞主线程。建议将创建操作放在后台线程中执行,避免影响用户体验。

    Android LruCache 源码深度剖析:内存优化与性能调优实战

LruCache 在图片加载库中的应用

很多优秀的图片加载库,如 Glide、Fresco 等,都使用了 LruCache 来缓存图片。例如,Glide 会使用 LruCache 缓存解码后的 Bitmap 对象,从而避免重复解码,提高图片加载速度。

总结

android.util.LruCache 是一个简单而强大的内存缓存工具。通过深入了解其源码和原理,并结合实际场景进行优化,可以有效地提升应用的性能和稳定性。希望本文对你在 Android 开发中使用 LruCache 有所帮助。

关于 Android LruCache 的应用场景,可以参考 Nginx 在 Web 服务中的作用。Nginx 作为反向代理服务器,常常使用缓存来加速静态资源的访问。通过将静态资源缓存在 Nginx 中,可以减轻后端服务器的压力,提高网站的并发连接数。类似地,LruCache 在 Android 应用中也起到了类似的作用,通过缓存常用的数据,可以减少对网络或数据库的访问,提高应用的响应速度。为了进一步优化 Nginx 性能,可以考虑使用宝塔面板来管理 Nginx 配置,以及根据实际情况调整 Nginx 的 worker 进程数和连接超时时间。

Android LruCache 源码深度剖析:内存优化与性能调优实战

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea2.store/article/42776.html

本文最后 发布于2026-04-12 14:08:29,已经过了15天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 月光族 3 天前
    关于 create() 方法的建议很实用,避免在主线程做耗时操作是必须的!
  • 老王隔壁 2 天前
    学习了,之前只是简单用了一下 LruCache,看完这篇才对它的原理有了更深入的理解。
  • 月光族 1 天前
    文章通俗易懂,对新手很友好,点赞! 不过关于 Bitmap 的回收,除了 recycle() 还需要注意什么吗?
  • 四川担担面 3 天前
    关于 create() 方法的建议很实用,避免在主线程做耗时操作是必须的!