在 Android 开发中,处理图片、网络请求结果等需要缓存的数据时,android.util.LruCache 是一个非常实用的工具。它基于 Least Recently Used (LRU) 算法实现,能够有效地管理内存,防止 OOM (Out of Memory) 异常。本文将深入分析 LruCache 的源码,并结合实际场景,探讨如何利用它来提升应用的性能。
LruCache 的基本原理
LruCache 的核心思想是:当缓存容量达到上限时,优先移除最近最少使用的数据。它内部使用 LinkedHashMap 来存储缓存数据,并利用 LinkedHashMap 的特性来实现 LRU 算法。
简单来说,LinkedHashMap 默认情况下按照元素的插入顺序维护元素的顺序,但可以通过构造函数中的 accessOrder 参数设置为 true,使其按照元素的访问顺序维护元素的顺序。当一个元素被访问时(get 或 put 操作),它会被移动到链表的末尾,表示最近被访问过。当缓存满时,链表的头部元素(最久未被访问的元素)会被移除。
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,强制移除所有缓存
}
// ... 省略其他辅助方法
}
实战避坑经验总结
合理设置 maxSize:
maxSize的设置至关重要。如果设置过小,会导致缓存频繁被移除,降低缓存命中率;如果设置过大,则可能导致 OOM。需要根据应用的实际情况,合理估算缓存数据的大小,并留有一定的余量。可以使用sizeOf()方法来精确计算每个缓存对象的大小。
重写 sizeOf() 方法: 默认情况下,
sizeOf()方法返回 1,表示每个缓存对象的大小相同。但实际上,不同对象的占用内存大小可能差异很大。因此,需要根据缓存对象的类型,重写sizeOf()方法,返回对象实际占用的内存大小。例如,对于图片缓存,可以返回图片的像素大小(宽度 * 高度 * 像素深度)。处理 Bitmap 对象: 当缓存
Bitmap对象时,需要注意及时回收不再使用的Bitmap对象,避免内存泄漏。可以在entryRemoved()方法中调用bitmap.recycle()方法来回收Bitmap对象。
线程安全:
LruCache本身是线程安全的,但需要注意在多线程环境下对缓存数据的并发访问。如果多个线程同时访问同一个缓存对象,可能会导致竞争条件。可以使用锁或其他同步机制来保护缓存数据。谨慎使用 create() 方法:
create()方法在缓存未命中时被调用,用于创建缓存对象。如果创建过程比较耗时,可能会阻塞主线程。建议将创建操作放在后台线程中执行,避免影响用户体验。
LruCache 在图片加载库中的应用
很多优秀的图片加载库,如 Glide、Fresco 等,都使用了 LruCache 来缓存图片。例如,Glide 会使用 LruCache 缓存解码后的 Bitmap 对象,从而避免重复解码,提高图片加载速度。
总结
android.util.LruCache 是一个简单而强大的内存缓存工具。通过深入了解其源码和原理,并结合实际场景进行优化,可以有效地提升应用的性能和稳定性。希望本文对你在 Android 开发中使用 LruCache 有所帮助。
关于 Android LruCache 的应用场景,可以参考 Nginx 在 Web 服务中的作用。Nginx 作为反向代理服务器,常常使用缓存来加速静态资源的访问。通过将静态资源缓存在 Nginx 中,可以减轻后端服务器的压力,提高网站的并发连接数。类似地,LruCache 在 Android 应用中也起到了类似的作用,通过缓存常用的数据,可以减少对网络或数据库的访问,提高应用的响应速度。为了进一步优化 Nginx 性能,可以考虑使用宝塔面板来管理 Nginx 配置,以及根据实际情况调整 Nginx 的 worker 进程数和连接超时时间。
冠军资讯
代码一只喵