Glide 源码解析:从图片请求到透明 Fragment,以及权限库为何也爱这么做

这篇从 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 分成两段:RequestManagerRetrieverRequestTracker 负责"这次请求属于谁、什么时候可以跑";Engine 负责"资源从缓存还是数据源来、怎样解码"。把这两段分开理解,后面看到 Fragment 生命周期和缓存源码时就不容易混淆。


1. 先从你写的一行代码开始

日常使用很短:

kotlin 复制代码
Glide.with(view)
    .load("https://example.com/banner.webp")
    .placeholder(R.drawable.banner_placeholder)
    .into(imageView)

它实际完成了两件性质不同的事:

  1. 把模型数据(URL、文件、Uri、资源 ID)变成 Drawable 或 Bitmap。
  2. 决定这次请求归谁管理,以及这个宿主何时开始、暂停、销毁。

第二件更容易被忽略。假如用户快速退出页面,仍在执行的网络请求和解码任务没有被管理,就可能浪费带宽、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)

注意:不同请求类型会经过不同的 ModelLoaderDataFetcherResourceDecoderTransformation。它们是 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 当然能工作一部分场景,但会丢掉两个能力:

  1. Glide.with(thisFragment) 应该跟随这个 Fragment,而不是整个 Activity。
  2. 嵌套 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。按下面顺序更容易建立骨架:

  1. Glide.with()RequestManagerRetriever:确认 RequestManager 从哪里来。
  2. RequestManagerRequestTracker:确认请求如何被批量控制。
  3. SupportRequestManagerFragment / RequestManagerFragment:确认生命周期是怎样进来的。
  4. RequestBuilderSingleRequest:确认一次 into() 如何成为可执行请求。
  5. EngineEngineJobDecodeJob:确认缓存、去重和解码如何协作。
  6. RegistryModelLoaderDataFetcher:最后再研究 Glide 如何支持 URL、文件、Uri 和自定义模型。

读到某个类时,可以连续问四个问题:它由谁创建?它持有什么状态?它在什么生命周期被调用?它把结果交给谁?这些问题比死记类名更有用。


12. 最后的判断

Glide 使用无 UI Fragment,不是历史包袱式的"奇技淫巧"。在当时的 Android API 条件下,它让一个静态、链式的图片加载入口获得了准确的页面生命周期和 Fragment 层级信息。权限库采用同类形式,也是为了解决系统回调和业务调用之间的归属问题。

今天,权限和 Activity 结果更推荐 Activity Result API;图片加载则更推荐交给 Glide 或 Coil 这样的成熟框架。但底层思想值得留下:异步工作最重要的不只是开始执行,而是明确它属于谁、何时该暂停、结果该交给谁、宿主消失时怎样收尾。

相关推荐
Co_Hui18 天前
Glide 加载图片请求 到 完整显示
glide
Coffeeee3 个月前
如何使用Glide和Coil加载WebP动图
android·kotlin·glide
魏思凡3 个月前
Glide 源码学习系列
源码·glide
帅次5 个月前
链路到端上:HTTPS 之后安全题还在考什么
android·okhttp·glide·zygote·retrofit
studyForMokey5 个月前
【Android面试】Glide专题
android·面试·glide
花卷HJ6 个月前
[特殊字符] Glide 图片加载优化:自定义队列管理器(支持并发控制 + 防抖 + 滑动优化)
glide
Greenland_126 个月前
Android Java使用Glide无法生成GlideApp
android·java·glide
pvIaUtLZ7 个月前
每个支路的三相阻抗矩阵
glide
CjQYqIyjLwCW8 个月前
Halcon联合C#开发最新版实用框架 实际项目应用验证过的版本,源码,修改了大量Bug以适合...
glide