Skip to content

图片加载方式:概念 · 考点 · 优势 · 性能对比 ​

回到总览:图片加载 / Image Loading前置:图片加载方式总览与原理(本页的展开对比版,概念与架构图以本页为准);Landscapist 封装;Flutter 图片加载相关模块:Bitmap 内存与 GC · LRU 与淘汰策略 · Compose 状态模型

一句话定位 ​

本页把 Glide / Coil / Fresco / Compose 原生 / 手动解码 / Flutter 六类「图片加载」放在一起,从概念、考点(易考/易踩)、各自优势、性能对比四个角度横向拆开,并用架构图与具体例子说明复杂概念的差异。这是 02 的「复习/面试导向」对照页。

1. 概念速查(它们各自是什么) ​

方式一句话概念角色定位
GlideGoogle 主导的 Android 图片加载库,命令式 with().load().into(),成熟生态老牌全能型,生命周期自动、复用激进
CoilKotlin-first 库,ImageLoader + 协程 + AsyncImage(Compose 原语)轻量、Compose 原生、v3 起 KMP
FrescoFacebook 出品,Drawee(UI) + ImagePipeline(producers 链) + CloseableReference native 内存超大图/低端机、复杂管线优先
Compose 原生painterResource(本地同步) / AsyncImage(Coil) / SubcomposeAsyncImage声明式、零/低依赖
手动解码ImageDecoder(API28+) / BitmapFactory + inSampleSize/inBitmap来源特殊或需精确控制解码时
Flutter自绘引擎 dart:ui:Image/ImageProvider + 全局 ImageCache(内存),CachedNetworkImage 加磁盘层跨端一致、API 简洁、无 native 内存池

共同骨架:取字节 → 解码(降采样) → 写缓存(内存/磁盘) → 绑定生命周期绘制。区别只在「谁负责」与「缓存分几级」。Flutter 是这条骨架在 Dart 自绘世界的平行实现,与 Android 那套不互通。

2. 架构图(复杂概念的可视化) ​

2.1 Glide:四级查找 + BitmapPool ​

Glide 四级查找 + BitmapPool 架构

要点:两级内存(Active 防淘汰 + LRU 回收);解码复用来自 LruBitmapPool;生命周期靠注入隐藏 SupportRequestManagerFragment 监听 onDestroy。

2.2 Coil:Interceptor 责任链 ​

Coil Interceptor 责任链

要点:每个 Interceptor 只负责一级,命中即短路返回;整条链跑在协程 Dispatchers.IO;AsyncImagePainter 把结果映射成 Empty → Loading → Success / Error 状态机。

2.3 Fresco:Drawee + ImagePipeline(producers 链) ​

Fresco Drawee + ImagePipeline(producers 链)

要点:Drawee 只管 UI(层级 drawable + 生命周期),ImagePipeline 只管生产链,两者解耦;CloseableReference 引用计数管理(含 native)内存,使用方须 close()。

2.4 Flutter:ImageProvider.resolve + ImageCache + 磁盘层 ​

Flutter ImageProvider.resolve + ImageCache + 磁盘层

要点:Flutter 没有 native BitmapPool,ImageCache 是 Dart 侧 LRU(数量+字节双限);CachedNetworkImage 在内存之上再加一层设备磁盘缓存;cacheWidth/cacheHeight 在解码阶段降采样,等价于 Android 的 inBitmap/inSampleSize。

3. 考点(易考 / 易踩) ​

3.1 Glide ​

  • 四级缓存分别存什么?为什么要有「ActiveResources + MemoryCache」两级内存?
    • 一句话答:ActiveResources 持有正在显示的资源,MemoryCache 持有可复用的近期热图,Resource/Data 磁盘缓存分别存处理后资源与原始字节;两级内存是为了同时兼顾“正在用的不被误淘汰”和“滚回时能秒命中”。
  • 为什么用隐藏 Fragment 管生命周期?Fragment onDestroy 时做了什么(暂停/取消请求)?
    • 一句话答:因为它能稳定跟随 Activity/Fragment 生命周期;宿主销毁时 Glide 会通过这层生命周期代理清理、暂停或取消相关请求,避免页面没了请求还继续回调。
  • Glide.with(ctx) 传 Activity vs Application 的区别(生命周期范围不同,前者随页面销毁取消,后者随进程)。
    • 一句话答:传 Activity 会把请求绑到页面生命周期,离开页面自动停;传 Application 则更像进程级 loader,不会随单页销毁自动取消,适合脱离页面的长生命周期场景。
  • inBitmap / BitmapPool 复用条件(宽高、色彩格式一致才能复用)。
    • 一句话答:核心前提是待解码结果与待复用 Bitmap 在尺寸、配置和可复用性上兼容,否则解码器不能安全重用这块内存。
  • 列表 ImageView 复用时为什么要 clear()(防止错位 / 旧图覆盖新图)。
    • 一句话答:因为复用 View 会让旧请求晚到覆盖新 item;clear() 本质是在解绑旧目标、取消旧请求并释放占用,防止错位和多余解码。

3.2 Coil ​

  • Interceptor 责任链顺序,命中如何「短路」。
    • 一句话答:请求会按责任链依次经过缓存、取数、解码等拦截器,某一级一旦拿到最终结果就直接返回,不再继续往下走网络或解码路径。
  • MemoryCache 为什么分「强引用 + 弱引用」两部分(命中率 vs 内存弹性)。
    • 一句话答:强引用层保证热图稳定命中,弱引用层在 GC 还没回收前提供“捡漏式”复用,所以它是在命中率和内存压力之间折中。
  • 协程如何绑定生命周期(Composition 退出即取消)。
    • 一句话答:Compose 里的图片请求跟随 Composition/remember 作用域,节点离开组合或请求参数变化时,对应协程会被取消,旧结果不会再回写到已失效的 UI。
  • AsyncImagePainter 状态机有哪些状态。
    • 一句话答:核心就是 Empty、Loading、Success、Error 四态,分别对应未发起、加载中、成功出图和失败回退。
  • Coil 3 的 KMP 怎么做到的(ImageLoader 平台无关,网络栈可换 Ktor/OkHttp)。
    • 一句话答:它把 ImageLoader、请求模型和缓存接口做成跨平台抽象,再把网络与平台细节下沉到可替换组件,所以同一套加载模型能落到 Android、iOS、Desktop 等端。

3.3 Fresco ​

  • Drawee 与 ImagePipeline 为什么要分离(UI 与生产线解耦,管线可复用于非 UI 场景)。
    • 一句话答:Drawee 只关心显示与生命周期,ImagePipeline 只关心取数/缓存/解码,这样 UI 层和生产链路可以独立演进,也便于在非 UI 场景复用管线。
  • producers 链中「编码字节缓存(内存+磁盘)」与「解码后 Bitmap 缓存」两级各在哪、存什么。
    • 一句话答:前者缓存网络拿到的原始编码字节,适合避免重复下载;后者缓存已经解码好的 Bitmap/图像对象,适合直接拿来显示,二者分别服务“省网络”和“省解码”。
  • CloseableReference 引用计数,不 close() 的后果(内存泄漏 / native 内存不释放)。
    • 一句话答:它靠引用计数决定底层资源何时释放,所以忘记 close() 就等于一直有人在持有图像,结果就是 Java 对象和 native 内存都可能长期不回收。
  • 为什么现代 Android 上 Fresco 优势减弱(API 21+ Bitmap 像素已进 native,核心卖点被系统吸收)。
    • 一句话答:因为新版 Android 自己已经把 Bitmap 像素放到 native 内存,Fresco 当年最突出的“绕开 Dalvik 堆压力”红利被系统部分内建了,而 Glide/Coil 在包体和 Compose 适配上更轻。

3.4 Compose 原生 / 手动 ​

  • painterResource 同步、无缓存、适用场景(本地图标/资源)。
    • 一句话答:painterResource 读取的是本地资源并同步出图,不负责网络与多级缓存,因此最适合体积可控、立即可用的图标或包内静态图。
  • SubcomposeAsyncImage 与 AsyncImage 区别(前者每状态可子组合不同 UI)。
    • 一句话答:AsyncImage 更轻,适合大多数直接展示图片的场景;SubcomposeAsyncImage 则允许你对 loading/error/success 各自组合不同 UI,但代价是多一层子组合开销。
  • ImageDecoder(API28+) vs BitmapFactory 降采样 API 差异;手动解码为何仍需自己写缓存/取消。
    • 一句话答:ImageDecoder API 更新、能力更现代,BitmapFactory 则更底层也更老;但无论你用哪个自己解码,缓存、复用、取消、生命周期回收都不会自动出现,仍得自己补整套管理逻辑。

3.5 Flutter ​

  • ImageCache 双限淘汰:数量 maximumSize 与字节 maximumSizeBytes 谁先到先逐出;列表大图不 evict 会撑满内存。
    • 一句话答:Flutter 会在“张数上限”和“总字节上限”任一触发时开始逐出旧图,所以长列表大图如果只进不出,最终还是会把缓存和内存顶满。
  • ImageCache 绑定 root Isolate:新 Isolate 不共享缓存,跨 Isolate 加载不命中彼此。
    • 一句话答:因为 ImageCache 跟随当前 UI/root isolate 的运行时存在,不是全进程共享单例,所以你在别的 isolate 解过的图,主 isolate 默认拿不到现成缓存。
  • CachedNetworkImage 与 ImageCache 是两层:前者磁盘、后者内存;清内存缓存不代表清磁盘。
    • 一句话答:ImageCache 负责当前进程内的热图内存命中,CachedNetworkImage 负责把原始文件落到设备磁盘,两层命中时机和清理方式完全不同。
  • cacheWidth/cacheHeight 在解码阶段生效(控内存),不是显示尺寸;要省内存必须设 cacheWidth。
    • 一句话答:它控制的是“解码出多大的位图进入内存”,不是控件最终画多大,所以想真正降内存占用,必须在接近目标显示尺寸时就把解码尺寸压下来。
  • Flutter vs Android 内存世界观不同:无 BitmapPool,超大图只能靠 cacheWidth 降采样。
    • 一句话答:Flutter 没有 Glide 那种 Bitmap 复用池,图片主要靠 ImageCache 留住结果,因此遇到超大图时最有效的手段不是复用旧 bitmap,而是从解码入口就把尺寸降下来。

4. 优势对比(每种方式强在哪) ​

方式核心优势相对短板
Glide生态最成熟、BitmapPool 复用激进、生命周期全自动、老工程资料多API 命令式偏旧、包体大、无 KMP
CoilKotlin/协程原生、体积小、Compose AsyncImage 最顺手、v3 KMP极端内存手段(native)与超复杂管线弱于 Glide/Fresco
FrescoCloseableReference + native 内存对超大图/低端机(API<21)稳、producers 链可插拔包体最重、学习曲线陡、不 Compose 优先(需 AndroidView 桥)
Compose 原生本地资源零依赖、painterResource 同步可控;AsyncImage 声明式网络/大图能力依赖 Coil,无自带多级缓存
手动解码解码线程/采样点完全可控,能吃非标准来源缓存、生命周期、占位/重试全要自己写
Flutter跨端一致(Android/iOS/Web 同一 dart:ui)、API 简洁、磁盘+内存两层缓存易组合无 native 内存池,超大图仅靠 cacheWidth 控内存;需引 cached_network_image

5. 性能对比 ​

分数为相对排序(1–5,5 最好) ,用于横向直觉,非基准测试数字。维度:内存友好、包体积(小为好)、解码速度、KMP 多端、生命周期管理、易用/Compose 友好。

维度GlideCoilFrescoCompose 原生手动Flutter
内存友好4453*13**
包体积(小好)352554
解码速度443324
KMP 多端1513***15
生命周期管理544314
易用/Compose352414

* Compose 原生若只加载本地资源则内存无压力;若用它做网络图实际走 Coil,按 Coil 计。 ** Flutter 无 native 内存池,超大图靠 cacheWidth 降采样控内存;常规图 + ImageCache 双限足够。 *** Compose 原生跨端仅限本地资源;网络图需 Coil/KMP 后端才跨端。

结论直觉:

  • 内存最稳:Fresco(native + 引用计数主动释放)≥ Glide/Coil(Java 堆但复用好)> Flutter(Dart 堆,靠 cacheWidth)> 手动最差(无缓存)。
  • 最轻量 / 最 Compose 友好:Coil(小包体、AsyncImage)。
  • 跨端一致性第一:Flutter(自绘一套走全平台)≈ Coil 3 + Landscapist(KMP);Glide/Fresco 仅 Android。
  • 唯一真·多端图片库:Coil 3 + Landscapist;Flutter 则是另一套跨端方案(自绘,不与 Android 图库互通)。
  • 解码速度:冷解码都基于平台解码器,差距主要在缓存命中率与复用,而非解码本身。

6. 对比分析举例(复杂概念怎么理解) ​

例 1:同一张网络大图,首次加载 vs 第二次缓存命中 ​

  • 首次(都 miss 缓存) :
    • Glide:into → Active miss → Memory miss → Disk miss → Source 下载 → Downsampler 解码 → 回写 Disk/Memory/Active。
    • Coil:enqueue → Memory miss → Disk miss → FetchInterceptor 取字节 → DecodeInterceptor 解码 → EngineInterceptor 回写。
    • Fresco:NetworkFetchProducer → EncodedMemoryCache miss → DiskCache miss → DecodeProducer → BitmapMemoryCache → CloseableReference 交付 Drawee。
    • Flutter:CachedNetworkImage → 磁盘 miss → http 下载 → 写磁盘 → ImageProvider 解码进 ImageCache → 绘制。
    • 差异点:Glide 在「解码前」先落磁盘原始字节(DataCache);Coil/Fresco 在「编码字节」层就有内存+磁盘两级;Fresco 额外用 CloseableReference 管交付;Flutter 的磁盘层来自 CachedNetworkImage(非框架自带)。
  • 第二次(命中) :Glide 命中 MemoryCache、Coil 命中 MemoryCacheInterceptor、Fresco 命中 BitmapMemoryCacheProducer/EncodedMemoryCacheProducer、Flutter 同 session 命中 ImageCache(内存),杀进程重开则 Flutter 还能命中磁盘层。这正是多级缓存存在的意义:热图几乎零成本。

例 2:列表快速滚动(RecyclerView / LazyColumn / Flutter ListView) ​

  • Glide:视图 detach 时 clear(),且 ActiveResources 用弱引用持有正在显示的图,防止被 LRU 淘汰导致「滚动回来又重新加载/错位」。
  • Coil:AsyncImagePainter 随 Composition 离开而取消协程,旧请求不会写回已滚走的 item。
  • Fresco:DraweeController 监听 onAttach/onDetach,detach 时关闭 CloseableReference 释放图。
  • Flutter:CachedNetworkImage 的磁盘层让滚回的 item 直接读本地;ImageCache 双限自动淘汰离屏图,避免 OOM;手动 Image.network 则应注意滚走后适时 evict。
  • 考点回扣:不取消 → 复用 item 上旧请求先回来覆盖新图(错位),或并发解码堆积 OOM。各方案都把「取消/释放」绑到了生命周期,这是它们相对手动解码的核心价值。

例 3:超大图(如 8000×8000 地图/长图) ​

  • Fresco:native 内存 + 可配合区域解码,Dalvik 堆压力最小,低端机/API<21 最稳。
  • Glide:靠 override() 降采样到显示尺寸 + BitmapPool 复用,能扛但 Java 堆占用高于 Fresco。
  • Coil:同样降采样,能力齐但无 native 内存手段。
  • Flutter:必须显式 cacheWidth/cacheHeight 降采样(解码阶段缩尺寸),否则整图进 Dart 堆;无 native 内存池兜底。
  • 选型直觉:超大图 + 低端机 → Fresco;常规列表/详情图 → Coil(轻、Compose 原生);跨端统一 → Flutter 自绘(配 cacheWidth)。

本页回答的问题(一句话答) ​

  • 六类加载方式的核心区别在哪?
    • 一句话答:请求如何建模、缓存分几级、bitmap 如何复用、生命周期如何绑定——四件事的分工不同(详见 §1、§2)。
  • 多级缓存到底解决了什么?
    • 一句话答:热图命中内存/磁盘短路网络,几乎零成本;首次 miss 才走源解码并回写(见例 1)。
  • 为什么各库都比「手动解码」更适合列表?
    • 一句话答:它们把取消/释放绑到了生命周期,避免错位与 OOM;手动要自己重写这套(见例 2)。
  • 现代 Android 上 Fresco 还值不值得用?
    • 一句话答:常规场景 Glide/Coil 更轻更 Compose 友好;仅超大图/低端机/复杂管线才显 Fresco 的 native 内存优势(见 §3.3、例 3)。
  • KMP/多端图片加载目前谁行?
    • 一句话答:Android 侧只有 Coil 3(+ Landscapist);跨端自绘则是 Flutter 一套 dart:ui 走全平台——二者是平行世界观(见 §5)。
  • Flutter 的 ImageCache 和 Android 的 BitmapPool 是一回事吗?
    • 一句话答:不是。ImageCache 是 Dart 侧 LRU(数量+字节双限),存 ui.Image;BitmapPool 是 Glide 在 native 堆的 Bitmap 复用池。Flutter 无 native 复用池,超大图靠 cacheWidth(见 §3.5、§4)。

对应实验 ​

  • Flutter 侧已有可跑 lab:labs/flutter/host/lib/topics/image_loading/cached_network_image_demo_page.dart(CachedNetworkImage 网络图 + 占位/进度/失败 + cacheWidth 降采样演示,见 lab README)。
  • Android 侧(Glide/Coil/Fresco、Compose AsyncImage)暂无 lab:labs/kotlin/host 是 JVM 工程,无法运行 Android 图库;如后续建立 Android Host lab,可补最小 AsyncImage / GlideImage 对比工程。

复习检查题 ​

  1. Glide 为什么要「两级内存」(ActiveResources + MemoryCache)? 答:ActiveResources 用弱引用持有正在显示的图,防止它们被 MemoryCache 的 LRU 在仍在使用期间淘汰;两级配合 = 正在用的不被回收、不用的按 LRU 回收。
  2. Coil 的 MemoryCache 为什么分强引用 + 弱引用? 答:强引用(LRU)保证近期访问一定命中;弱引用在内存压力下可被 GC,被回收前再访问仍能命中,兼顾命中率与内存弹性。
  3. Fresco 的 Drawee 与 ImagePipeline 分离有什么好处? 答:UI 与生产线解耦,管线可被非 UI 场景复用,且切图时不阻塞生产链。
  4. 为什么现代 Android 上 Fresco 优势变小? 答:API 21+ Bitmap 像素已分配在 native 内存,Fresco 用 CloseableReference 绕开 Dalvik 堆的核心卖点被系统吸收;而 Glide/Coil 更轻、Compose 更友好。
  5. 列表滚动时若不取消图片请求会怎样? 答:复用 item 上旧请求先回来会覆盖新图(错位),并发解码堆积还可能 OOM;各库都把取消/释放绑到生命周期来避免。
  6. 谁支持 KMP/多端图片加载? 答:Android 侧仅 Coil 3(+ Landscapist);跨端自绘则是 Flutter 一套 dart:ui 走全平台,二者不互通。
  7. Flutter 的 ImageCache 和 BitmapPool 是一回事吗? 答:不是。ImageCache 是 Dart 侧 LRU(数量+字节双限)存 ui.Image;BitmapPool 是 Glide 在 native 堆的 Bitmap 复用池。Flutter 无 native 复用池,超大图只能靠 cacheWidth 降采样控内存。

速记 ​

  • 六类:Glide / Coil / Fresco / Compose 原生 / 手动 / Flutter。
  • 架构关键词:Glide=四级查找+BitmapPool;Coil=Interceptor 链+协程+状态机;Fresco=Drawee+producers 链+CloseableReference;Flutter=ImageProvider.resolve+ImageCache 双限+CachedNetworkImage 磁盘层。
  • 性能直觉:内存 Fresco≥Glide≈Coil > Flutter > 手动;轻量/Compose 友好 Coil 第一;跨端一致 Flutter≈Coil3,多端图库仅 Coil3。
  • 库 vs 手动:除非来源特殊,否则用库——取消/释放/缓存它都替你绑好了。
  • Flutter ≠ Android 图库:自绘 dart:ui,无 BitmapPool,超大图靠 cacheWidth。

站点构建时间:2026/8/24 23:43:17