Android图片三级缓存原理了解

一、引言:为什么需要三级缓存?

在移动应用开发中,图片加载是影响用户体验的关键因素之一。三级缓存架构通过 内存缓存 → 磁盘缓存 → 网络加载 的层次化设计,实现了:

  • 极速响应: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 开箱即用,功能完善 定制化难度较大 常规图片加载需求
仅使用内存缓存 响应速度最快 应用重启后缓存失效 临时性图片展示
仅使用磁盘缓存 持久化存储 读取速度较慢 低频访问的图片资源

四、完整使用步骤(以自定义实现为例)

  1. 初始化缓存

    public class App extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            MemoryCache.init(this);
            DiskCache.init(this);
        }
    }
    
  2. 图片加载调用

    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);
    }
    
  3. 配置内存回收监听

    public class MainActivity extends Activity {
        @Override
        public void onTrimMemory(int level) {
            if (level >= TRIM_MEMORY_MODERATE) {
                MemoryCache.clearSoftCache();
            }
        }
    }
    

五、关键优化技巧总结

  1. Bitmap内存优化

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565; // 内存减半
    options.inSampleSize = 2; // 尺寸缩小为1/2
    
  2. 动态调整缓存策略

    // 根据设备内存等级调整缓存比例
    ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    if (am.isLowRamDevice()) {
        cacheSize = maxMemory / 16; // 低端设备使用更小缓存
    }
    
  3. 线程池管理

    private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors() // 按CPU核心数配置
    );
    

六、监控与调试方案

  1. 内存泄漏检测

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
    
  2. 性能分析工具

    # 查看内存中Bitmap分布
    adb shell dumpsys meminfo <package_name> | grep "Bitmap"
    
  3. StrictMode检测

    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
        .detectDiskReads()
        .detectDiskWrites()
        .penaltyLog()
        .build());
    

七、结语:何时需要自研缓存?

推荐优先使用成熟库(Glide默认包含三级缓存),但在以下场景建议自研:

  • 需要深度定制缓存淘汰算法
  • 对二进制数据(非图片)进行缓存
  • 特殊加密/解密存储需求
  • 学习底层原理的最佳实践

作者建议:生产环境推荐使用Glide等成熟框架,本方案主要用于了解原理。

你可能感兴趣的:(Android实战与技巧,kotlin,android,java)