跳到主要内容

设计图片加载框架

问题

如何从零设计一个 Android 图片加载框架?

答案

整体架构

核心模块

模块职责关键技术
请求管理URL 去重、队列管理并发 Map、协程
内存缓存LRU 缓存 BitmapLruCache(maxSize = 堆内存 1/8)
磁盘缓存持久化原始/处理后的图片DiskLruCache
网络加载下载图片OkHttp
解码器按目标尺寸采样解码BitmapFactory.Options.inSampleSize
转换器圆角、模糊等变换Bitmap.createBitmap + Canvas
生命周期与 View / Activity 生命周期绑定Lifecycle 观察者

三级缓存

class ImageCache {
// L1:内存缓存(LruCache)
private val memoryCache = object : LruCache<String, Bitmap>(maxMemory / 8) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.allocationByteCount
}
}

// L2:磁盘缓存
private val diskCache = DiskLruCache.open(cacheDir, 1, 1, 100 * 1024 * 1024) // 100MB

fun get(key: String): Bitmap? {
// 先查内存
memoryCache.get(key)?.let { return it }
// 再查磁盘
diskCache.get(key)?.let { snapshot ->
val bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0))
memoryCache.put(key, bitmap) // 回填内存缓存
return bitmap
}
return null
}
}

采样解码(避免 OOM)

fun decodeSampledBitmap(filePath: String, reqWidth: Int, reqHeight: Int): Bitmap {
// 第一步:只读取尺寸
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeFile(filePath, options)

// 第二步:计算采样率
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false

// 第三步:按采样率解码
return BitmapFactory.decodeFile(filePath, options)
}

fun calculateInSampleSize(options: BitmapFactory.Options, reqW: Int, reqH: Int): Int {
val (height, width) = options.outHeight to options.outWidth
var inSampleSize = 1
if (height > reqH || width > reqW) {
val halfH = height / 2
val halfW = width / 2
while (halfH / inSampleSize >= reqH && halfW / inSampleSize >= reqW) {
inSampleSize *= 2
}
}
return inSampleSize
}

设计要点

  • 请求去重:同一 URL 并发请求只发起一次网络加载,其他请求等待结果
  • 取消机制:View 被复用时取消旧请求,避免图片显示错乱
  • 线程安全:缓存读写需要同步(LruCache 自身线程安全,磁盘操作需加锁)
  • Bitmap 复用:使用 inBitmap 复用已有的 Bitmap 内存块

常见面试问题

Q1: Glide 如何避免列表中的图片闪烁?

答案

  1. 生命周期感知:Glide 绑定到 Fragment/Activity 的生命周期,页面不可见时暂停加载
  2. Tag 标记:Glide 将请求绑定到 ImageView 的 Tag 上,RecyclerView 复用时自动取消旧请求
  3. 内存缓存:已加载的图片优先从内存缓存读取,无需等待网络
  4. 占位图placeholder() 在加载期间显示占位图,避免空白闪烁

相关链接