一、引言:为什么需要三级缓存?
在移动应用开发中,图片加载是影响用户体验的关键因素之一。三级缓存架构通过 内存缓存 → 磁盘缓存 → 网络加载 的层次化设计,实现了:
- 极速响应:90%+的图片请求命中内存缓存
- 流量节省:避免重复下载已缓存的图片
- 性能优化:降低GC频率,提升界面流畅度
二、三级缓存完整实现(含代码细节)
1. 内存缓存实现:LruCache + 弱引用双保险
核心代码实现
public class MemoryCache {
// 主缓存:基于LRU算法
private final LruCache<String, Bitmap> lruCache;
// 辅助缓存:应对内存紧张场景
private final Map<String, SoftReference<Bitmap>> softCache = new ConcurrentHashMap<>();
public MemoryCache(Context context) {
// 分配可用内存的1/8(可根据设备动态调整)
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
lruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
// 计算Bitmap占用的内存大小(KB)
return value.getByteCount() / 1024;
}
@Override
protected void entryRemoved(boolean evicted, String key,
Bitmap oldValue, Bitmap newValue) {
// 被移除的Bitmap转移到软引用缓存
softCache.put(key, new SoftReference<>(oldValue));
}
};
}
public Bitmap get(String key) {
Bitmap bitmap = lruCache.get(key);
if (bitmap == null) {
SoftReference<Bitmap> softRef = softCache.get(key);
if (softRef != null) {
bitmap = softRef.get();
if (bitmap != null) {
// 重新放入主缓存
lruCache.put(key, bitmap);
} else {
softCache.remove(key);
}
}
}
return bitmap;
}
public void put(String key, Bitmap bitmap) {
if (get(key) == null) {
lruCache.put(key, bitmap);
}
}
}
关键技术点
- 动态容量计算:根据设备内存自动调整缓存大小
- 软引用兜底:当LRU缓存满时,被移除的Bitmap暂时保留在软引用中
- 线程安全:使用
ConcurrentHashMap保证多线程安全
2. 磁盘缓存实现:DiskLruCache
初始化与写入
public class DiskCache {
private static final int APP_VERSION = 1;
private static final int VALUE_COUNT = 1;
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50; // 50MB
public static DiskLruCache open(Context context) throws IOException {
File cacheDir = new File(context.getCacheDir(), "image_cache");
if (!cacheDir.exists()) cacheDir.mkdirs();
return DiskLruCache.open(cacheDir, APP_VERSION, VALUE_COUNT, DISK_CACHE_SIZE);
}
public static void put(String key, Bitmap bitmap) {
DiskLruCache.Editor editor = diskCache.edit(key);
try {
OutputStream os = editor.newOutputStream(0);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
os.flush();
os.close();
editor.commit();
} catch (IOException e) {
editor.abort();
}
}
public static Bitmap get(String key) {
DiskLruCache.Snapshot snapshot = diskCache.get(key);
if (snapshot != null) {
InputStream is = snapshot.getInputStream(0);
return BitmapFactory.decodeStream(is);
}
return null;
}
}
3. 网络加载实现:OkHttp + 异步线程
public class NetworkLoader {
private static final OkHttpClient client = new OkHttpClient();
public static void load(String url, ImageView imageView) {
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) return null;
return BitmapFactory.decodeStream(response.body().byteStream());
} catch (IOException e) {
return null;
}
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
// 更新缓存
MemoryCacheHelper.put(url, bitmap);
DiskCacheHelper.put(url, bitmap);
}
}
}.execute();
}
}
三、技术对比与选型建议
| 技术方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动实现三级缓存 | 完全可控,深度优化空间大 | 开发成本高,易出错 | 对性能有极致要求的核心模块 |
| Glide/Picasso | 开箱即用,功能完善 | 定制化难度较大 | 常规图片加载需求 |
| 仅使用内存缓存 | 响应速度最快 | 应用重启后缓存失效 | 临时性图片展示 |
| 仅使用磁盘缓存 | 持久化存储 | 读取速度较慢 | 低频访问的图片资源 |
四、完整使用步骤(以自定义实现为例)
-
初始化缓存
public class App extends Application { @Override public void onCreate() { super.onCreate(); MemoryCache.init(this); DiskCache.init(this); } } -
图片加载调用
public void loadImage(String url, ImageView imageView) { // 1. 检查内存缓存 Bitmap bitmap = MemoryCache.get(url); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } // 2. 检查磁盘缓存 bitmap = DiskCache.get(url); if (bitmap != null) { imageView.setImageBitmap(bitmap); MemoryCache.put(url, bitmap); return; } // 3. 发起网络请求 NetworkLoader.load(url, imageView); } -
配置内存回收监听
public class MainActivity extends Activity { @Override public void onTrimMemory(int level) { if (level >= TRIM_MEMORY_MODERATE) { MemoryCache.clearSoftCache(); } } }
五、关键优化技巧总结
-
Bitmap内存优化
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; // 内存减半 options.inSampleSize = 2; // 尺寸缩小为1/2 -
动态调整缓存策略
// 根据设备内存等级调整缓存比例 ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); if (am.isLowRamDevice()) { cacheSize = maxMemory / 16; // 低端设备使用更小缓存 } -
线程池管理
private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors() // 按CPU核心数配置 );
六、监控与调试方案
-
内存泄漏检测
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' -
性能分析工具
# 查看内存中Bitmap分布 adb shell dumpsys meminfo <package_name> | grep "Bitmap" -
StrictMode检测
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .penaltyLog() .build());
七、结语:何时需要自研缓存?
推荐优先使用成熟库(Glide默认包含三级缓存),但在以下场景建议自研:
- 需要深度定制缓存淘汰算法
- 对二进制数据(非图片)进行缓存
- 特殊加密/解密存储需求
- 学习底层原理的最佳实践
作者建议:生产环境推荐使用Glide等成熟框架,本方案主要用于了解原理。