Appearance
图片加载方式总览与原理
回到总览:图片加载 / Image Loading上层封装:Landscapist:Compose 图片加载封装(本页讲的引擎即它背后的三个后端) 相关模块:Bitmap 内存与 GC(
inBitmap复用 / 降采样)· LRU 与淘汰策略(内存/磁盘缓存的淘汰基础)· Compose 状态模型(remember与重组语义)
一句话定义
「图片加载」在工程上 = 取字节(网络/磁盘/资源)→ 解码成 Bitmap/Drawable(含降采样)→ 缓存(内存/磁盘)→ 绑定到视图生命周期并绘制 四个阶段的组合。不同库的区别在于:请求如何建模、缓存分几级、bitmap 如何复用、生命周期如何绑定。本页按 Glide / Coil / Fresco / Compose 原生 / 手动解码 五类逐一拆解其内部机制。
代码索引
| 主题 | Lab 说明 | 源码 |
|---|---|---|
| 图片加载方式总览与原理 | 暂无可运行 lab(需 Android / Compose 运行环境;labs/kotlin/host 为 JVM,不适用) | — |
1. 实现原理
1.1 Glide:Request 链 + 四级查找 + BitmapPool
请求建模:Glide.with(context) 返回 RequestManager(绑定生命周期);.load(model) 返回 RequestBuilder;.into(target) 构建并启动 Request。
加载流程(内存四级 → 磁盘 → 源) :
into() ──► ActiveResources(弱引用,正在使用的资源)
└─ miss ─► MemoryCache(LruResourceCache,LRU)
└─ miss ─► DiskCache
├─ ResourceCache(已变换/解码的 bitmap)
└─ DataCache(原始字节,变换前)
└─ miss ─► Source(网络/文件解码)- ActiveResources:
Map<Key, ResourceWeakReference>,持有「正在显示」的资源弱引用,避免被 MemoryCache 的 LRU 淘汰掉正在用的图。 - MemoryCache / DiskCache:见
00的 LRU 与DiskLruCache。 - 解码:
Downsampler用BitmapFactory.Options.inSampleSize+inBitmap做降采样与复用(复用机制见02的inBitmap段);复用池是LruBitmapPool。 - 线程:磁盘读取/解码在
DiskCacheExecutor/SourceExecutor(后台),结果post回主线程绘制。 - 生命周期绑定:
RequestManagerRetriever向 Activity/Fragment 注入一个不可见的SupportRequestManagerFragment,监听onDestroy以暂停/取消请求。
1.2 Coil:ImageLoader 单例 + Interceptor 责任链
请求建模:AsyncImage(model, …)(Compose)或 imageLoader.enqueue(ImageRequest) 构建 ImageRequest,交给全局 ImageLoader(由 ImageLoader.Builder 配置 MemoryCache / DiskCache / BitmapPool / Components)。
加载流程(Interceptor 链) :
enqueue ──► MemoryCacheInterceptor(强引用 + 弱引用二级)
└─ miss ─► DiskCacheInterceptor(DiskLruCache)
└─ miss ─► FetchInterceptor(OkHttp/文件/Content 取字节)
└─► DecodeInterceptor(Decoder 解码,降采样)
└─► EngineInterceptor(写回 MemoryCache/DiskCache)- MemoryCache:
StrongMemoryCache(LRU,value 强引用)+WeakMemoryCache(value 弱引用,被 GC 前仍可命中),带引用计数。 - DiskCache:
DiskLruCache(Jake Wharton 版,journal 日志)。 - 协程驱动:整条链跑在
Dispatchers.IO;AsyncImagePainter把结果映射成状态机:Empty → Loading → Success / Error,Compose 据此切占位/成功/失败。 - 生命周期:
rememberAsyncImagePainter/AsyncImage把请求绑定到 Composition,离开组合取消。 - Coil 3 / KMP:
ImageLoader平台无关;网络栈可换 Ktor/OkHttp,同一AsyncImage在 Android / iOS / JVM / Wasm 复用。
1.3 Fresco:Drawee(UI)+ ImagePipeline(producers 链)
两层分离:
- Drawee(视图层) :
DraweeController+DraweeHierarchy(多层 drawable:placeholder / 实际图 / progress / retry / failure)。SimpleDraweeView是包裹DraweeHolder的View。 - ImagePipeline(非 UI 层) :一条
Producer责任链,每个节点只做一步并交给下游:
NetworkFetchProducer ─► DiskCacheProducer(编码字节,磁盘)
└─► EncodedMemoryCacheProducer(编码字节,内存)
└─► DecodeProducer(解码,PlatformDecoder:BitmapFactory/ImageDecoder)
└─► BitmapMemoryCacheProducer(解码后 bitmap,LRU)
└─► PostprocessorProducer ─► CloseableReference 交付 DraweeCloseableReference<T>:引用计数管理的 native/Java 内存句柄,使用方必须close()释放。早期 Android 上 bitmap 像素放 native 内存(Ashmem / pinned),绕开 Dalvik 堆上限——这是 Fresco 相对 Glide/Coil 的差异化点(现代 Android 上 bitmap 本身已进 native,该优势减弱)。- 生命周期:
DraweeController监听 View 的onAttach/onDetach;detach 时关闭CloseableReference释放图片。
1.4 Compose 原生加载
Image(painter = painterResource(R.drawable.x), …):同步解码本地资源(vector / drawable / mipmap),无网络、无缓存、无异步;适用于图标、本地图。AsyncImage(model, …)(Coil) :见 §1.2;声明式,placeholder/error/loading都是 Composable,状态来自AsyncImagePainter。SubcomposeAsyncImage:在loading/success/error各状态可子组合出不同 UI(如加载完成后用主色做背景),比AsyncImage更灵活。rememberAsyncImagePainter(model):返回Painter,自行用Image(painter)绘制,适合自定义绘制逻辑。- 手动解码:
ImageDecoder.decodeDrawable(API 28+) 或BitmapFactory.decodeStream+inSampleSize/inBitmap,得到Bitmap后Image(bitmap.asImageBitmap());用于需要精确控制解码线程/采样、或加载非标准来源(加密文件、自定义协议)的场景。
1.4.1 Android 本地资源解码与密度缩放
在使用 BitmapFactory.decodeResource(resources, id) 或 painterResource 加载打包在 APK 中的位图资源(PNG/JPG)时,Android 系统会在解码期隐式应用密度缩放:
- 缩放计算:系统将图片所在资源目录的密度设为
inDensity,设备的真实密度设为inTargetDensity,解码输出尺寸公式为:。 - 关联策略:为避免在低密度桶放置图片导致在现代高分屏设备上被二次放大、引发内存平方级飙升,本地小图标推荐使用矢量图(
VectorDrawable)或放置于高密度桶(如xxhdpi),大背景图则放置于drawable-nodpi配合inSampleSize降采样(详细物理内存计算与密度桶选型策略,见12/01 dp/dpi 与图片资源放置 §2-4)。
1.5 五类方式的共同骨架
无论哪类,内部都绕不开这四步,区别只在「谁负责」与「缓存分几级」:
取字节 → 解码(降采样)→ 写缓存(内存/磁盘)→ 绑定生命周期并绘制2. 特点与优势(与其他方式的区别)
- Glide vs Coil:Glide 生态成熟、
BitmapPool复用激进、生命周期自动、Java 时代积累多;代价是 API 偏旧(命令式into)、包体大、无 KMP。Coil Kotlin 优先、协程、体积小、Compose 原生AsyncImage、v3 起 KMP;代价是生态与极端内存手段(native 内存)弱于 Glide/Fresco。 - Glide/Coil vs Fresco:Fresco 的
CloseableReference+ native 内存对超大图/低端机(尤其是 API<21)更稳,producers 链可插拔、适合复杂管线;代价是包体最重、学习曲线陡、不 Compose 优先(需AndroidView桥SimpleDraweeView)。现代 Android(bitmap 已 native)上 Fresco 的相对优势明显缩小。 - 库加载 vs Compose 原生
painterResource:本地资源用painterResource最简、零依赖、同步可控;网络/大图必须走带缓存与生命周期管理的库(CoilAsyncImage或其封装),否则要自己重写缓存与取消逻辑。 - 库加载 vs 手动
BitmapFactory/ImageDecoder:手动解码给你解码线程与采样点的完全控制,但缓存、生命周期、占位/重试全要自己写;除非来源特殊(加密/私有格式),否则用库。 - 与 Landscapist 的关系:Landscapist 是 §1.1–§1.3 三个引擎的 Compose 封装层,自身不实现取字节/解码/缓存(见 01 的「缓存来自引擎」),本页才是被封装引擎的内部原理。
3. 用法
3.1 Glide
kotlin
Glide.with(imageView)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.apply(RequestOptions().override(400, 400).centerCrop())
.into(imageView)
// 取消:Glide.with(imageView).clear(imageView)(视图销毁自动触发)3.2 Coil(Compose)
kotlin
AsyncImage(
model = url,
contentDescription = "photo",
modifier = Modifier.size(200.dp),
placeholder = painterResource(R.drawable.placeholder),
error = painterResource(R.drawable.error),
)
// 或更低层:
val painter = rememberAsyncImagePainter(url)
Image(painter = painter, contentDescription = null)3.3 Fresco
kotlin
// Application 初始化一次:Fresco.initialize(context)
val controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setOldController(simpleDraweeView.controller)
.build()
simpleDraweeView.controller = controller
// 内存由 CloseableReference 管理,View detach 自动释放3.4 Compose 原生(本地资源 / 手动解码)
kotlin
// 本地资源:同步、无缓存
Image(painter = painterResource(R.drawable.icon), contentDescription = "icon")
// 手动解码(API 28+),自行控制采样与线程
val source = ImageDecoder.createSource(context.contentResolver, uri)
val drawable = ImageDecoder.decodeDrawable(source) { decoder, _, _ ->
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
decoder.setTargetSize(400, 400) // 降采样到目标尺寸
}
Image(painter = drawable.toPainter(), contentDescription = null)3.5 选型速查
| 场景 | 首选 |
|---|---|
| Android + Compose 网络图,轻量 | Coil AsyncImage |
| 需要多后端统一 API + 插件(blur/palette/shimmer)/ KMP | Landscapist(见 01) |
| 超大图 / 低端机 / 复杂管线 | Fresco |
| 老工程、生态依赖多 | Glide |
| 本地图标/资源 | painterResource |
| 加密/私有格式来源 | 手动 ImageDecoder + 库缓存 |
本页回答的问题(一句话答)
- Glide 的加载流程分几级查找?
- 一句话答:内存四级(ActiveResources 弱引用 → MemoryCache LRU → DiskCache 的 Resource/Data 两级 → Source),逐级 miss 才向下,解码后逐级回写。
- Coil 为什么是「责任链」而不是「一步步调用」?
- 一句话答:
ImageLoader把 MemoryCache/DiskCache/Fetch/Decode 做成Interceptor链,每个节点只负责一级(命中即短路返回),便于插入自定义拦截器与统一回写缓存。
- 一句话答:
- Fresco 的
CloseableReference解决什么?- 一句话答:用引用计数管理(含 native)内存,使用方
close()才释放,避免 bitmap 长时间占 Dalvik 堆;现代 Android 上该优势已减弱。
- 一句话答:用引用计数管理(含 native)内存,使用方
- Compose 里加载本地图和加载网络图为什么用不同 API?
- 一句话答:本地
painterResource同步解码、无缓存需求;网络/大图需缓存 + 生命周期 + 异步,交给 CoilAsyncImage或 Landscapist。
- 一句话答:本地
- 手动
BitmapFactory/ImageDecoder何时才该用?- 一句话答:仅当来源特殊(加密/私有协议)或需精确控制解码线程/采样;否则用库,避免重写缓存与取消逻辑。
对应实验
暂无对应 lab:图片加载库需要 Android / Compose 运行环境,而 labs/kotlin/host 是 JVM 工程,无法运行 Glide/Coil/Fresco 或 Compose AsyncImage;如后续建立 Android Host lab,可补一个最小 AsyncImage / GlideImage 验证工程。
复习检查题
- Glide 的「ActiveResources」和「MemoryCache」分别解决什么?为什么需要两级内存? 答:ActiveResources 用弱引用持有「正在显示」的资源,防止它们被 MemoryCache 的 LRU 在仍在使用期间淘汰;MemoryCache 是常规 LRU 缓存。两级配合:正在用的不被淘汰,不用的按 LRU 回收。
- Coil 的 MemoryCache 为什么有「强引用 + 弱引用」两部分? 答:强引用部分(LRU)保证近期访问一定命中;弱引用部分在内存压力下可被 GC 回收,被回收前若再次访问仍能命中,兼顾命中率与内存弹性。
- Fresco 的 ImagePipeline 和 Drawee 为什么要分开? 答:Drawee 只管 UI 层(层级 drawable + 生命周期),ImagePipeline 只管取字节/解码/缓存的生产链(producers),两者解耦后管线可被非 UI 场景复用,且 Drawee 切换图时不阻塞生产。
- 为什么现代 Android 上 Fresco 相对 Glide/Coil 的优势变小了? 答:API 21+ 起 Bitmap 像素本身已分配在 native 内存,Fresco 用
CloseableReference绕开 Dalvik 堆的核心卖点被系统吸收;而 Glide/Coil 更轻、Compose 更友好。 - 什么情况下应该「不用库、手动
ImageDecoder解码」? 答:来源不是标准 URL/文件(加密文件、私有协议、需自定义采样/色彩空间),或要在特定线程精确控制解码;此时仍需自己接缓存与生命周期。
速记
- 五类方式:Glide / Coil / Fresco / Compose 原生(
painterResource、AsyncImage)/ 手动(ImageDecoder、BitmapFactory)。 - 共同骨架:取字节 → 解码(降采样)→ 写缓存 → 绑定生命周期绘制。
- Glide:四级查找 +
BitmapPool(inBitmap) + 隐藏 Fragment 管生命周期。 - Coil:
ImageLoader单例 + Interceptor 链(内存→磁盘→取→解码→回写)+ 协程 +AsyncImagePainter状态机。 - Fresco:Drawee(UI) + ImagePipeline(producers 链) +
CloseableReference(native 内存,引用计数)。 - 库 vs 手动:除非来源特殊,否则用库,别自己重写缓存/取消。