这篇从
Glide.with()出发,顺着 RequestManager、生命周期、请求追踪与解码引擎一路走下去。重点不只是"Glide 怎么加载图片",而是看它为什么要放一个没有 UI 的 Fragment 在页面里,以及这个思路为什么也会出现在权限申请库中。
一句话结论
Glide 的核心不是单纯把网络图片解码出来,而是让一次图片请求和正确的 Android 生命周期绑定。Glide 4.x 中的 SupportRequestManagerFragment / RequestManagerFragment 就像一个无 UI 的生命周期宿主:它不展示界面,却能让 RequestManager 在页面可见时恢复请求、不可见时暂停、销毁时释放资源。RxPermissions 等权限库使用无 UI Fragment 的原因相似,只是它们托管的是系统权限回调,而不是图片请求。
本文中的类名以 Glide 4.x 源码的职责划分为准。不同小版本的字段、检索器内部实现可能调整,但"检索生命周期宿主 -> 获取 RequestManager -> 跟踪 Request -> 交给 Engine"的主线不变。
读图时先固定四个问题:入口是谁创建的?状态归谁持有?生命周期从哪里进来?结果最终回给谁? 后面的源码类再多,也可以放回这四个位置。
text
Glide.with(fragment)
↓
RequestManagerRetriever 找到生命周期宿主
↓
无 UI RequestManagerFragment / SupportRequestManagerFragment
↓
RequestManager 创建并追踪 SingleRequest
↓
RequestTracker 根据 onStart / onStop 暂停或恢复
↓
Engine 查询活动资源、内存缓存、磁盘缓存或发起解码
↓
Target 收到 Bitmap / Drawable
图 1:一次图片请求如何穿过 Glide
#mermaid-svg-V09z817nt9p8s73f{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V09z817nt9p8s73f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V09z817nt9p8s73f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V09z817nt9p8s73f .error-icon{fill:#552222;}#mermaid-svg-V09z817nt9p8s73f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V09z817nt9p8s73f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V09z817nt9p8s73f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V09z817nt9p8s73f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V09z817nt9p8s73f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V09z817nt9p8s73f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V09z817nt9p8s73f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V09z817nt9p8s73f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V09z817nt9p8s73f .marker.cross{stroke:#333333;}#mermaid-svg-V09z817nt9p8s73f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V09z817nt9p8s73f p{margin:0;}#mermaid-svg-V09z817nt9p8s73f .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V09z817nt9p8s73f .cluster-label text{fill:#333;}#mermaid-svg-V09z817nt9p8s73f .cluster-label span{color:#333;}#mermaid-svg-V09z817nt9p8s73f .cluster-label span p{background-color:transparent;}#mermaid-svg-V09z817nt9p8s73f .label text,#mermaid-svg-V09z817nt9p8s73f span{fill:#333;color:#333;}#mermaid-svg-V09z817nt9p8s73f .node rect,#mermaid-svg-V09z817nt9p8s73f .node circle,#mermaid-svg-V09z817nt9p8s73f .node ellipse,#mermaid-svg-V09z817nt9p8s73f .node polygon,#mermaid-svg-V09z817nt9p8s73f .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V09z817nt9p8s73f .rough-node .label text,#mermaid-svg-V09z817nt9p8s73f .node .label text,#mermaid-svg-V09z817nt9p8s73f .image-shape .label,#mermaid-svg-V09z817nt9p8s73f .icon-shape .label{text-anchor:middle;}#mermaid-svg-V09z817nt9p8s73f .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V09z817nt9p8s73f .rough-node .label,#mermaid-svg-V09z817nt9p8s73f .node .label,#mermaid-svg-V09z817nt9p8s73f .image-shape .label,#mermaid-svg-V09z817nt9p8s73f .icon-shape .label{text-align:center;}#mermaid-svg-V09z817nt9p8s73f .node.clickable{cursor:pointer;}#mermaid-svg-V09z817nt9p8s73f .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V09z817nt9p8s73f .arrowheadPath{fill:#333333;}#mermaid-svg-V09z817nt9p8s73f .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V09z817nt9p8s73f .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V09z817nt9p8s73f .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V09z817nt9p8s73f .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V09z817nt9p8s73f .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V09z817nt9p8s73f .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V09z817nt9p8s73f .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V09z817nt9p8s73f .cluster text{fill:#333;}#mermaid-svg-V09z817nt9p8s73f .cluster span{color:#333;}#mermaid-svg-V09z817nt9p8s73f div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V09z817nt9p8s73f .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V09z817nt9p8s73f rect.text{fill:none;stroke-width:0;}#mermaid-svg-V09z817nt9p8s73f .icon-shape,#mermaid-svg-V09z817nt9p8s73f .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V09z817nt9p8s73f .icon-shape p,#mermaid-svg-V09z817nt9p8s73f .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V09z817nt9p8s73f .icon-shape .label rect,#mermaid-svg-V09z817nt9p8s73f .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V09z817nt9p8s73f .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V09z817nt9p8s73f .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V09z817nt9p8s73f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务代码:Glide with
RequestManagerRetriever
RequestManager
RequestBuilder
SingleRequest
RequestTracker
Engine load
ImageViewTarget:资源回调
这张图把 Glide 分成两段:RequestManagerRetriever 到 RequestTracker 负责"这次请求属于谁、什么时候可以跑";Engine 负责"资源从缓存还是数据源来、怎样解码"。把这两段分开理解,后面看到 Fragment 生命周期和缓存源码时就不容易混淆。
1. 先从你写的一行代码开始
日常使用很短:
kotlin
Glide.with(view)
.load("https://example.com/banner.webp")
.placeholder(R.drawable.banner_placeholder)
.into(imageView)
它实际完成了两件性质不同的事:
- 把模型数据(URL、文件、Uri、资源 ID)变成 Drawable 或 Bitmap。
- 决定这次请求归谁管理,以及这个宿主何时开始、暂停、销毁。
第二件更容易被忽略。假如用户快速退出页面,仍在执行的网络请求和解码任务没有被管理,就可能浪费带宽、CPU 和内存;回调还可能继续持有已经销毁的 View。
Glide.with() 的职责不是"创建 Glide"
全局 Glide 通常是单例,with() 更重要的产物是一个和特定 Context、Activity 或 Fragment 绑定的 RequestManager。
下面是为了阅读而压缩过的伪代码,并非 Glide 的逐行源码:
kotlin
object Glide {
fun with(fragment: Fragment): RequestManager {
return requestManagerRetriever.get(fragment)
}
}
class RequestManagerRetriever {
fun get(fragment: Fragment): RequestManager {
checkMainThread()
// Glide 4.x 的具体检索细节会随版本演进,核心是:
// 取到与 fragment 绑定的无 UI 生命周期 Fragment。
val host = getSupportRequestManagerFragment(
fragment.childFragmentManager,
parent = fragment
)
return host.requestManager
}
}
图 2:为什么 with(fragment) 会找到无 UI Fragment
#mermaid-svg-XaoV1uQMJvLe8yH0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XaoV1uQMJvLe8yH0 .error-icon{fill:#552222;}#mermaid-svg-XaoV1uQMJvLe8yH0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XaoV1uQMJvLe8yH0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .marker.cross{stroke:#333333;}#mermaid-svg-XaoV1uQMJvLe8yH0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XaoV1uQMJvLe8yH0 p{margin:0;}#mermaid-svg-XaoV1uQMJvLe8yH0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .cluster-label text{fill:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .cluster-label span{color:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .cluster-label span p{background-color:transparent;}#mermaid-svg-XaoV1uQMJvLe8yH0 .label text,#mermaid-svg-XaoV1uQMJvLe8yH0 span{fill:#333;color:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .node rect,#mermaid-svg-XaoV1uQMJvLe8yH0 .node circle,#mermaid-svg-XaoV1uQMJvLe8yH0 .node ellipse,#mermaid-svg-XaoV1uQMJvLe8yH0 .node polygon,#mermaid-svg-XaoV1uQMJvLe8yH0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .rough-node .label text,#mermaid-svg-XaoV1uQMJvLe8yH0 .node .label text,#mermaid-svg-XaoV1uQMJvLe8yH0 .image-shape .label,#mermaid-svg-XaoV1uQMJvLe8yH0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XaoV1uQMJvLe8yH0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .rough-node .label,#mermaid-svg-XaoV1uQMJvLe8yH0 .node .label,#mermaid-svg-XaoV1uQMJvLe8yH0 .image-shape .label,#mermaid-svg-XaoV1uQMJvLe8yH0 .icon-shape .label{text-align:center;}#mermaid-svg-XaoV1uQMJvLe8yH0 .node.clickable{cursor:pointer;}#mermaid-svg-XaoV1uQMJvLe8yH0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .arrowheadPath{fill:#333333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XaoV1uQMJvLe8yH0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XaoV1uQMJvLe8yH0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XaoV1uQMJvLe8yH0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XaoV1uQMJvLe8yH0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .cluster text{fill:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 .cluster span{color:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XaoV1uQMJvLe8yH0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XaoV1uQMJvLe8yH0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XaoV1uQMJvLe8yH0 .icon-shape,#mermaid-svg-XaoV1uQMJvLe8yH0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XaoV1uQMJvLe8yH0 .icon-shape p,#mermaid-svg-XaoV1uQMJvLe8yH0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XaoV1uQMJvLe8yH0 .icon-shape .label rect,#mermaid-svg-XaoV1uQMJvLe8yH0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XaoV1uQMJvLe8yH0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XaoV1uQMJvLe8yH0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XaoV1uQMJvLe8yH0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 已存在
未创建
Glide with Fragment
RequestManagerRetriever
child FragmentManager
已有生命周期宿主
SupportRequestManagerFragment
添加无 UI Fragment
ActivityFragmentLifecycle
RequestManager
这里的"透明"不是给 Fragment 设置半透明 View;它根本不承载页面 UI。它被放进正确的 FragmentManager,目的是把 RequestManager 接到调用方的生命周期树上。实际 Glide 4.x 检索器还会处理主线程、待提交 Fragment、根节点和嵌套 Fragment 等细节;图中保留的是最关键的归属关系。
这里有一个很关键的分层:
| 对象 | 它解决的问题 |
|---|---|
Glide |
全局组件、Registry、Engine 等基础设施如何初始化? |
RequestManagerRetriever |
这次调用该绑定哪一个生命周期? |
RequestManager |
这批请求何时开始、暂停、清理? |
RequestBuilder / SingleRequest |
这一张图如何构建、执行、回调? |
把"全局图片系统"和"某个页面的一批请求"拆开,Glide 才能既复用缓存,又让页面退出时有正确的收尾动作。
2. 从 API 到图片:完整请求链路
把常见调用展开,可以得到一条更接近源码阅读顺序的链路:
text
Glide.with(fragment)
↓ RequestManagerRetriever
RequestManager
↓ asDrawable() / asBitmap() / load(model)
RequestBuilder
↓ into(imageView)
SingleRequest
↓ RequestManager.track()
RequestTracker
↓ begin()
Engine.load()
├─ ActiveResources:正在被界面使用的资源
├─ MemoryCache:内存 LRU 缓存
├─ EngineJob:相同 Key 的进行中任务复用
└─ DecodeJob:磁盘缓存 / 数据源 / 解码 / 变换
↓
Target.onResourceReady(resource)
注意:不同请求类型会经过不同的 ModelLoader、DataFetcher、ResourceDecoder 和 Transformation。它们是 Glide 可扩展性的来源;本文先抓住协调关系,避免一开始陷入每一种图片格式的细节。
into() 后到底发生了什么
RequestBuilder.into(imageView) 会根据当前配置构建请求。常见的实现对象是 SingleRequest,它保存本次加载的状态:模型、尺寸、优先级、占位图、Target、监听器、缓存策略等。
简化后的职责链:
kotlin
class RequestBuilder<TranscodeType>(
private val requestManager: RequestManager
) {
fun into(target: Target<TranscodeType>) {
val request = SingleRequest(
model = model,
target = target,
options = requestOptions
)
requestManager.track(target, request)
}
}
class RequestManager {
fun track(target: Target<*>, request: Request) {
target.setRequest(request)
requestTracker.runRequest(request)
}
}
这样实现的好处是:ImageView 不需要自己记住网络任务,RequestManager 可以统一暂停、恢复、清除所有属于它的请求。
3. RequestManager:一组请求的生命周期总管
RequestManager 可以理解成某个生命周期范围内的"请求控制台"。它会监听生命周期事件,并委托 RequestTracker 处理真正的批量动作。
概念伪代码如下:
kotlin
class RequestManager(
private val lifecycle: Lifecycle,
private val requestTracker: RequestTracker
) : LifecycleListener {
override fun onStart() {
requestTracker.resumeRequests()
}
override fun onStop() {
requestTracker.pauseRequests()
}
override fun onDestroy() {
requestTracker.clearRequests()
}
}
class RequestTracker {
private val running = mutableSetOf<Request>()
private val pending = mutableSetOf<Request>()
fun runRequest(request: Request) {
if (isPaused) pending += request else {
running += request
request.begin()
}
}
}
图 3:生命周期如何变成请求控制命令
#mermaid-svg-MCbQeTh9QAF8TJix{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MCbQeTh9QAF8TJix .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MCbQeTh9QAF8TJix .error-icon{fill:#552222;}#mermaid-svg-MCbQeTh9QAF8TJix .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MCbQeTh9QAF8TJix .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MCbQeTh9QAF8TJix .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MCbQeTh9QAF8TJix .marker.cross{stroke:#333333;}#mermaid-svg-MCbQeTh9QAF8TJix svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MCbQeTh9QAF8TJix p{margin:0;}#mermaid-svg-MCbQeTh9QAF8TJix .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MCbQeTh9QAF8TJix .cluster-label text{fill:#333;}#mermaid-svg-MCbQeTh9QAF8TJix .cluster-label span{color:#333;}#mermaid-svg-MCbQeTh9QAF8TJix .cluster-label span p{background-color:transparent;}#mermaid-svg-MCbQeTh9QAF8TJix .label text,#mermaid-svg-MCbQeTh9QAF8TJix span{fill:#333;color:#333;}#mermaid-svg-MCbQeTh9QAF8TJix .node rect,#mermaid-svg-MCbQeTh9QAF8TJix .node circle,#mermaid-svg-MCbQeTh9QAF8TJix .node ellipse,#mermaid-svg-MCbQeTh9QAF8TJix .node polygon,#mermaid-svg-MCbQeTh9QAF8TJix .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MCbQeTh9QAF8TJix .rough-node .label text,#mermaid-svg-MCbQeTh9QAF8TJix .node .label text,#mermaid-svg-MCbQeTh9QAF8TJix .image-shape .label,#mermaid-svg-MCbQeTh9QAF8TJix .icon-shape .label{text-anchor:middle;}#mermaid-svg-MCbQeTh9QAF8TJix .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MCbQeTh9QAF8TJix .rough-node .label,#mermaid-svg-MCbQeTh9QAF8TJix .node .label,#mermaid-svg-MCbQeTh9QAF8TJix .image-shape .label,#mermaid-svg-MCbQeTh9QAF8TJix .icon-shape .label{text-align:center;}#mermaid-svg-MCbQeTh9QAF8TJix .node.clickable{cursor:pointer;}#mermaid-svg-MCbQeTh9QAF8TJix .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MCbQeTh9QAF8TJix .arrowheadPath{fill:#333333;}#mermaid-svg-MCbQeTh9QAF8TJix .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MCbQeTh9QAF8TJix .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MCbQeTh9QAF8TJix .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MCbQeTh9QAF8TJix .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MCbQeTh9QAF8TJix .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MCbQeTh9QAF8TJix .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MCbQeTh9QAF8TJix .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MCbQeTh9QAF8TJix .cluster text{fill:#333;}#mermaid-svg-MCbQeTh9QAF8TJix .cluster span{color:#333;}#mermaid-svg-MCbQeTh9QAF8TJix div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MCbQeTh9QAF8TJix .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MCbQeTh9QAF8TJix rect.text{fill:none;stroke-width:0;}#mermaid-svg-MCbQeTh9QAF8TJix .icon-shape,#mermaid-svg-MCbQeTh9QAF8TJix .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MCbQeTh9QAF8TJix .icon-shape p,#mermaid-svg-MCbQeTh9QAF8TJix .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MCbQeTh9QAF8TJix .icon-shape .label rect,#mermaid-svg-MCbQeTh9QAF8TJix .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MCbQeTh9QAF8TJix .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MCbQeTh9QAF8TJix .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MCbQeTh9QAF8TJix :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Fragment onStart
Lifecycle start
Fragment onStop
Lifecycle stop
Fragment onDestroy
Lifecycle destroy
RequestManager
恢复请求
暂停请求
清理请求
SingleRequest 集合
重点是 RequestManager 不保存每一次网络 I/O 的实现细节,它只订阅生命周期并把批量命令交给 RequestTracker。因此业务页面不需要在每个 onStop() 中遍历 ImageView 取消任务;请求集合的控制权在 Glide 内部保持集中。
这段代码刻意省略了重试、连接状态、弱引用与请求状态机。它想表达的是:图片请求不是孤立任务,而是某个页面范围内的一组可控制任务。
为什么是暂停,不总是取消
页面进入后台时,图片请求通常不适合继续抢占资源,但用户回到页面又希望快速看到原来的内容。Glide 的请求状态机和缓存层可以在暂停、恢复、清除之间做更细的判断。对调用方而言,onStop() 后不必手写一大串 cancel();这就是生命周期绑定带来的价值。
4. Engine:缓存、任务去重与解码流水线
如果 RequestManager 是"谁来管请求",那么 Engine 解决的是"怎样高效拿到资源"。它并不只做一次网络请求。
一次 Engine.load() 的优先级思路
text
先找正在被界面使用的 Active Resource
↓ 没有
再找内存缓存 MemoryCache
↓ 没有
查看相同 EngineKey 是否已经有 EngineJob 在跑
↓ 没有
新建 EngineJob + DecodeJob
↓
尝试资源磁盘缓存 / 原始数据磁盘缓存 / 原始数据源
↓
解码、缩放、变换、转码
↓
回调所有等待同一个 Key 的请求
图 4:Engine 的命中、合并与解码分支
#mermaid-svg-qWuMN2Ha6LTe2Q8q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .error-icon{fill:#552222;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .marker.cross{stroke:#333333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q p{margin:0;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .cluster-label text{fill:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .cluster-label span{color:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .cluster-label span p{background-color:transparent;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .label text,#mermaid-svg-qWuMN2Ha6LTe2Q8q span{fill:#333;color:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .node rect,#mermaid-svg-qWuMN2Ha6LTe2Q8q .node circle,#mermaid-svg-qWuMN2Ha6LTe2Q8q .node ellipse,#mermaid-svg-qWuMN2Ha6LTe2Q8q .node polygon,#mermaid-svg-qWuMN2Ha6LTe2Q8q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .rough-node .label text,#mermaid-svg-qWuMN2Ha6LTe2Q8q .node .label text,#mermaid-svg-qWuMN2Ha6LTe2Q8q .image-shape .label,#mermaid-svg-qWuMN2Ha6LTe2Q8q .icon-shape .label{text-anchor:middle;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .rough-node .label,#mermaid-svg-qWuMN2Ha6LTe2Q8q .node .label,#mermaid-svg-qWuMN2Ha6LTe2Q8q .image-shape .label,#mermaid-svg-qWuMN2Ha6LTe2Q8q .icon-shape .label{text-align:center;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .node.clickable{cursor:pointer;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .arrowheadPath{fill:#333333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qWuMN2Ha6LTe2Q8q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qWuMN2Ha6LTe2Q8q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qWuMN2Ha6LTe2Q8q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .cluster text{fill:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .cluster span{color:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qWuMN2Ha6LTe2Q8q rect.text{fill:none;stroke-width:0;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .icon-shape,#mermaid-svg-qWuMN2Ha6LTe2Q8q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .icon-shape p,#mermaid-svg-qWuMN2Ha6LTe2Q8q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .icon-shape .label rect,#mermaid-svg-qWuMN2Ha6LTe2Q8q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qWuMN2Ha6LTe2Q8q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qWuMN2Ha6LTe2Q8q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qWuMN2Ha6LTe2Q8q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
是
否
Engine load:请求 Key
ActiveResources 命中
立即回调 Resource
MemoryCache 命中
同 Key 的 Job 运行中
加入 EngineJob 回调列表
新建 EngineJob 和 DecodeJob
磁盘缓存命中
解码与变换
读取网络、文件或 Uri
按策略写入缓存
图里的 EngineKey 是请求去重的关键。它不只包含 URL,还会综合尺寸、变换、资源类型和选项;所以"同一个 URL"不一定能复用资源,而"同一个完整 Key"的多个请求可以共用进行中的 EngineJob。这也是 Glide 能避免列表滚动时重复下载、重复解码的原因。
为什么要有 EngineJob
同一屏的多个 ImageView 可能请求同一个 URL、相同尺寸和相同变换。如果它们各自发起网络和解码,资源会被重复消耗。
EngineKey 会综合模型、签名、尺寸、变换、资源类型、选项等信息。相同 Key 的请求可加入同一个进行中的 EngineJob,完成后由 Job 通知多个回调。
kotlin
// 教学伪代码:表达"同 Key 合并",不是 Glide 的真实并发实现。
fun load(key: EngineKey, callback: ResourceCallback) {
memoryCache[key]?.let { callback.onResourceReady(it); return }
jobs[key]?.addCallback(callback)?.let { return }
val job = EngineJob(key)
job.addCallback(callback)
jobs[key] = job
executor.execute { decodeAndNotify(job) }
}
这样实现有三个直接收益:
- 相同资源不会重复下载和解码。
- 内存缓存负责快速命中,磁盘缓存减少 I/O 和网络。
DecodeJob可以按阶段切换线程池:磁盘、源数据、动画等工作不必都挤在同一条队列。
5. 真正的主角:无 UI 的 RequestManagerFragment
很多人第一次看到 Glide 源码会困惑:图片库为什么要创建 Fragment?答案是它需要一个能被 FragmentManager 管理、又能自然收到生命周期回调的对象。
Glide 4.x 中可以看到两类对应实现:
| 宿主类型 | 典型无 UI Fragment |
|---|---|
| framework Fragment / Activity | RequestManagerFragment |
| AndroidX Fragment / FragmentActivity | SupportRequestManagerFragment |
它们不负责 inflate 页面、不绘制任何 View;"透明 Fragment"是社区中的口语说法,更准确的名字是无 UI(headless / invisible)Fragment。
它如何把生命周期交给 RequestManager
概念上,Fragment 自己有一个 ActivityFragmentLifecycle,在 onStart()、onStop()、onDestroy() 时向已注册的监听器广播;RequestManager 就是其中一个监听器。
kotlin
class SupportRequestManagerFragment : Fragment() {
val glideLifecycle = ActivityFragmentLifecycle()
lateinit var requestManager: RequestManager
override fun onStart() {
super.onStart()
glideLifecycle.onStart()
}
override fun onStop() {
glideLifecycle.onStop()
super.onStop()
}
override fun onDestroy() {
glideLifecycle.onDestroy()
super.onDestroy()
}
}
这不是为了"偷偷放一个对象",而是借用 Android 已经提供的生命周期和层级模型。
为什么不直接把 Activity 传给 RequestManager
直接观察 Activity 当然能工作一部分场景,但会丢掉两个能力:
Glide.with(thisFragment)应该跟随这个 Fragment,而不是整个 Activity。- 嵌套 Fragment 有自己的可见性与生命周期范围,需要和父级关系一起处理。
因此 SupportRequestManagerFragment 还会维护根节点、子节点以及 RequestManagerTreeNode 一类的关系,帮助 Glide 在合适的范围内找到后代 RequestManager。这里的要点不是"Fragment 能存对象",而是"FragmentManager 已经是 Android 的生命周期树"。
6. 透明 Fragment 模式,本质上解决什么问题
我们把 Glide 的做法抽象一下:
text
业务入口是静态/链式 API
↓
需要把异步任务绑定到调用者的生命周期
↓
库不希望要求每个业务页面手写 onStart / onStop
↓
向当前 FragmentManager 挂一个无 UI Fragment
↓
该 Fragment 接收系统回调,再转发给库内部控制器
这是一种"把基础设施挂到生命周期树上"的模式。它适合以下条件同时满足的场景:
- 库需要生命周期,却不希望污染业务页面。
- 需要在配置变更后重新关联或保留正确的宿主关系。
- 需要接收系统回调,例如权限结果、Activity 结果、Fragment 可见状态。
- 调用入口希望保持简洁,例如
Glide.with(...)或requestPermission(...)。
它不适合成为任意全局状态的藏身处。无 UI Fragment 仍然属于页面层级,滥用会增加 FragmentManager 的时序与恢复成本。
7. 权限库为什么也常见无 UI Fragment
Android 的运行时权限结果最初通过 Activity.onRequestPermissionsResult() 或 Fragment.onRequestPermissionsResult() 返回。对于一个链式库而言,难点不是调用 requestPermissions(),而是:当系统回调回来时,怎么找到当初发起这次请求的那段业务逻辑?
RxPermissions 是这类设计的经典例子:它会把一个内部 Fragment 以固定 Tag 加到 FragmentManager,由该 Fragment 发起权限请求并接收结果,再把结果发给对应的 Rx 订阅者。不同版本的权限库细节不同,PermissionX 等实现中也常能看到 InvisibleFragment 一类命名,但不能把它们当作完全相同的源码实现。
Glide 与权限库的共同点和差异
| 对比项 | Glide | 基于无 UI Fragment 的权限库 |
|---|---|---|
| 核心目标 | 生命周期内管理图片请求 | 发起系统权限请求并路由结果 |
| Fragment 的角色 | 生命周期宿主和 RequestManager 树节点 | 系统回调接收器和请求队列宿主 |
| 主要回调 | onStart / onStop / onDestroy |
onRequestPermissionsResult + 生命周期 |
| 需要保存的状态 | 请求、Target、暂停状态 | requestCode、权限集合、回调或 Observable |
| 常见风险 | 使用 Application Context 导致无法自动暂停 | 并发请求、配置变更、回调丢失 |
共同点是都把"业务代码不该手动处理的系统协作"放到一个可管理的生命周期节点。不同点是 Glide 管的是任务状态,权限库管的是一次性系统结果。
图 5:Glide 与权限库复用的是同一种"生命周期桥"
#mermaid-svg-z4Ne8T0QwpA29Q0x{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z4Ne8T0QwpA29Q0x .error-icon{fill:#552222;}#mermaid-svg-z4Ne8T0QwpA29Q0x .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z4Ne8T0QwpA29Q0x .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .marker.cross{stroke:#333333;}#mermaid-svg-z4Ne8T0QwpA29Q0x svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z4Ne8T0QwpA29Q0x p{margin:0;}#mermaid-svg-z4Ne8T0QwpA29Q0x .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .cluster-label text{fill:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .cluster-label span{color:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .cluster-label span p{background-color:transparent;}#mermaid-svg-z4Ne8T0QwpA29Q0x .label text,#mermaid-svg-z4Ne8T0QwpA29Q0x span{fill:#333;color:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .node rect,#mermaid-svg-z4Ne8T0QwpA29Q0x .node circle,#mermaid-svg-z4Ne8T0QwpA29Q0x .node ellipse,#mermaid-svg-z4Ne8T0QwpA29Q0x .node polygon,#mermaid-svg-z4Ne8T0QwpA29Q0x .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .rough-node .label text,#mermaid-svg-z4Ne8T0QwpA29Q0x .node .label text,#mermaid-svg-z4Ne8T0QwpA29Q0x .image-shape .label,#mermaid-svg-z4Ne8T0QwpA29Q0x .icon-shape .label{text-anchor:middle;}#mermaid-svg-z4Ne8T0QwpA29Q0x .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .rough-node .label,#mermaid-svg-z4Ne8T0QwpA29Q0x .node .label,#mermaid-svg-z4Ne8T0QwpA29Q0x .image-shape .label,#mermaid-svg-z4Ne8T0QwpA29Q0x .icon-shape .label{text-align:center;}#mermaid-svg-z4Ne8T0QwpA29Q0x .node.clickable{cursor:pointer;}#mermaid-svg-z4Ne8T0QwpA29Q0x .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .arrowheadPath{fill:#333333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z4Ne8T0QwpA29Q0x .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z4Ne8T0QwpA29Q0x .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z4Ne8T0QwpA29Q0x .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z4Ne8T0QwpA29Q0x .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .cluster text{fill:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x .cluster span{color:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z4Ne8T0QwpA29Q0x .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z4Ne8T0QwpA29Q0x rect.text{fill:none;stroke-width:0;}#mermaid-svg-z4Ne8T0QwpA29Q0x .icon-shape,#mermaid-svg-z4Ne8T0QwpA29Q0x .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z4Ne8T0QwpA29Q0x .icon-shape p,#mermaid-svg-z4Ne8T0QwpA29Q0x .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z4Ne8T0QwpA29Q0x .icon-shape .label rect,#mermaid-svg-z4Ne8T0QwpA29Q0x .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z4Ne8T0QwpA29Q0x .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z4Ne8T0QwpA29Q0x .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z4Ne8T0QwpA29Q0x :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Glide with Fragment
无 UI Fragment
申请权限
无 UI Fragment
Start、Stop、Destroy
RequestManager 与 RequestTracker
暂停、恢复、清理图片请求
权限结果回调
请求编码与回调路由
业务权限结果
两者都借 FragmentManager 获得稳定的页面归属,但不能把它们等同起来:Glide 更关心一组可暂停、可清理的长短任务;权限库更关心"某一次系统弹窗结果该回给谁"。AndroidX Activity Result API 则把后者的生命周期注册与结果路由收进了官方组件,不再需要业务方自行维护 requestCode 或内部 Fragment。
权限桥的教学版伪代码
kotlin
class PermissionBridgeFragment : Fragment() {
private val callbacks = mutableMapOf<Int, (Map<String, Boolean>) -> Unit>()
fun request(
permissions: Array<String>,
requestCode: Int,
onResult: (Map<String, Boolean>) -> Unit
) {
callbacks[requestCode] = onResult
requestPermissions(permissions, requestCode)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
val result = permissions.indices.associate { index ->
permissions[index] to (grantResults[index] == PackageManager.PERMISSION_GRANTED)
}
callbacks.remove(requestCode)?.invoke(result)
}
}
这个教学版省略了 requestCode 分配、同一权限合并请求、永久拒绝判断、进程死亡恢复与取消逻辑。真实库需要把这些边界设计得更严谨。
8. 自己实现一个最小的生命周期请求宿主
下面的例子不是要重写 Glide,而是把"无 UI Fragment + 请求管理器"的核心关系跑通。它只做暂停、恢复、清理,省略网络、缓存、线程调度与 Fragment 事务竞争处理。
kotlin
interface ManagedTask {
fun start()
fun pause()
fun clear()
}
class TaskHostFragment : Fragment() {
private val tasks = linkedSetOf<ManagedTask>()
fun track(task: ManagedTask) {
tasks += task
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
task.start()
}
}
override fun onStart() {
super.onStart()
tasks.forEach(ManagedTask::start)
}
override fun onStop() {
tasks.forEach(ManagedTask::pause)
super.onStop()
}
override fun onDestroy() {
tasks.forEach(ManagedTask::clear)
tasks.clear()
super.onDestroy()
}
}
调用方需要先从 childFragmentManager 找到或安装这个宿主:
kotlin
private const val TASK_HOST_TAG = "task-host"
fun Fragment.taskHost(): TaskHostFragment {
val manager = childFragmentManager
val existing = manager.findFragmentByTag(TASK_HOST_TAG) as? TaskHostFragment
if (existing != null) return existing
// 仅用于理解关系:必须在主线程且 FragmentManager 未保存状态时调用。
return TaskHostFragment().also { host ->
manager.beginTransaction()
.add(host, TASK_HOST_TAG)
.commitNow()
}
}
生产代码不能把 commitNow() 当万能方案:在 onSaveInstanceState() 之后提交事务会触发状态丢失问题。Glide 的检索器需要处理主线程、待添加 Fragment 和生命周期边界,正是因为真实场景比这个教学版本复杂得多。
9. 今天还有必要新写透明 Fragment 吗
有时需要,但它已不再是默认首选。
权限与结果:优先 Activity Result API
AndroidX 的 registerForActivityResult() 把结果回调和生命周期注册统一起来,通常比自己维护 requestCode 与隐式 Fragment 更直接。
kotlin
class CameraFragment : Fragment() {
private val requestCamera = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) openCamera() else showPermissionHint()
}
fun askForCamera() {
requestCamera.launch(Manifest.permission.CAMERA)
}
}
在 Compose 中,rememberLauncherForActivityResult 是同一方向的 API。它们并没有否定"把结果绑定生命周期"的思想,只是官方将这个桥接层收进了 Activity Result 体系。
图片加载:优先直接使用成熟库
Glide、Coil、Picasso 解决的不止一条请求。尺寸计算、资源复用、GIF、视频帧、磁盘缓存、线程调度、变换和内存压力处理都需要长期维护。业务项目适合理解并选择它们,而不是因为看懂了无 UI Fragment 就自己实现一套图片框架。
10. 容易踩到的边界
| 场景 | 建议 |
|---|---|
| 在非页面任务中使用 Glide | Glide.with(applicationContext) 没有 Activity/Fragment 生命周期自动暂停,适合真正的应用级工作;不要把它当成页面请求的默认值。 |
| Fragment 内加载图片 | 优先 Glide.with(this) 或与 View 生命周期匹配的用法,避免让请求比实际界面活得更久。 |
| 嵌套 Fragment | 无 UI 宿主通常应挂在正确的 FragmentManager 范围,不能不分层级地只挂 Activity。 |
| 权限并发 | 必须为每次请求建立可识别的结果路由,并定义相同权限同时请求时是合并、排队还是拒绝。 |
| 状态已保存后添加 Fragment | 不要用 commitAllowingStateLoss() 轻率掩盖时序错误;先定义调用时机和恢复策略。 |
| 保存回调闭包 | 销毁时清空回调;长期持有 Activity、View 或匿名回调会造成泄漏风险。 |
11. 阅读 Glide 源码时的路线图
第一次阅读时,不必追所有泛型和所有 Decoder。按下面顺序更容易建立骨架:
Glide.with()与RequestManagerRetriever:确认 RequestManager 从哪里来。RequestManager、RequestTracker:确认请求如何被批量控制。SupportRequestManagerFragment/RequestManagerFragment:确认生命周期是怎样进来的。RequestBuilder、SingleRequest:确认一次into()如何成为可执行请求。Engine、EngineJob、DecodeJob:确认缓存、去重和解码如何协作。Registry、ModelLoader、DataFetcher:最后再研究 Glide 如何支持 URL、文件、Uri 和自定义模型。
读到某个类时,可以连续问四个问题:它由谁创建?它持有什么状态?它在什么生命周期被调用?它把结果交给谁?这些问题比死记类名更有用。
12. 最后的判断
Glide 使用无 UI Fragment,不是历史包袱式的"奇技淫巧"。在当时的 Android API 条件下,它让一个静态、链式的图片加载入口获得了准确的页面生命周期和 Fragment 层级信息。权限库采用同类形式,也是为了解决系统回调和业务调用之间的归属问题。
今天,权限和 Activity 结果更推荐 Activity Result API;图片加载则更推荐交给 Glide 或 Coil 这样的成熟框架。但底层思想值得留下:异步工作最重要的不只是开始执行,而是明确它属于谁、何时该暂停、结果该交给谁、宿主消失时怎样收尾。