编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

    • Kotlin精通

    • 库的解读

      • README
      • LeakCanary内存收集
      • Glide图片加载设计
        • OkHttp网络框架设计
        • EventBus事件总设计
        • ARouter路由实践设计
      • 专栏博客

    • iOS开发和进阶

    • Web开发和进阶

    • Linux应用开发

    • IoT智能硬件开发

    • Apps
    • Android提升进阶
    • 库的解读
    杨充
    2025-03-19
    目录

    Glide图片加载设计

    # 02.Glide图片加载框架设计原理

    # 目录介绍

    • 01.整体概述介绍
      • 1.1 项目背景介绍
      • 1.2 设计的目标
      • 1.3 核心方案设计
      • 1.4 一些问题思考
    • 02.Glide设计思路
      • 2.1 整体设计思路
      • 2.2 设计初始化思路
      • 2.3 封装参数设计
      • 2.4 解析路径设计
      • 2.5 读取资源设计
      • 2.6 缓存方案设计
      • 2.7 图片解码和压缩设计
      • 2.8 图片显示设计
      • 2.9 其他一些设计
    • 03.Glide原理思考
      • 3.1 要思考一些问题
      • 3.2 原理流程的概括
      • 3.3 with()绑定生命周期
      • 3.4 load()加载资源
      • 3.5 into()请求执行
      • 3.6 缓存机制详解
      • 3.7 图片解码和压缩
      • 3.8 图片显示和变换
    • 04.一些技术点思考
      • 4.1 为何监听生命周期
      • 4.2 空白Fragment的妙用
      • 4.3 对象池的优化思考
      • 4.4 缓存Key的设计
      • 4.5 LruCache淘汰策略
      • 4.6 Bitmap复用机制
      • 4.7 加载进度监听思考
      • 4.8 Glide核心设计哲学——"最小工作量"原则
      • 4.9 资源释放与内存回收的完整链路
      • 4.10 请求合并与去重的并发设计
    • 04a.Glide线程安全与并发控制设计
      • 4a.1 多线程并发访问的挑战
      • 4a.2 ActiveResources的线程安全设计
      • 4a.3 MemoryCache的线程安全设计
      • 4a.4 线程池的并发隔离设计
      • 4a.5 BitmapPool的线程安全与无锁设计尝试
    • 04b.DecodeJob状态机与责任链的深层设计
      • 4b.1 DecodeJob的运行模型
      • 4b.2 责任链模式在加载管线中的应用
      • 4b.3 加载管线的错误恢复机制
    • 05.Glide优秀的设计模式
      • 5.1 建造者模式(Builder Pattern)
      • 5.2 工厂模式(Factory Pattern)
      • 5.3 策略模式(Strategy Pattern)
      • 5.4 观察者模式(Observer Pattern)
      • 5.5 享元模式与对象池
      • 5.6 Registry注册机制的设计
      • 5.7 Engine调度的生产者-消费者设计
    • 06.如何实现加载速度监控
      • 6.1 加载速度监控背景
      • 6.2 加载速度思路分析
      • 6.3 替换通信组件
      • 6.4 添加拦截器和监听
      • 6.5 回调和计算加载速度
    • 06a.缩略图加载与渐进式显示设计
      • 6a.1 缩略图机制的设计原理
      • 6a.2 预加载(preload)的设计原理
    • 06b.列表滚动性能优化全景
      • 6b.1 RecyclerView中的Glide优化策略
      • 6b.2 快速滑动时的请求抑制策略
    • 07.面试高频问题深度解析
      • 7.1 经典面试题
      • 7.2 进阶面试题
      • 7.3 性能相关面试题

    # 01.整体概述介绍

    # 1.1 项目背景介绍

    在Android应用中,图片是最常见的资源类型之一,也是内存消耗的大户。一张1920x1080的图片在ARGB_8888格式下需要占用约8MB内存。如果直接加载大量图片,很容易导致OOM。

    图片加载看似简单,实际上涉及网络下载、磁盘缓存、内存缓存、图片解码、尺寸适配、内存管理、生命周期绑定等众多复杂问题。Glide是Google推荐的Android图片加载框架,由Bump Technologies开发,后被Google收购。它是一个快速高效的Android图片加载库,专注于平滑滚动。

    主流图片加载框架对比:
    
    | 特性            | Glide         | Picasso       | Fresco        |
    |----------------|---------------|---------------|---------------|
    | 缓存            | 内存+磁盘     | 内存+磁盘     | 内存+磁盘     |
    | 默认Bitmap格式  | RGB_565       | ARGB_8888     | ARGB_8888     |
    | 生命周期绑定    | 自动(Fragment)| 无           | 自动(DraweeView)|
    | GIF支持         | 支持          | 不支持        | 支持          |
    | 缓存Key         | 多维度        | URL           | URL+变换      |
    | 包体积          | 约700KB       | 约120KB       | 约3MB         |
    | 大图加载        | 一般          | 一般          | 优秀          |
    

    # 1.2 设计的目标

    如果让你来设计一个图片加载框架,核心目标应该包括:

    1. 高效加载:利用多级缓存(内存→磁盘→网络)减少重复加载,使用采样率和下采样减少内存占用
    2. 简洁API:通过链式调用简化使用,如 Glide.with(context).load(url).into(imageView)
    3. 生命周期感知:自动绑定Activity/Fragment的生命周期,页面销毁时自动取消请求
    4. 灵活配置:支持自定义缓存策略、图片变换、网络组件、解码格式等
    5. 内存安全:通过BitmapPool复用、适当的图片格式和采样率控制内存使用
    6. 强大扩展性:通过Registry注册机制支持自定义数据源、解码器、编码器等

    # 1.3 核心方案设计

    Glide的三步链式调用对应三个核心阶段:

    Glide三步调用的内部逻辑:
    
    Glide.with(context)   → 创建RequestManager,绑定生命周期
         .load(url)        → 创建RequestBuilder,配置加载参数
         .into(imageView)  → 创建Request,执行加载流程
    
    具体流程:
    with(context):
      ├── 获取Glide单例
      ├── 创建RequestManagerRetriever
      ├── 根据context类型获取/创建RequestManager
      └── 添加空白Fragment到Activity(绑定生命周期)
    
    load(url):
      ├── 创建RequestBuilder
      ├── 设置Model(url/file/resourceId等)
      └── 应用默认选项(占位图、错误图、缓存策略等)
    
    into(imageView):
      ├── 创建Target(ImageViewTarget)
      ├── 构建Request(SingleRequest)
      ├── 提交到RequestManager
      ├── 检查缓存(内存→磁盘)
      ├── 无缓存则下载(网络请求)
      ├── 解码Bitmap(采样、变换)
      ├── 缓存结果
      └── 回调Target显示图片
    

    # 1.4 一些问题思考

    基础问题:

    • Bitmap的使用过程中都有哪些常见问题?它如何占用内存?Glide是如何做内存优化的?
    • 如何使用Glide加载本地图片或资源文件?如何加载网络图片?
    • Glide的图片加载过程是如何进行的?图片缓存机制是怎样的?有哪些类型的缓存?

    高级问题:

    • Glide的生命周期是如何管理的?如何在Activity销毁时取消图片加载请求?
    • Glide是如何实现Bitmap复用的?BitmapPool是什么?
    • Glide的缓存Key是如何设计的?为什么不能只用URL作为Key?
    • Glide的三级缓存是如何协作的?ActiveResources的作用是什么?

    性能问题:

    • 如何使用Glide加载大型图片时避免内存溢出?
    • Glide的图片加载过程中如何处理图片的内存缓存和磁盘缓存?
    • 列表滚动时Glide如何保证加载性能?

    # 02.Glide设计思路

    # 2.1 整体设计思路

    图片加载框架的通用流程:

    图片加载完整流程:
    
    封装参数 → 解析路径 → 检查缓存 → 获取资源 → 解码压缩 → 变换处理 → 缓存结果 → 显示图片
    
    详细说明:
    1. 封装参数
       └── 从指定来源到输出结果,中间经历很多流程
       └── 封装URL、宽高、缓存策略、变换、占位图等参数
    
    2. 解析路径
       └── 图片来源多种:网络URL、本地文件、资源ID、ContentProvider等
       └── 需要规范化处理,将不同来源统一为数据流
    
    3. 检查缓存
       └── 内存缓存(ActiveResources → MemoryCache)
       └── 磁盘缓存(ResourceCache → DataCache)
    
    4. 获取资源
       └── 本地文件直接读取
       └── 网络图片通过HttpUrlConnection或OkHttp下载
    
    5. 解码压缩
       └── 根据目标View尺寸计算采样率
       └── 使用BitmapFactory.Options进行下采样解码
       └── 选择合适的Bitmap格式(RGB_565/ARGB_8888)
    
    6. 变换处理
       └── 圆角、圆形、模糊、滤镜等Transformation
    
    7. 缓存结果
       └── 变换后的Bitmap缓存到内存和磁盘
    
    8. 显示图片
       └── 切换到主线程
       └── 设置到ImageView
       └── 可添加动画(淡入、crossFade等)
    

    # 2.2 设计初始化思路

    Glide的初始化通过懒加载实现,在首次调用Glide.with()时触发:

    Glide初始化流程:
    
    Glide.with(context) 
      → Glide.get(context)
        → 双重检查锁获取单例
        → 如果未初始化:
           → 扫描Manifest中的GlideModule配置
           → 通过APT生成的GeneratedAppGlideModuleImpl获取配置
           → 创建GlideBuilder
           → 配置BitmapPool、MemoryCache、DiskCache等
           → 注册默认的ModelLoader、Decoder、Encoder等
           → 构建Glide单例
    
    GlideBuilder配置项:
    ├── BitmapPool:Bitmap复用池
    ├── ArrayPool:字节数组复用池
    ├── MemoryCache:内存缓存(LruResourceCache)
    ├── DiskCache:磁盘缓存工厂
    ├── Engine:加载引擎
    ├── DecodeFormat:默认解码格式
    ├── RequestManagerRetriever:RequestManager获取器
    └── ConnectivityMonitorFactory:网络状态监听
    

    # 2.3 封装参数设计

    Glide通过RequestBuilder和RequestOptions封装请求参数:

    请求参数封装:
    
    RequestBuilder(请求构建器):
    ├── model:数据源(String URL / File / Uri / Integer resId)
    ├── requestOptions:请求选项
    ├── transitionOptions:过渡动画选项
    ├── transformations:变换列表
    └── target:目标(ImageViewTarget等)
    
    RequestOptions(请求选项):
    ├── placeholderDrawable:占位图
    ├── errorDrawable:错误图
    ├── fallbackDrawable:model为null时的兜底图
    ├── diskCacheStrategy:磁盘缓存策略
    ├── priority:请求优先级
    ├── overrideWidth/Height:覆盖宽高
    ├── sizeMultiplier:尺寸倍数
    ├── isTransformationRequired:是否需要变换
    └── signature:自定义缓存Key签名
    

    # 2.4 解析路径设计

    Glide通过ModelLoader机制支持多种数据源的加载:

    ModelLoader注册表(Registry):
    
    数据源(Model)          → ModelLoader              → 数据类型(Data)
    String(URL)            → HttpGlideUrlLoader        → InputStream
    String(file path)      → StringLoader<InputStream>  → InputStream
    File                   → FileLoader                 → InputStream
    Integer(resId)         → ResourceLoader             → InputStream
    Uri                    → UriLoader                  → InputStream
    byte[]                 → ByteArrayLoader            → InputStream
    GlideUrl               → HttpGlideUrlLoader         → InputStream
    
    ModelLoader的职责:
    1. 判断是否能处理给定的Model类型
    2. 将Model转换为可读取的Data(通常是InputStream)
    3. 构建DataFetcher执行实际的数据获取
    
    DataFetcher的职责:
    1. 执行实际的数据获取操作(网络下载/文件读取)
    2. 支持取消操作
    3. 回调获取结果或错误
    

    # 2.5 读取资源设计

    Glide的资源读取通过Engine引擎协调多个组件完成:

    Engine加载流程:
    
    engine.load(...)
      ↓
    Step 1: 检查ActiveResources(活跃资源缓存)
      ├── 命中 → 直接返回(最快)
      └── 未命中 ↓
    
    Step 2: 检查MemoryCache(LRU内存缓存)
      ├── 命中 → 移到ActiveResources → 返回
      └── 未命中 ↓
    
    Step 3: 检查是否有相同请求正在执行
      ├── 有 → 复用该请求,添加回调等待结果
      └── 没有 → 创建新的EngineJob + DecodeJob ↓
    
    Step 4: DecodeJob执行(线程池中)
      ├── Stage.RESOURCE_CACHE → 检查磁盘缓存(变换后的资源)
      ├── Stage.DATA_CACHE → 检查磁盘缓存(原始数据)
      └── Stage.SOURCE → 从数据源获取(网络下载/文件读取)
      ↓
    Step 5: 解码+变换
      └── 使用ResourceDecoder解码为Bitmap
      └── 应用Transformation变换
      ↓
    Step 6: 缓存结果
      └── 缓存到磁盘(如果策略允许)
      └── 缓存到内存(如果策略允许)
      ↓
    Step 7: 回调通知
      └── 切换到主线程
      └── 回调Target.onResourceReady()
    

    # 2.6 缓存方案设计

    背景: 在移动应用中,图片加载面临网络延迟和带宽限制等问题,缓存是提高用户体验的关键。

    Glide的三级缓存设计:

    Glide缓存层级(由快到慢):
    
    第一级:ActiveResources(活跃资源缓存)
    ├── 存储当前正在被View使用的资源
    ├── 使用WeakReference保持引用
    ├── 当View不再使用时,资源转移到MemoryCache
    ├── 防止LruCache淘汰正在使用的资源
    └── 最快:直接内存访问
    
    第二级:MemoryCache(LRU内存缓存)
    ├── 使用LruResourceCache实现
    ├── 默认大小 = 屏幕宽×高×4字节×2(两屏)
    ├── 淘汰策略:最近最少使用(LRU)
    ├── 被淘汰的Bitmap进入BitmapPool等待复用
    └── 快:内存访问,但有LRU计算开销
    
    第三级:DiskCache(磁盘缓存)
    ├── 使用DiskLruCache实现
    ├── 默认大小250MB
    ├── 两种缓存类型:
    │   ├── ResourceCache:变换后的资源(可直接使用)
    │   └── DataCache:原始数据(需要重新解码)
    └── 慢:需要磁盘IO和解码
    
    第四级(非缓存):网络/数据源
    └── 从网络下载或本地文件读取
    └── 最慢:需要网络IO
    
    缓存策略(DiskCacheStrategy):
    ├── ALL:缓存原始数据和变换后的资源
    ├── DATA:只缓存原始数据
    ├── RESOURCE:只缓存变换后的资源
    ├── AUTOMATIC(默认):根据DataFetcher自动选择
    └── NONE:不使用磁盘缓存
    

    疑惑:为什么需要ActiveResources?直接用MemoryCache不行吗?

    答疑: MemoryCache使用LRU淘汰策略,当缓存满时会自动淘汰最久未使用的项。但如果某个Bitmap正在被ImageView显示,LRU可能会错误地将它淘汰。ActiveResources使用WeakReference持有正在使用的资源,保证正在显示的图片不会被MemoryCache淘汰。当图片不再显示时,WeakReference被回收,资源重新进入MemoryCache。

    # 2.7 图片解码和压缩设计

    场景: 加载图片到ImageView上时,合理做法是根据目标ImageView的尺寸对原始图像进行下采样。

    Android的普通方案: 采样率压缩+质量压缩,使用BitmapFactory.Options的inJustDecodeBounds参数先获取图片尺寸,再计算inSampleSize进行下采样。

    Glide的极致方案:

    Glide图片解码流程:
    
    1. 获取目标尺寸
       └── 从Target获取目标宽高
       └── 如果ImageView尺寸未确定,等待布局完成
    
    2. 预读取图片信息
       └── 设置inJustDecodeBounds = true
       └── 只读取图片宽高和类型,不加载到内存
       └── 获取原始图片的EXIF旋转信息
    
    3. 计算采样率
       └── 根据原始尺寸和目标尺寸计算inSampleSize
       └── 采样率必须是2的幂次(1, 2, 4, 8, ...)
       └── 使用DownsampleStrategy控制策略:
           ├── FIT_CENTER:缩放到目标区域内
           ├── CENTER_CROP:裁剪填满目标区域
           ├── CENTER_INSIDE:不放大只缩小
           └── AT_MOST:不超过目标尺寸
    
    4. 复用Bitmap
       └── 从BitmapPool获取可复用的Bitmap
       └── 设置inBitmap = reusedBitmap
       └── 避免频繁创建新的Bitmap对象
    
    5. 解码
       └── 设置inPreferredConfig(默认RGB_565,比ARGB_8888省一半内存)
       └── BitmapFactory.decodeStream()
       └── 处理EXIF旋转
    
    6. 后处理
       └── 如果采样后尺寸仍然不精确
       └── 使用Matrix进行精确缩放
    

    # 2.8 图片显示设计

    图片加载完成后的显示流程:

    图片显示流程:
    
    DecodeJob完成解码
      ↓
    EngineJob回调onResourceReady()
      ↓
    切换到主线程(通过Handler)
      ↓
    Target.onResourceReady(resource, transition)
      ├── ImageViewTarget:
      │   ├── 设置Drawable到ImageView
      │   ├── 执行过渡动画(如CrossFade淡入)
      │   └── 通知请求完成
      └── CustomTarget/ViewTarget等其他Target
    
    过渡动画类型:
    ├── NoTransition:无动画
    ├── DrawableCrossFadeTransition:淡入淡出
    ├── GenericTransitionOptions:自定义过渡
    └── withCrossFade(duration):指定淡入时长
    

    # 2.9 其他一些设计

    BitmapPool的设计:

    BitmapPool(Bitmap复用池):
    
    作用:复用已不再使用的Bitmap对象,避免频繁创建和GC
    
    原理:
    1. 当Bitmap从MemoryCache中被淘汰时,不直接回收
       → 放入BitmapPool等待复用
    2. 下次需要创建Bitmap时,先从Pool中查找
       → 找到尺寸匹配的Bitmap → 通过inBitmap复用
       → 找不到 → 创建新的Bitmap
    3. Pool满时淘汰最久未使用的Bitmap
    
    复用条件(Android 4.4+):
    ├── 被复用的Bitmap大小 >= 新Bitmap需要的大小
    ├── Bitmap的Config匹配(或新Bitmap为ARGB_8888)
    └── 被复用的Bitmap必须是mutable的
    
    内存节省效果:
      假设列表滚动加载100张图片
      无复用:创建100个Bitmap对象 + 100次GC
      有复用:创建~10个Bitmap对象 + ~0次GC
    

    # 03.Glide原理思考

    # 3.1 要思考一些问题

    深入Glide源码前的思考:
    
    1. with(context)做了什么?为什么需要传context?
    2. 空白Fragment是如何绑定生命周期的?
    3. load(url)为什么不直接开始加载?
    4. into(imageView)到底触发了什么?
    5. 缓存是如何命中的?Key是如何计算的?
    6. 线程是如何调度的?哪些在主线程,哪些在工作线程?
    

    # 3.2 原理流程的概括

    Glide的完整加载流程可以分为三大阶段:

    阶段一:with(context) → 创建RequestManager
    
    Glide.with(activity)
      → RequestManagerRetriever.get(activity)
        → 判断是否在主线程
        ├── 非主线程 → 使用Application级RequestManager
        └── 主线程 → 获取Activity的FragmentManager
           → 查找/创建SupportRequestManagerFragment
           → 从Fragment获取RequestManager
           → 如果没有则创建新的RequestManager并关联Fragment
    
    阶段二:load(model) → 配置请求参数
    
    requestManager.load(url)
      → 创建RequestBuilder<Drawable>
      → 设置model = url
      → 返回RequestBuilder(支持链式调用继续配置)
    
    阶段三:into(target) → 执行加载
    
    requestBuilder.into(imageView)
      → 创建ImageViewTarget
      → 构建Request(SingleRequest)
      → requestManager.track(target, request)
        → targetTracker.track(target)  // 跟踪Target
        → requestTracker.runRequest(request)  // 执行请求
          → request.begin()
            → 获取目标尺寸 → onSizeReady()
            → engine.load(...)  // 进入Engine加载引擎
    

    # 3.3 with()绑定生命周期

    with()方法根据传入的context类型选择不同的处理策略:

    with()的context类型处理:
    
    with(Context context):
      └── 如果是Application → 使用ApplicationManager(不绑定生命周期)
      └── 如果是Activity → 提取Activity处理
      └── 如果是其他 → 尝试获取Activity,失败则用Application
    
    with(Activity activity):
      └── 创建/获取空白Fragment
      └── Fragment绑定Activity生命周期
      └── RequestManager监听Fragment生命周期
    
    with(Fragment fragment):
      └── 使用Fragment的ChildFragmentManager
      └── 创建/获取子空白Fragment
    
    关键代码逻辑:
    RequestManagerRetriever.get(Activity activity) {
        if (isOnBackgroundThread()) {
            return get(activity.getApplicationContext());
        }
        FragmentManager fm = activity.getFragmentManager();
        return fragmentGet(activity, fm, null);
    }
    
    fragmentGet() {
        // 查找或创建SupportRequestManagerFragment
        SupportRequestManagerFragment fragment = getSupportRequestManagerFragment(fm);
        RequestManager requestManager = fragment.getRequestManager();
        if (requestManager == null) {
            requestManager = new RequestManager(fragment.getLifecycle(), ...);
            fragment.setRequestManager(requestManager);
        }
        return requestManager;
    }
    

    为什么在非主线程时使用Application级RequestManager?

    因为Fragment的事务(add/remove)只能在主线程中执行。如果在子线程调用Glide.with(activity),添加Fragment会抛出异常。因此子线程中降级使用Application级别的RequestManager,不绑定生命周期。

    # 3.4 load()加载资源

    load()方法仅仅是配置参数,不触发实际加载:

    load()支持的数据源类型:
    
    load(String url)       → 网络图片URL / 文件路径
    load(File file)        → 本地文件
    load(Uri uri)          → Content URI / File URI
    load(Integer resourceId) → 资源ID (R.drawable.xxx)
    load(byte[] bytes)     → 字节数组
    load(Drawable drawable) → Drawable对象
    
    load()内部逻辑(非常简单):
    RequestBuilder<Drawable> load(String model) {
        return loadGeneric(model);
    }
    
    private RequestBuilder<Drawable> loadGeneric(Object model) {
        this.model = model;
        this.isModelSet = true;
        return this;  // 返回自身,支持链式调用
    }
    

    # 3.5 into()请求执行

    into()是真正触发加载的方法:

    into()执行流程:
    
    into(ImageView imageView)
      ↓
    1. 根据ImageView的ScaleType创建对应的Target
       └── FIT_CENTER → DrawableImageViewTarget
       └── CENTER_CROP → DrawableImageViewTarget (with CenterCrop transform)
    
    2. 构建Request
       └── buildRequest() → SingleRequest.obtain(...)
       └── Request中包含所有配置参数
    
    3. 清理旧请求
       └── 如果ImageView已有绑定的Request → 取消旧请求
       └── 防止列表滚动时图片错位
    
    4. 执行请求
       └── RequestManager.track(target, request)
       └── request.begin()
           └── 获取目标尺寸(可能需要等待View布局)
           └── onSizeReady(width, height)
           └── engine.load(key, width, height, ...)
    
    5. Engine加载
       └── 检查内存缓存 → 命中则直接返回
       └── 检查是否有相同请求在执行 → 复用
       └── 创建EngineJob + DecodeJob → 提交到线程池
       └── DecodeJob按阶段依次检查:磁盘缓存 → 数据源
    
    6. 结果回调
       └── EngineJob切换到主线程
       └── Target.onResourceReady(resource)
       └── ImageView.setImageDrawable(drawable)
    

    # 3.6 缓存机制详解

    Glide完整缓存流程(读取顺序):
    
    读取:
    1. ActiveResources → WeakReference持有,最快
    2. MemoryCache (LRU) → 固定大小,按LRU淘汰
    3. ResourceCache (磁盘) → 缓存变换后的图片
    4. DataCache (磁盘) → 缓存原始数据
    5. Source → 网络下载/文件读取
    
    写入(反向):
    获取到原始数据 → 写入DataCache
    解码+变换后 → 写入ResourceCache
    加载完成 → 写入MemoryCache → 移入ActiveResources
    
    缓存Key设计(非常关键):
    EngineKey = hash(
        model,           // 数据源(URL等)
        signature,       // 自定义签名
        width,           // 目标宽度
        height,          // 目标高度
        transformations, // 变换列表
        resourceClass,   // 资源类型
        transcodeClass,  // 转码类型
        options          // 其他选项
    )
    
    疑惑:为什么Key包含width和height?
    答疑:同一张图片加载到不同尺寸的ImageView中,
          解码后的Bitmap尺寸不同。
          如果共享同一个缓存,可能导致图片模糊或内存浪费。
          因此不同尺寸需要不同的缓存。
    

    # 3.7 图片解码和压缩

    Glide图片解码核心——Downsampler:
    
    Downsampler.decode() 核心逻辑:
    
    1. 获取图片原始信息
       options.inJustDecodeBounds = true
       BitmapFactory.decodeStream(is, null, options)
       → 得到原始宽高 sourceWidth, sourceHeight
    
    2. 获取EXIF旋转信息
       → 如果图片有旋转标记,交换宽高
    
    3. 计算目标尺寸
       DownsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, 
                                          targetWidth, targetHeight)
       → FIT_CENTER: min(targetW/sourceW, targetH/sourceH)
       → CENTER_CROP: max(targetW/sourceW, targetH/sourceH)
    
    4. 计算inSampleSize
       → 必须是2的幂次
       → 保证采样后的尺寸 >= 目标尺寸
    
    5. 尝试从BitmapPool获取可复用Bitmap
       options.inBitmap = bitmapPool.get(targetWidth, targetHeight, config)
       → 如果inBitmap复用失败会抛异常,需要catch后重试
    
    6. 解码
       options.inJustDecodeBounds = false
       options.inSampleSize = calculatedSampleSize
       options.inPreferredConfig = preferredConfig
       Bitmap bitmap = BitmapFactory.decodeStream(is, null, options)
    
    7. 精确缩放(如果采样后尺寸不精确)
       → 使用Matrix.setScale() 进行精确缩放
       → Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true)
    

    # 3.8 图片显示和变换

    图片变换(Transformation)机制:
    
    内置变换:
    ├── CenterCrop:居中裁剪填满
    ├── FitCenter:等比缩放适应
    ├── CenterInside:不放大只缩小
    ├── CircleCrop:圆形裁剪
    ├── RoundedCorners:圆角
    ├── GranularRoundedCorners:不同角的圆角
    └── Rotate:旋转
    
    变换执行时机:
    解码Bitmap → 应用Transformation → 缓存变换结果
    
    自定义变换示例:
    class BlurTransformation : Transformation<Bitmap> {
        override fun transform(pool: BitmapPool, bitmap: Bitmap, 
                              outWidth: Int, outHeight: Int): Resource<Bitmap> {
            // 从Pool获取目标Bitmap
            val result = pool.get(outWidth, outHeight, bitmap.config)
            // 执行模糊处理
            val blurred = applyBlur(bitmap, result)
            return BitmapResource.obtain(blurred, pool)
        }
        
        override fun updateDiskCacheKey(messageDigest: MessageDigest) {
            // 影响缓存Key的计算
            messageDigest.update("blur_transform".toByteArray())
        }
    }
    
    多变换组合:
    Glide.with(context)
        .load(url)
        .transform(CenterCrop(), RoundedCorners(20))
        .into(imageView)
    // 多变换会封装为MultiTransformation,按顺序执行
    

    # 04.一些技术点思考

    # 4.1 为何监听生命周期

    with()方法可以接收Context、Activity或Fragment类型的参数。传入的实例决定了Glide加载图片的生命周期:

    • 传入Activity/Fragment → 页面销毁时图片加载自动停止
    • 传入ApplicationContext → 只有应用被杀时图片加载才停止

    为什么需要绑定生命周期?

    如果不绑定生命周期,当用户退出页面后,图片加载请求仍在执行,不仅浪费网络和CPU资源,还可能导致:

    1. 回调中访问已销毁的View → Crash
    2. 加载结果设置到已复用的ImageView → 图片错位
    3. 内存无法及时释放 → 内存增长

    # 4.2 空白Fragment的妙用

    Glide使用空白Fragment(SupportRequestManagerFragment/RequestManagerFragment)来监听生命周期,这是一个非常巧妙的设计:

    空白Fragment监听生命周期原理:
    
    Activity.onCreate()
      → Glide.with(activity)
        → 添加空白Fragment到Activity
        
    Activity.onStart()
      → Fragment.onStart()
        → RequestManager.onStart()
          → 恢复所有暂停的请求
    
    Activity.onStop()
      → Fragment.onStop()
        → RequestManager.onStop()
          → 暂停所有正在执行的请求
    
    Activity.onDestroy()
      → Fragment.onDestroy()
        → RequestManager.onDestroy()
          → 取消所有请求
          → 清理所有Target
          → 释放所有资源
    
    优势:
    ├── 不需要侵入Activity/Fragment的代码
    ├── 不需要开发者手动管理生命周期
    ├── Fragment自动跟随宿主的生命周期
    └── 支持Activity和Fragment两个级别的生命周期绑定
    
    注意:这也是bug高发地带
    └── 延迟回调(如网络请求callback)中Activity可能已销毁
    └── 此时Glide对象也已销毁
    └── 再调用Glide加载图片会Crash
    └── 解决:在回调中先检查Activity.isFinishing()/isDestroyed()
    

    # 4.3 对象池的优化思考

    业务背景: Glide频繁请求图片时,如果每次都new这些类(Request、Key、Options等),大量图片请求时频繁创建和销毁会导致内存抖动。

    Glide中的对象池化策略:
    
    1. BitmapPool(Bitmap对象池)
       └── LruBitmapPool实现
       └── 复用不再显示的Bitmap
       └── 通过inBitmap机制实现真正的零拷贝复用
    
    2. ArrayPool(字节数组池)
       └── LruArrayPool实现
       └── 复用解码过程中的byte[]缓冲区
       └── 减少频繁的内存分配
    
    3. FactoryPools(通用对象池)
       └── 复用Request、GlideException等频繁创建的对象
       └── 使用obtain()/recycle()模式
       └── 类似Android的Message对象池
    
    4. 多条件Key缓存
       └── EngineKey使用多个字段组合(url+宽高+变换等)
       └── Key对象使用ObjectPool复用
       └── 查找缓存时,命中的Key可以回收到Pool中
    

    # 4.4 缓存Key的设计

    缓存Key的多维度设计:
    
    疑惑:为什么不能只用URL作为缓存Key?
    
    场景1:同一URL,不同尺寸的ImageView
      → 100x100的缩略图 和 500x500的大图
      → 如果共用一个缓存,缩略图拿到大图的Bitmap会浪费内存
      → 大图拿到缩略图的Bitmap会模糊
    
    场景2:同一URL,不同变换
      → 圆角图 和 圆形图
      → 变换结果完全不同,不能共用缓存
    
    场景3:同一URL,图片已更新
      → 服务器更新了图片但URL不变
      → 需要自定义signature来使缓存失效
    
    因此Glide的EngineKey包含多个维度:
    EngineKey = {
        model,            // URL/File/Uri等
        signature,        // 自定义签名(可用于缓存失效)
        width,            // 目标宽度
        height,           // 目标高度
        transformations,  // 变换列表
        resourceClass,    // 资源类型
        transcodeClass,   // 转码类型
        options           // 加载选项
    }
    

    # 4.5 LruCache淘汰策略

    LRU(Least Recently Used)淘汰策略:
    
    原理:维护一个按访问时间排序的双向链表
      → 每次访问某项,将其移到链表头部
      → 当缓存满时,淘汰链表尾部的项(最久未使用)
    
    Glide的LruResourceCache实现:
    继承自Android的LruCache<Key, Resource<?>>
    
    缓存大小计算:
      默认大小 = 屏幕宽 × 屏幕高 × 每像素字节数 × 系数
      系数根据设备内存等级调整:
      ├── lowMemory设备:0.33
      ├── 普通设备:0.4
      └── 高内存设备:0.4
    
    淘汰时的处理:
      被淘汰的Resource → 放入BitmapPool等待复用
      (而不是直接回收,减少GC压力)
    

    # 4.6 Bitmap复用机制

    Bitmap复用(inBitmap)原理:
    
    Android 3.0+:
      被复用的Bitmap必须和新Bitmap完全相同的尺寸和Config
    
    Android 4.4+(Glide主要依赖):
      被复用的Bitmap字节大小 >= 新Bitmap需要的字节大小
      (更灵活,大尺寸的Bitmap可以被小尺寸复用)
    
    Glide的BitmapPool查找逻辑:
    bitmapPool.get(width, height, config)
      → 按 width×height×bytesPerPixel 计算需要的字节数
      → 从Pool中找到 >= 该字节数的最小Bitmap
      → 如果找到 → 返回该Bitmap(设置为inBitmap)
      → 如果没找到 → 返回null(将创建新Bitmap)
    
    复用失败处理:
      如果inBitmap设置的Bitmap不满足条件
      → BitmapFactory会抛出IllegalArgumentException
      → Glide catch该异常
      → 清除inBitmap → 重新解码(不复用)
    

    # 4.7 加载进度监听思考

    Glide默认不支持加载进度监听,需要替换网络组件:

    加载进度监听方案:
    
    思路:
    1. 替换Glide的HTTP通信组件为OkHttp
    2. 利用OkHttp的拦截器机制
    3. 在拦截器中包装ResponseBody
    4. 在读取数据时回调进度
    
    实现步骤:
    1. 添加Glide的OkHttp集成库
    2. 自定义OkHttp拦截器
    3. 包装ResponseBody,重写source()方法
    4. 在read()中计算已读字节/总字节 = 进度
    5. 通过回调通知上层
    

    # 4.8 Glide核心设计哲学——"最小工作量"原则

    Glide 的设计中有一条贯穿始终的哲学:永远只做最少的工作。这条理念渗透在加载管线的每个环节:

    "最小工作量"原则的五层体现:
    
    第一层:能不用加载就不用加载
    ├── 缓存优先策略:ActiveResources → MemoryCache → DiskCache → Source
    ├── 四级缓存层层拦截,99%的命中发生在内存层
    └── 如果内存已有,连磁盘IO都不做
    
    第二层:能复用就不新建
    ├── BitmapPool:Bitmap对象池,通过inBitmap实现零拷贝复用
    ├── ArrayPool:字节数组池,解码过程中的byte[]缓冲区复用
    ├── FactoryPools:通用对象池,Request/Key等高频对象的obtain()/recycle()
    └── 列表滚动100张图 → 实际只创建~10个Bitmap对象
    
    第三层:能读小的就不读大的
    ├── 下采样:根据ImageView实际尺寸计算inSampleSize
    ├── 默认RGB_565:比ARGB_8888省一半内存
    ├── 缩略图优先:先显示低质量缩略图,再替换为全尺寸图
    └── 一张1920x1080的图显示在100x100的ImageView中 → 内存从8MB降到40KB
    
    第四层:能等别人做完就等着
    ├── 请求合并:相同Key的请求共享同一个DecodeJob
    ├── 列表快速滚动时,同一个URL可能被多个ImageView同时请求
    ├── Glide不创建多个下载任务,而是让后续请求等待第一个的结果
    └── 一次下载,N次分发
    
    第五层:能做一半就不做完整
    ├── 缩略图机制:先显示0.25倍采样率的图,再加载全尺寸
    ├── 生命周期感知:页面不可见时暂停请求,可见时恢复
    ├── 优先级队列:当前可见的图片优先加载
    └── 用户滑走了的图片 → 直接取消,不浪费资源
    

    为什么要坚持"最小工作量"?

    在移动端,每一毫秒CPU时间、每一KB内存分配都影响用户体验。Glide 面对的典型场景是列表快速滚动——用户一秒钟滑过10个Item,每个Item都有一张图片。如果"努力工作":

    不遵循最小工作量原则的后果:
    ├── 10张图片 × 10MB原始大小 = 100MB内存 → OOM崩溃
    ├── 10个网络请求并发 → 带宽争抢,全部变慢
    ├── 10次Bitmap创建+10次GC → UI线程卡顿
    └── 列表滚出屏幕的图片仍在加载 → 浪费流量和电量
    
    遵循最小工作量原则的结果:
    ├── 10张图片 × 40KB(采样后) = 400KB内存 → 平稳
    ├── 相同URL只下载1次 → 节省90%流量
    ├── BitmapPool复用 → 几乎零GC
    └── 屏幕外的图片自动取消 → 不浪费资源
    

    # 4.9 资源释放与内存回收的完整链路

    Glide 的内存管理有一条精密的资源回收链路,确保没有任何 Bitmap 被"遗忘":

    资源生命周期完整链路:
    
    创建阶段:
    BitmapFactory.decode() 创建Bitmap
      → 包装为BitmapResource
      → 进入Engine加载流程
    
    使用阶段:
    资源加载完成
      → 回调Target.onResourceReady()
      → 资源被Target持有
      → 同时被ActiveResources持有(WeakReference)
    
    释放触发时机:
    触发一:View被回收(列表滚动)
      → ImageView.onViewRecycled()
      → Target.onLoadCleared()
      → 资源从ActiveResources移除
      → Bitmap是否直接回收?
         ├── 尺寸符合BitmapPool条件 → 进入BitmapPool等待复用
         └── 尺寸不符合 → 调用bitmap.recycle()直接回收
    
    触发二:页面销毁(Activity.onDestroy())
      → Fragment.onDestroy()
      → RequestManager.onDestroy()
      → 取消所有进行中的请求
      → 清理所有Target
      → 遍历ActiveResources,逐一释放
      → 符合条件的进入BitmapPool
    
    触发三:LRU淘汰(MemoryCache满)
      → 最久未使用的Resource被淘汰
      → 移除前检查:是否正在被ActiveResources引用?
         ├── 是 → 不淘汰(WeakReference还在)
         └── 否 → 淘汰进入BitmapPool
    
    触发四:系统内存压力(onTrimMemory/onLowMemory)
      → Glide收到系统内存警告
      → TRIM_MEMORY_MODERATE:清空MemoryCache,保留BitmapPool
      → TRIM_MEMORY_BACKGROUND:清空MemoryCache + BitmapPool
      → TRIM_MEMORY_CRITICAL:释放所有内存缓存
    

    ActiveResources 的双重保护机制:

    ActiveResources为什么需要存在?
    
    问题场景:
    一张图片正在ImageView上显示
      → MemoryCache(LRU)满了
      → LRU算法看到这张图"很久没被访问"(因为已经在ImageView上了)
      → LRU淘汰它
      → 图片从屏幕消失 → 用户体验极差
    
    ActiveResources的解决方案:
    MemoryCache中的Resource被Target引用时
      → 从MemoryCache移除(不再受LRU管辖)
      → 移入ActiveResources(WeakReference持有)
      → 只要ImageView还在用,WeakReference不会回收
      → 即使MemoryCache满了也不会淘汰正在显示的图片
    
    当Target不再使用时:
      → WeakReference被GC回收
      → Resource从ActiveResources移除
      → 重新放入MemoryCache(重新接受LRU管理)
      → 或者如果BitmapPool有空间 → 进入BitmapPool复用
    
    这就是 "两层缓存 + 中间保护区" 的设计精髓:
    ActiveResources 隔离了"正在使用"和"可以被淘汰"两种状态
    

    BitmapPool: 内存回收的最后一道防线:

    Bitmap回收 vs Bitmap复用:
    
    传统做法(无BitmapPool):
    ImageView不再显示 → bitmap.recycle() → GC回收
    下次需要同尺寸Bitmap → 重新分配native内存 → 又是一次malloc
    
    Glide的做法(有BitmapPool):
    ImageView不再显示 → bitmap放入Pool → 标记为"可复用"
    下次需要同尺寸Bitmap → 从Pool获取 → 通过inBitmap复用同一块native内存
                                  → 零分配!
    
    关键代码路径:
    // 释放Bitmap
    public void put(Bitmap bitmap) {
        if (bitmap.isMutable() && bitmap.getAllocationByteCount() <= maxSize) {
            strategy.put(bitmap);  // 按(size+config)分桶存储
        } else {
            bitmap.recycle();      // 不符合复用条件,直接回收
        }
    }
    
    // 获取可复用Bitmap
    public Bitmap get(int width, int height, Bitmap.Config config) {
        int size = width * height * getBytesPerPixel(config);
        // 使用TreeMap查找 >= size 的最小Bitmap(O(logN))
        Bitmap result = strategy.get(width, height, config);
        if (result != null) {
            result.eraseColor(Color.TRANSPARENT);  // 清空内容,保留native内存
        }
        return result;
    }
    

    # 4.10 请求合并与去重的并发设计

    Glide 的请求去重是一个精巧的并发控制设计,确保相同内容的图片在网络层只加载一次:

    请求去重的完整流程:
    
    场景:列表中有3个相同的商品图URL
    
    时间线:
    T1: Item1进入屏幕 → Glide请求URL_A
    T2: Item2进入屏幕 → Glide请求URL_A(与Item1相同)
    T3: Item3进入屏幕 → Glide请求URL_A(与Item1相同)
    
    普通做法(无去重):
      发起3个独立的网络请求 → 下载同一张图片3次 → 浪费200%的流量
      3个请求竞争带宽 → 每个都变慢
    
    Glide的去重做法:
    T1 → Engine.load(key)
         ├── 检查内存缓存 → 未命中
         ├── 检查jobs Map → 没有相同Key的Job
         └── 创建EngineJob + DecodeJob → 提交到线程池
             同时将Job记录到 jobs.put(key, engineJob)
    
    T2 → Engine.load(key)
         ├── 检查内存缓存 → 未命中(还在加载中)
         ├── 检查jobs Map → 找到了!Key相同
         └── engineJob.addCallback(cb)  // 不创建新任务,只添加回调
             Item2的Target注册到Item1的Job上
    
    T3 → 同T2,添加第三个回调
    
    T4: Item1的DecodeJob完成下载+解码
         → EngineJob遍历所有回调:
           ├── 回调Target_1.onResourceReady(resource)
           ├── 回调Target_2.onResourceReady(resource)
           └── 回调Target_3.onResourceReady(resource)
         → jobs.remove(key)  // 从Map中移除
         → 结果缓存到MemoryCache(下次T1直接命中)
    
    核心数据结构:
    class Engine {
        // Key -> 正在执行的Job
        private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
        
        public <R> LoadStatus load(Key key, ...) {
            // 1. 先查内存缓存
            EngineResource<?> memoryResource = loadFromMemory(key);
            if (memoryResource != null) {
                cb.onResourceReady(memoryResource);
                return;  // 内存命中,最快路径
            }
            
            // 2. 查是否有相同Key的Job正在执行
            EngineJob<?> current = jobs.get(key);
            if (current != null) {
                current.addCallback(cb);  // 复用已有Job
                return;                   // 不创建新任务!
            }
            
            // 3. 没有缓存,没有相同Job → 创建新任务
            EngineJob<R> engineJob = new EngineJob<>(key, ...);
            DecodeJob<R> decodeJob = new DecodeJob<>(key, ...);
            jobs.put(key, engineJob);  // 登记到Map
            engineJob.start(decodeJob);
        }
    }
    

    请求去重的三种场景:

    场景 行为 效果
    列表滚动 相同URL被多个ImageView请求 1次下载,N次分发
    预加载+显示 preload()和into()同时请求 共享同一个DecodeJob
    快速切换页面 前一个请求未完成就发起新请求 复用进行中的Job

    默认加载优先级与调度策略:

    请求优先级设计:
    
    Glide的优先级系统:
    enum Priority {
        IMMEDIATE,  // 立即加载
        HIGH,       // 高优先级(当前可见)
        NORMAL,     // 普通优先级
        LOW,        // 低优先级(预加载)
    }
    
    线程池调度策略:
    sourceExecutor(源数据加载线程池)
      → 使用PriorityBlockingQueue
      → 高优先级的DecodeJob优先被线程执行
      → 核心线程数 = CPU核心数(避免过多并发网络请求)
      → 最大线程数 = CPU核心数
    
    列表滚动场景的优先级切换:
    Item进入屏幕 → 请求优先级设为HIGH
    Item滑出屏幕 → 请求优先级降为LOW(或直接取消)
    当前可见Item的HIGH优先级请求 → 优先执行 → 快速显示
    

    # 04a. Glide线程安全与并发控制设计

    # 4a.1 多线程并发访问的挑战

    Glide 作为一个被全局使用的单例,需要在多线程环境下安全地处理缓存读写、Bitmap 复用、请求调度等操作:

    Glide面临的并发场景:
    
    1. 主线程调用with().load().into()
       → 创建RequestManager、注册Target、检查缓存
       → 需要与工作线程的缓存写入操作同步
    
    2. 工作线程执行网络下载
       → 下载完成后写入磁盘缓存和内存缓存
       → 需要与主线程的缓存读取操作同步
    
    3. 多个请求同时操作BitmapPool
       → 线程A归还Bitmap,线程B获取Bitmap
       → 需要保证Pool状态的一致性
    
    4. GC线程回收WeakReference
       → ActiveResources中的WeakReference被回收
       → 需要安全地将Resource重新放入MemoryCache
    

    # 4a.2 ActiveResources的线程安全设计

    ActiveResources的并发控制:
    
    核心设计:ReferenceQueue + 后台清理线程
    
    class ActiveResources {
        // WeakReference关联的ReferenceQueue
        private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue;
        // 活跃资源的Map(需要线程安全)
        private final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
        // 清理线程
        private Thread cleanReferenceQueueThread;
        // volatile保证可见性
        private volatile boolean isShutdown;
        
        // 后台清理线程的逻辑:
        cleanReferenceQueueThread = new Thread(() -> {
            while (!isShutdown) {
                try {
                    // 阻塞等待被GC回收的Resource
                    ResourceWeakReference ref = (ResourceWeakReference) 
                        resourceReferenceQueue.remove();
                    // 被GC回调 → 清理Map中的条目
                    cleanupActiveReference(ref);
                } catch (InterruptedException e) {
                    // 线程被中断,退出循环
                }
            }
        });
        
        // 清理方法(synchronized保证原子性)
        synchronized void cleanupActiveReference(ResourceWeakReference ref) {
            // 从Map中移除已回收的引用
            activeEngineResources.remove(ref.key);
            // 如果Resource还未被释放 → 放回MemoryCache
            if (!ref.resource.isRecycled()) {
                memoryCache.put(ref.key, ref.resource);
            }
        }
        
        // 添加活跃资源(synchronized)
        synchronized void activate(Key key, EngineResource<?> resource) {
            ResourceWeakReference ref = new ResourceWeakReference(
                key, resource, resourceReferenceQueue);
            activeEngineResources.put(key, ref);
        }
        
        // 移除活跃资源(synchronized)
        synchronized void deactivate(Key key) {
            ResourceWeakReference ref = activeEngineResources.remove(key);
            if (ref != null) {
                ref.resource.release();  // 减少引用计数
            }
        }
    }
    

    设计要点:

    • synchronized 保护 activeEngineResources HashMap 的并发访问
    • ReferenceQueue 提供后台非阻塞的回收通知
    • 清理线程使用 remove() 阻塞等待,不占用CPU
    • volatile 保证 isShutdown 的跨线程可见性

    # 4a.3 MemoryCache的线程安全设计

    LruResourceCache的并发控制:
    
    Android的LruCache本身就是线程安全的:
    public class LruCache<K, V> {
        private final LinkedHashMap<K, V> map;  // 非线程安全
        
        public final V get(K key) {
            synchronized (this) {  // 同步块保护
                return map.get(key);
            }
        }
        
        public final V put(K key, V value) {
            synchronized (this) {
                V previous = map.put(key, value);
                trimToSize(maxSize);  // LRU淘汰逻辑
                return previous;
            }
        }
    }
    
    额外保护:BitmapPool淘汰时的回调也做了同步处理
    
    class LruResourceCache extends LruCache<Key, Resource<?>> {
        @Override
        protected void entryRemoved(Key key, Resource<?> resource) {
            // 被LRU淘汰的资源 → 放入BitmapPool
            // 这里的操作在synchronized(this)块中执行
            bitmapPool.put(resource.getBitmap());
        }
    }
    

    # 4a.4 线程池的并发隔离设计

    Glide线程池的分层设计(按工作类型隔离):
    
    为什么需要多个线程池?
    
    单一线程池的问题:
      磁盘IO操作(慢,毫秒级)和网络IO操作(更慢,秒级)
      混在同一个线程池中会互相阻塞
      → 大量磁盘缓存操作占用线程 → 网络下载任务排队等待
      → 用户看到加载慢,但本地缓存明明有图
    
    Glide的线程池分工:
    ┌──────────────────────────────────────────────────────┐
    │ sourceExecutor(源数据加载)                           │
    │ ├── 任务类型:网络下载、从ContentProvider读取          │
    │ ├── 核心线程数:CPU核心数                             │
    │ ├── 队列:PriorityBlockingQueue(优先级排序)          │
    │ └── IO密集 → 线程数无需太多(网络带宽是瓶颈)          │
    ├──────────────────────────────────────────────────────┤
    │ diskCacheExecutor(磁盘缓存读写)                     │
    │ ├── 任务类型:磁盘缓存读取、磁盘缓存写入              │
    │ ├── 核心线程数:1(单线程顺序执行,避免磁盘竞争)      │
    │ ├── 队列:PriorityBlockingQueue                       │
    │ └── 单线程原因:磁盘IO的瓶颈是磁头寻道,多线程反而慢    │
    ├──────────────────────────────────────────────────────┤
    │ animationExecutor(GIF动画解码)                      │
    │ ├── 任务类型:GIF逐帧解码                             │
    │ ├── 核心线程数:0或1(按需创建)                      │
    │ └── GIF解码是CPU密集+持续性的,单独线程避免影响其他     │
    ├──────────────────────────────────────────────────────┤
    │ sourceUnlimitedExecutor(不限并发的源加载)            │
    │ ├── 任务类型:低优先级的预加载                         │
    │ ├── 核心线程数:0                                     │
    │ ├── 最大线程数:Integer.MAX_VALUE                      │
    │ └── 使用SynchronousQueue → 任务立即执行或新建线程     │
    └──────────────────────────────────────────────────────┘
    
    主线程回调:
    EngineJob完成时,通过Handler切换到主线程
      → Target.onResourceReady()在主线程执行
      → ImageView.setImageDrawable()在主线程执行
    

    # 4a.5 BitmapPool的线程安全与无锁设计尝试

    BitmapPool的并发控制演进:
    
    早期(Glide 3.x):使用synchronized锁
      → 频繁的Bitmap获取/归还操作成为性能瓶颈
      → 列表滚动时,锁竞争导致丢帧
    
    后期(Glide 4.x):分段锁 + 无锁化尝试
      SizeConfigStrategy使用GroupedLinkedMap:
      
      class GroupedLinkedMap<K extends Poolable, V> {
          // 使用LinkedEntry组成的双向链表 + HashMap快速查找
          private final LinkedEntry<K, V> head = new LinkedEntry<>();
          private final Map<K, LinkedEntry<K, V>> keyToEntry = new HashMap<>();
          
          public V get(K key) {
              LinkedEntry<K, V> entry = keyToEntry.get(key);
              if (entry == null) {
                  entry = new LinkedEntry<>(key);
                  keyToEntry.put(key, entry);
              } else {
                  // 访问时移到链表头部(LRU语义)
                  makeHead(entry);
              }
              return entry.removeLast();
          }
      }
      
      优化点:
      ├── HashMap O(1) 查找对应尺寸的桶
      ├── 双向链表 O(1) 移除和插入(LRU调整)
      └── GroupedLinkedMap 减少了锁的粒度
    

    # 04b. DecodeJob状态机与责任链的深层设计

    # 4b.1 DecodeJob的运行模型

    DecodeJob 是 Glide 加载管线的核心执行者,它使用阶段式状态机驱动加载流程:

    DecodeJob状态机(Stage枚举):
    
    enum Stage {
        INITIALIZE,      // 初始状态,决定从哪个阶段开始
        RESOURCE_CACHE,  // 读取磁盘上的已变换资源
        DATA_CACHE,      // 读取磁盘上的原始数据
        SOURCE,          // 从数据源获取(网络/文件)
        ENCODE,          // 编码写入磁盘缓存
        FINISHED,        // 完成
    }
    
    状态转换图:
                        ┌─────────────────┐
                        │   INITIALIZE     │
                        │ 决定起始阶段      │
                        └────────┬────────┘
                                 │
                  ┌──────────────┼──────────────┐
                  ▼              ▼              ▼
         ┌────────────┐  ┌────────────┐  ┌────────────┐
         │RESOURCE_CACHE│ │ DATA_CACHE  │ │  SOURCE    │
         │ 磁盘资源缓存  │ │ 磁盘数据缓存  │ │ 网络/文件   │
         └──────┬─────┘  └──────┬─────┘  └──────┬─────┘
                │失败            │失败            │成功
                ▼               ▼               ▼
           下一个阶段        下一个阶段      ┌──────────┐
                                           │  DECODE   │
                                           │ 解码+变换  │
                                           └─────┬────┘
                                                 │
                                            ┌────▼────┐
                                            │  ENCODE  │
                                            │写入磁盘缓存│
                                            └────┬────┘
                                                 │
                                            ┌────▼────┐
                                            │ FINISHED │
                                            │ 回调通知  │
                                            └─────────┘
    
    关键设计:
    1. 每个Stage失败时自动降级到下一个Stage
       → RESOURCE_CACHE未命中 → 尝试DATA_CACHE
       → DATA_CACHE未命中 → 从SOURCE获取
       → 不抛出异常,优雅降级
    
    2. INITIALIZE根据DiskCacheStrategy动态决定起始阶段
       → DiskCacheStrategy.RESOURCE → 跳过DATA_CACHE
       → DiskCacheStrategy.DATA → 跳过RESOURCE_CACHE
       → DiskCacheStrategy.NONE → 直接从SOURCE开始
    
    3. DataFetcherGenerator负责每个Stage的具体执行
       → ResourceCacheGenerator → 从磁盘资源缓存读取
       → DataCacheGenerator → 从磁盘数据缓存读取
       → SourceGenerator → 从数据源获取
    

    # 4b.2 责任链模式在加载管线中的应用

    Glide加载管线是一条完整的责任链:
    
    加载责任链:
    Model(数据源)
      → ModelLoader(数据加载器)—— 将Model转换为Data
      → DataFetcher(数据获取器)—— 从网络/文件读取原始数据
      → ResourceDecoder(资源解码器)—— 将原始Data解码为Resource
      → Transformation(变换器)—— 对Resource应用变换
      → Transcoder(转码器)—— 将Resource转码为Target需要的类型
      → Target(显示目标)—— 显示到ImageView
    
    链式调用的完整示例:
    URL(String)
      → HttpGlideUrlLoader.loadData()
        → HttpUrlFetcher.loadData()         // InputStream
          → StreamBitmapDecoder.decode()     // Bitmap
            → CenterCrop.transform()         // Bitmap(已裁剪)
              → BitmapDrawableTranscoder.transcode()  // Drawable
                → ImageViewTarget.onResourceReady()   // 显示
    
    责任链的扩展点:
    每个环节都可以被替换:
    registry.replace(GlideUrl.class, InputStream.class,
        new OkHttpUrlLoader.Factory(client));       // 替换网络层
    registry.prepend(InputStream.class, Bitmap.class, 
        new MyCustomDecoder());                     // 添加自定义解码器
    registry.register(Bitmap.class, MyResource.class,
        new MyTranscoder());                        // 注册新类型转换
    

    # 4b.3 加载管线的错误恢复机制

    DecodeJob的错误处理:
    
    try/catch的层级设计:
    DecodeJob.run()
      └── runWrapped()
          └── getNextGenerator()
              └── currentGenerator.startNext()
                  └── SourceGenerator.startNext()
                      └── loadData.fetcher.loadData(callback)
                          │
                          ├── onDataReady(data)  // 成功
                          │   └── 解码 → 变换 → 缓存 → 回调
                          │
                          ├── onLoadFailed(exception)  // 失败
                          │   └── 记录异常
                          │   └── 尝试下一个DataFetcher(如果有多个数据源)
                          │   └── 全部失败 → 触发降级
                          │
                          └── 网络异常的特殊处理:
                              └── 如果是网络不可用
                              └── 将异常交给ConnectivityMonitor
                              └── 网络恢复时自动重试
    
    多数据源的容错设计:
    SourceGenerator支持多个数据源的降级:
    loadData.alternateUrls = [originalUrl, cdnUrl, backupUrl]
      → 优先从originalUrl加载
      → 失败 → 尝试CDN
      → 失败 → 尝试备用URL
      → 全部失败 → 回调onLoadFailed
    
    异常信息收集:
    GlideException 收集整条链路上的所有异常:
    class GlideException extends Exception {
        private final List<Exception> causes;  // 保留所有阶段的异常
        private final List<Class<?>> failingClasses;  // 记录了哪个环节失败
        
        // 日志中可以清晰看到:哪个阶段失败了?原因是什么?
        // "Failed to load resource:
        //  - HTTP 404 (source)
        //  - disk cache miss (data_cache)
        //  - disk cache miss (resource_cache)"
    }
    

    # 5.1 建造者模式(Builder Pattern)

    Glide大量使用建造者模式构建复杂对象,这是Glide API设计优秀的关键:

    // RequestBuilder — 链式调用的核心设计
    // 每个方法返回this,支持流畅的链式调用
    Glide.with(context)                      // 返回RequestManager
        .load(url)                           // 返回RequestBuilder<Drawable>
        .placeholder(R.drawable.loading)     // 返回RequestBuilder<Drawable>
        .error(R.drawable.error)             // 返回RequestBuilder<Drawable>
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        .transform(CenterCrop(), RoundedCorners(20))
        .into(imageView);                    // 触发加载
    
    // GlideBuilder — 构建不可变的Glide单例
    // 所有配置在build()之前完成,build()后不可修改
    GlideBuilder builder = new GlideBuilder();
    builder.setMemoryCache(new LruResourceCache(memorySizeBytes));
    builder.setBitmapPool(new LruBitmapPool(bitmapPoolSize));
    builder.setDiskCache(new InternalCacheDiskCacheFactory(context));
    Glide glide = builder.build(context);  // 构建不可变对象
    
    // 设计优势:
    // 1. 参数众多但API简洁,用户不需要一次性设置所有参数
    // 2. 构建出的对象不可变,线程安全
    // 3. 链式调用读起来像自然语言,可读性极佳
    // 4. RequestOptions可以预定义并复用:
    //    static final RequestOptions CIRCLE_OPTIONS = new RequestOptions()
    //        .circleCrop().placeholder(R.drawable.avatar_default);
    //    Glide.with(ctx).load(url).apply(CIRCLE_OPTIONS).into(iv);
    

    # 5.2 工厂模式(Factory Pattern)

    Glide使用工厂模式解耦对象创建和使用:

    // ModelLoaderFactory — 创建数据加载器
    // 每种数据源类型对应一个工厂
    public interface ModelLoaderFactory<Model, Data> {
        ModelLoader<Model, Data> build(MultiModelLoaderFactory multiFactory);
        void teardown();  // 工厂销毁时清理资源
    }
    
    // Registry中的注册机制 — 工厂注册表
    // Glide初始化时注册大量工厂
    registry.append(String.class, InputStream.class, 
        new StringLoader.StreamFactory());
    registry.append(Uri.class, InputStream.class, 
        new HttpUriLoader.Factory());
    registry.append(File.class, InputStream.class, 
        new FileLoader.StreamFactory());
    
    // 运行时根据Model类型查找对应的Factory → 创建ModelLoader
    // 这就是为什么load(String)和load(File)都能工作的原因
    
    // DecoderRegistry同理:
    // 注册不同的ResourceDecoder工厂
    // 根据数据类型(InputStream)和资源类型(Bitmap)找到对应Decoder
    // 支持自定义解码器
    

    # 5.3 策略模式(Strategy Pattern)

    // DiskCacheStrategy — 缓存策略的策略模式典范
    public abstract class DiskCacheStrategy {
        // 是否缓存原始数据
        public abstract boolean isDataCacheable(DataSource dataSource);
        // 是否缓存变换后的资源
        public abstract boolean isResourceCacheable(boolean isFromAlternateCacheKey, 
                                                     DataSource dataSource, 
                                                     EncodeStrategy encodeStrategy);
        // 是否解码已缓存的数据
        public abstract boolean decodeCachedData();
        // 是否解码已缓存的资源
        public abstract boolean decodeCachedResource();
        
        // 预定义的策略实现:
        public static final DiskCacheStrategy ALL = new DiskCacheStrategy() { ... };
        public static final DiskCacheStrategy NONE = new DiskCacheStrategy() { ... };
        public static final DiskCacheStrategy DATA = new DiskCacheStrategy() { ... };
        public static final DiskCacheStrategy RESOURCE = new DiskCacheStrategy() { ... };
        public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() { ... };
    }
    
    // DownsampleStrategy — 下采样策略
    // FIT_CENTER / CENTER_CROP / CENTER_INSIDE / AT_MOST
    // 每种策略的getScaleFactor()计算方式不同
    // 但对调用方(Downsampler)来说接口统一
    
    // 设计优势:
    // 1. 新增策略无需修改已有代码(开闭原则)
    // 2. 策略可以在运行时切换
    // 3. 静态常量方式提供预定义策略,使用方便
    

    # 5.4 观察者模式(Observer Pattern)

    // Target — Glide中最核心的观察者接口
    public interface Target<R> extends LifecycleListener {
        void onLoadStarted(Drawable placeholder);      // 开始加载
        void onResourceReady(R resource, Transition<? super R> transition); // 加载成功
        void onLoadFailed(Drawable errorDrawable);     // 加载失败
        void onLoadCleared(Drawable placeholder);      // 加载被清除
        void getSize(SizeReadyCallback cb);            // 获取目标尺寸
    }
    
    // RequestListener — 更细粒度的观察者
    public interface RequestListener<R> {
        boolean onLoadFailed(GlideException e, Object model, 
                             Target<R> target, boolean isFirstResource);
        boolean onResourceReady(R resource, Object model, 
                               Target<R> target, DataSource dataSource, 
                               boolean isFirstResource);
        // 返回true表示消费事件(不再传递给Target)
    }
    
    // ConnectivityMonitor — 网络状态观察者
    // 当网络恢复时自动重试失败的请求
    interface ConnectivityListener {
        void onConnectivityChanged(boolean isConnected);
    }
    
    // Lifecycle — 生命周期观察
    // RequestManager同时观察生命周期和请求状态
    // 这是观察者模式的多层嵌套应用
    

    # 5.5 享元模式与对象池

    // BitmapPool — Bitmap对象的享元池,Glide性能优化的核心
    public interface BitmapPool {
        void put(Bitmap bitmap);           // 归还Bitmap到池中
        Bitmap get(int width, int height, Bitmap.Config config);  // 获取可复用的Bitmap
        Bitmap getDirty(int width, int height, Bitmap.Config config); // 获取但不清零
    }
    
    // LruBitmapPool的内部实现:
    // 使用SizeConfigStrategy按(size+config)分桶存储Bitmap
    // 查找时找到 >= 所需大小的最小Bitmap
    class SizeConfigStrategy implements LruPoolStrategy {
        // Key = size + config → TreeMap中找到 >= 该key的最小entry
        private final KeyPool keyPool = new KeyPool();  // Key对象也被池化!
        private final GroupedLinkedMap<Key, Bitmap> groupedMap;
        
        @Override
        public Bitmap get(int width, int height, Bitmap.Config config) {
            int size = Util.getBitmapByteSize(width, height, config);
            Key targetKey = keyPool.get(size, config);
            Key bestKey = findBestKey(targetKey, size, config);
            Bitmap result = groupedMap.get(bestKey);
            // ...
        }
    }
    
    // FactoryPools — 通用对象池
    // 使用obtain()/recycle()模式(类似Android的Message)
    class FactoryPools {
        static <T extends Poolable> Pool<T> simple(int size, Factory<T> factory) {
            return new FactoryPool<>(size, factory);
        }
    }
    
    // SingleRequest使用对象池复用
    static <R> SingleRequest<R> obtain(...) {
        SingleRequest<R> request = (SingleRequest<R>) POOL.acquire();
        if (request == null) {
            request = new SingleRequest<>();
        }
        request.init(...);
        return request;
    }
    
    void recycle() {
        // 重置所有字段
        POOL.release(this);
    }
    
    // 设计精髓:
    // 1. BitmapPool按size分桶,O(logN)查找最佳匹配
    // 2. Key对象也做了池化(KeyPool),极致的内存优化
    // 3. getDirty()不清零Bitmap数据,比get()更快(反正要覆盖)
    // 4. GroupedLinkedMap结合了HashMap的O(1)查找和LRU淘汰
    

    # 5.6 Registry注册机制的设计

    Registry — Glide的核心扩展机制(开闭原则的极致体现)
    
    Registry是Glide扩展性的核心,它管理了所有组件的注册关系:
    
    ┌─────────────────────────────────────────────────┐
    │                    Registry                      │
    │                                                  │
    │  ModelLoaderRegistry                             │
    │    Model → Data 的映射                           │
    │    String → InputStream (HttpGlideUrlLoader)     │
    │    File → InputStream (FileLoader)               │
    │    Uri → InputStream (UriLoader)                 │
    │                                                  │
    │  ResourceDecoderRegistry                         │
    │    Data → Resource 的映射                        │
    │    InputStream → Bitmap (StreamBitmapDecoder)     │
    │    ByteBuffer → Bitmap (ByteBufferBitmapDecoder) │
    │    InputStream → GifDrawable (StreamGifDecoder)   │
    │                                                  │
    │  TranscoderRegistry                              │
    │    Resource → Target 的映射                      │
    │    Bitmap → Drawable (BitmapDrawableTranscoder)   │
    │    GifDrawable → Drawable (直接使用)              │
    │                                                  │
    │  EncoderRegistry                                 │
    │    Resource/Data → Encode 的映射                 │
    │    Bitmap → File (BitmapEncoder)                 │
    │    InputStream → File (StreamEncoder)            │
    └─────────────────────────────────────────────────┘
    
    扩展示例 — 自定义SVG解码器:
    registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
            .append(InputStream.class, SVG.class, new SvgDecoder());
    
    扩展示例 — 替换HTTP组件为OkHttp:
    registry.replace(GlideUrl.class, InputStream.class, 
        new OkHttpUrlLoader.Factory(okHttpClient));
    
    Registry的三个注册方法:
    ├── append():添加到末尾(低优先级)
    ├── prepend():添加到开头(高优先级)
    └── replace():替换已有的同类型注册
    
    设计精髓:
    1. 完全解耦了数据加载、解码、编码的各个环节
    2. 每个环节都可以独立替换和扩展
    3. 支持链式转换:Model→Data→Resource→Transcode
    4. append/prepend控制优先级,replace替换默认实现
    5. 这就是为什么Glide可以支持GIF、SVG、视频帧等各种格式
    

    # 5.7 Engine调度的生产者-消费者设计

    Engine — Glide的加载引擎,核心调度设计
    
    Engine内部的线程调度:
    
    ┌──────────────────────────────────────────┐
    │              Engine(主调度器)            │
    │                                          │
    │  load()                                  │
    │  ├── 检查ActiveResources(WeakRef缓存)  │
    │  ├── 检查MemoryCache(LRU缓存)          │
    │  └── 创建EngineJob + DecodeJob           │
    │       └── 提交到线程池                    │
    └──────────┬───────────────────────────────┘
               │
    ┌──────────▼───────────────────────────────┐
    │  EngineJob(任务管理器,主线程回调桥梁)    │
    │  ├── 管理DecodeJob的执行                  │
    │  ├── 管理回调Target列表                   │
    │  ├── 支持多个Target等待同一资源            │
    │  └── 完成时切换到主线程回调               │
    └──────────┬───────────────────────────────┘
               │
    ┌──────────▼───────────────────────────────┐
    │  DecodeJob(实际工作者,工作线程执行)      │
    │  实现Runnable接口                         │
    │  ├── Stage.RESOURCE_CACHE → 读磁盘资源缓存 │
    │  ├── Stage.DATA_CACHE → 读磁盘数据缓存    │
    │  ├── Stage.SOURCE → 从数据源获取          │
    │  │    └── DataFetcher.loadData()          │
    │  │         └── 网络下载/文件读取           │
    │  └── Stage.ENCODE → 编码写入磁盘缓存      │
    │                                           │
    │  状态机设计:                              │
    │  每个Stage对应一个DataFetcherGenerator     │
    │  当前Stage失败 → 自动切换到下一个Stage    │
    │  所有Stage都失败 → 回调onLoadFailed        │
    └───────────────────────────────────────────┘
    
    线程池设计:
    ├── sourceExecutor:从数据源加载的线程池(网络IO密集)
    ├── diskCacheExecutor:磁盘缓存读写的线程池
    ├── animationExecutor:GIF动画解码的线程池
    └── sourceUnlimitedExecutor:不限并发的源数据加载线程池
    
    请求去重设计(非常关键):
      同一个Key的请求只会执行一次:
      EngineJob activeJob = jobs.get(key);
      if (activeJob != null) {
          activeJob.addCallback(cb);  // 复用已有Job
          return;
      }
      // 没有相同Key的Job → 创建新Job
    
    设计模式在Glide中的协作:
    
    用户调用 Glide.with().load().into()
      ↓
    建造者模式 → 构建RequestBuilder、RequestOptions
      ↓
    工厂模式 → Registry查找ModelLoader、Decoder
      ↓
    策略模式 → 选择缓存策略、采样策略
      ↓
    享元模式 → 从BitmapPool获取可复用Bitmap
      ↓
    观察者模式 → Target接收加载结果
    
    责任链模式(类似):
    DecodeJob按阶段执行:
      RESOURCE_CACHE → DATA_CACHE → SOURCE
      每个阶段独立处理,失败则交给下一阶段
    

    # 06.如何实现加载速度监控

    # 6.1 加载速度监控背景

    使用Glide加载图片虽然简单,但无法得知当前图片的下载进度。如果图片很小还好,但对于大图用户等待时间长却看不到任何反馈,体验很差。

    # 6.2 加载速度思路分析

    Glide内部HTTP通讯组件的底层实现基于HttpUrlConnection,可扩展性有限。因此可以将Glide的HTTP通讯替换为OkHttp,利用OkHttp强大的拦截器机制来捕获下载过程并计算进度。

    # 6.3 替换通信组件

    // 1. 新建OkHttpFetcher实现DataFetcher接口
    // 2. 新建OkHttpGlideUrlLoader实现ModelLoader接口
    // 3. 在GlideModule中注册替换
    
    @GlideModule
    public class ImageGlideModule extends AppGlideModule {
        @Override
        public void registerComponents(Context context, Glide glide, Registry registry) {
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(new ProgressInterceptor())
                    .build();
            OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(client);
            registry.replace(GlideUrl.class, InputStream.class, factory);
        }
    }
    

    # 6.4 添加拦截器和监听

    public class ProgressInterceptor implements Interceptor {
        static final Map<String, ProgressListener> LISTENER_MAP = new HashMap<>();
        
        public static void addListener(String url, ProgressListener listener) {
            LISTENER_MAP.put(url, listener);
        }
        
        public static void removeListener(String url) {
            LISTENER_MAP.remove(url);
        }
        
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
            String url = request.url().toString();
            ResponseBody body = response.body();
            return response.newBuilder()
                    .body(new ProgressResponseBody(url, body))
                    .build();
        }
    }
    

    # 6.5 回调和计算加载速度

    // ProgressResponseBody包装原始ResponseBody
    public class ProgressResponseBody extends ResponseBody {
        private String url;
        private ResponseBody delegate;
        private ProgressListener listener;
        private BufferedSource bufferedSource;
        
        public ProgressResponseBody(String url, ResponseBody delegate) {
            this.url = url;
            this.delegate = delegate;
            this.listener = ProgressInterceptor.LISTENER_MAP.get(url);
        }
        
        @Override
        public BufferedSource source() {
            if (bufferedSource == null) {
                bufferedSource = Okio.buffer(new ForwardingSource(delegate.source()) {
                    long totalBytesRead = 0;
                    long lastProgress = 0;
                    
                    @Override
                    public long read(Buffer sink, long byteCount) throws IOException {
                        long bytesRead = super.read(sink, byteCount);
                        totalBytesRead += (bytesRead != -1 ? bytesRead : 0);
                        long contentLength = contentLength();
                        int progress = (int) (totalBytesRead * 100 / contentLength);
                        
                        if (listener != null && progress != lastProgress) {
                            lastProgress = progress;
                            listener.onProgress(progress);
                        }
                        
                        if (bytesRead == -1) {
                            listener.onProgress(100); // 完成
                        }
                        return bytesRead;
                    }
                });
            }
            return bufferedSource;
        }
    }
    
    // 使用方式
    ProgressInterceptor.addListener(imageUrl, progress -> {
        // progress: 0-100
        progressBar.setProgress(progress);
    });
    
    Glide.with(this)
        .load(imageUrl)
        .listener(new RequestListener<Drawable>() {
            @Override
            public boolean onResourceReady(...) {
                ProgressInterceptor.removeListener(imageUrl);
                return false;
            }
        })
        .into(imageView);
    

    # 06a. 缩略图加载与渐进式显示设计

    # 6a.1 缩略图机制的设计原理

    Glide 的缩略图功能不是简单的"先显示小图再替换大图",而是一套精巧的双请求管线设计:

    缩略图加载的两种模式:
    
    模式一:thumbnail(float sizeMultiplier)
      原理:用同一个URL,但以更小的采样率先加载一张低分辨率版本
      Glide.with(context)
          .load(url)
          .thumbnail(0.25f)   // 先加载0.25倍采样率的缩略图
          .into(imageView)
    
      内部实现:
      RequestBuilder.thumbnail(0.25f) 做了什么?
        → 创建第二个RequestBuilder,share同一个model(url)
        → 设置sizeMultiplier = 0.25(覆盖宽高为原来的1/4)
        → 设置优先级为IMMEDIATE(缩略图优先)
        → 两个Request同时发起
    
      加载时序:
      T0: 发起两个Request:
          Request_Thumb: priority=IMMEDIATE, sizeMultiplier=0.25
          Request_Full:  priority=NORMAL,   sizeMultiplier=1.0
      
      T1: Request_Thumb先完成(采样率高,解码快)
          → 显示模糊缩略图(200x200 ImageView → 50x50采样)
      
      T2: Request_Full完成
          → 动画替换为清晰全尺寸图
          → CrossFade过渡,无缝切换
    
    模式二:thumbnail(RequestBuilder thumbnailRequest)
      原理:用低分辨率URL先加载,再用高分辨率URL替换
      Glide.with(context)
          .load(highResUrl)
          .thumbnail(
              Glide.with(context).load(lowResUrl)  // 不同的URL!
          )
          .into(imageView)
    
      适用场景:
      ├── 服务器有两套图(thumb_200.jpg / original_1000.jpg)
      ├── thumb图秒加载,original按需下载
      └── 节省流量:用户快速滑动时只加载thumb
    
    缩略图与主图的协调机制:
    class RequestBuilder {
        private RequestBuilder<TranscodeType> thumbBuilder;
        private float thumbSizeMultiplier;
        
        // 缩略图请求完成时
        onThumbComplete() {
            if (mainRequest.isComplete()) {
                return;  // 主图也完成了,忽略缩略图
            }
            target.setImageDrawable(thumbDrawable);  // 先显示缩略图
        }
        
        // 主图请求完成时
        onMainComplete() {
            target.setImageDrawable(mainDrawable);   // 替换为高清图
            // 带有CrossFade动画,用户感知不到跳变
        }
    }
    

    # 6a.2 预加载(preload)的设计原理

    preload()的设计意图:
    
    典型场景:
      用户正在浏览商品列表 → 当前在看第1-3个商品
      → 但用户很可能继续往下滑
      → 提前加载第4-6个商品的大图
      → 用户滑到时图片已在缓存中 → 秒开
    
    API设计:
    Glide.with(context)
        .load(url)
        .preload()          // 只下载+缓存,不显示
        .override(width, height);  // 预加载到指定尺寸
    
    preload()内部实现:
    public Target<TranscodeType> preload(int width, int height) {
        // 创建PreloadTarget → 一个不设置到ImageView的Target
        final PreloadTarget<TranscodeType> target = 
            PreloadTarget.obtain(width, height);
        return into(target);
    }
    
    class PreloadTarget<Z> extends SimpleTarget<Z> {
        @Override
        public void onResourceReady(Z resource, Transition<? super Z> transition) {
            // 什么都不做!资源已经被缓存,等待正式的into()调用时命中缓存
            // 然后立即回收这个Target
            Glide.get(context).clear(this);
        }
    }
    
    预加载策略:
    1. 列表滚动监听 → 提前预加载下3-5个Item
    2. ViewPager页面切换 → 预加载相邻页面
    3. 详情页面的多张图片 → 先预加载,用户切换Tab时秒开
    
    注意事项:
    ├── 预加载使用LOW优先级 → 不影响当前可见图片的加载
    ├── 页面销毁时记得取消预加载 → 避免浪费资源
    └── 预加载不宜过多 → 占用带宽影响当前请求
    

    # 06b. 列表滚动性能优化全景

    # 6b.1 RecyclerView中的Glide优化策略

    列表滚动场景的性能挑战:
    
    用户快速滑动列表时:
      每秒经过10-20个Item
      → 每个Item都触发图片加载请求
      → 如果处理不当:
        ├── 大量网络请求并发 → 带宽竞争
        ├── 频繁创建Bitmap → 内存抖动 + GC卡顿
        ├── ImageView复用 → 图片错位
        └── 滚出屏幕的请求未被取消 → 浪费资源
    
    Glide的列表优化方案:
    
    1. 请求自动取消(ImageViwe复用检测)
       into(ImageView)时:
         → 获取ImageView上当前的Request
         → 如果有旧Request → request.clear()
         → 清除旧Request的回调和Target
         → 防止旧请求的结果设置到这个已被复用的ImageView上
    
       关键代码逻辑:
       class RequestBuilder {
           private <Z> Target<Z> into(ImageView imageView) {
               // 获取ImageView上绑定的上一个Request
               Request previousRequest = getRequest(imageView);
               if (previousRequest != null) {
                   previousRequest.clear();  // 取消旧请求!
                   setRequest(imageView, null);
               }
               // 创建新Request并绑定
               Request newRequest = buildRequest();
               setRequest(imageView, newRequest);
               newRequest.begin();
           }
       }
    
    2. 生命周期感知的请求暂停/恢复
       结合Fragment/Activity生命周期:
       → 列表所在的Fragment.onStop() → 暂停所有图片请求
       → 列表所在的Fragment.onStart() → 恢复已暂停的请求
       → Fragment.onDestroy() → 取消所有请求
       
       这确保了:
       ├── 用户按Home键 → 不再浪费带宽加载看不见的图片
       ├── 返回App → 自动恢复加载
       └── 页面退出 → 彻底清理
    
    3. 缩略图 + 主图的双层加载
       先显示模糊缩略图 → 再替换为清晰大图:
       Glide.with(context)
           .load(url)
           .thumbnail(0.1f)  // 10%采样率先显示
           .into(imageView);
       → 用户看到图片"从模糊变清晰"的过程
       → 比"从空白变清晰"的体验好很多
    
    4. RecyclerView预加载配合
       RecyclerView + LinearLayoutManager的预取机制:
       RecyclerView.LayoutManager.setItemPrefetchEnabled(true);
       → Glide配合GapStrategy进行适当的预取
       → 不使用preload(),而是将下一个请求优先级设为LOW
       → 当前可见的请求仍然是HIGH优先级
    

    # 6b.2 快速滑动时的请求抑制策略

    场景:用户以极快速度滑动列表
    
    传统做法的问题:
      → 每秒发起20个网络请求
      → 每个请求在用户滑过后才完成
      → 完成了也显示不到屏幕上(因为已滑走了)
      → 浪费大量流量和资源
    
    Glide的解决方案:结合RecyclerView的滑动状态
    
    RecyclerView.OnScrollListener {
        onScrollStateChanged(state) {
            when (state) {
                SCROLL_STATE_IDLE:
                    // 停止滑动 → 恢复所有请求,加载当前可见图片
                    Glide.with(context).resumeRequests();
                    
                SCROLL_STATE_DRAGGING:
                    // 手指拖动中 → 正常加载
                    // Glide默认行为:可见Item立即加载
                    
                SCROLL_STATE_SETTLING:
                    // 惯性滑动中(手指已离开屏幕但列表还在滑)
                    // → 暂停加载!等列表停下
                    Glide.with(context).pauseRequests();
                    // 列表停下后会触发SCROLL_STATE_IDLE → 恢复加载
            }
        }
    }
    
    效果对比:
    无抑制策略:
      快速滑动100个Item → 发起100个请求
      → 80个请求的结果被浪费(用户已滑过)
      → 浪费80%的流量
    
    有抑制策略:
      快速滑动中暂停 → 不发起请求
      → 停止时只加载可见的5个Item
      → 节省95%的请求量
    

    # 7.1 经典面试题

    问题1:Glide的缓存机制是怎样的?

    Glide四级缓存:
    
    1. ActiveResources(活跃资源)
       └── WeakReference持有当前正在使用的资源
       └── 防止被LRU淘汰
    
    2. MemoryCache(LRU内存缓存)
       └── LruResourceCache,固定大小
       └── 被淘汰的Bitmap进入BitmapPool
    
    3. ResourceCache(磁盘资源缓存)
       └── 缓存变换后的图片
       └── 可直接使用,速度较快
    
    4. DataCache(磁盘数据缓存)
       └── 缓存原始数据
       └── 需要重新解码,但节省网络请求
    
    读取顺序: 1 → 2 → 3 → 4 → 网络
    

    问题2:Glide如何绑定生命周期?

    核心原理:空白Fragment + Lifecycle
    
    1. 调用Glide.with(activity)时
       向Activity中添加一个不可见的Fragment(SupportRequestManagerFragment)
    
    2. Fragment自动跟随Activity的生命周期:
       onStart → 恢复暂停的请求
       onStop → 暂停正在执行的请求
       onDestroy → 取消所有请求,清理资源
    
    3. RequestManager注册为Fragment的LifecycleListener
       → Fragment生命周期变化时通知RequestManager
       → RequestManager管理所有Request的状态
    

    # 7.2 进阶面试题

    问题3:Glide的缓存Key是如何设计的?

    EngineKey包含多个维度:
    model + signature + width + height + transformations + 
    resourceClass + transcodeClass + options
    
    原因:
    同一URL的图片,在不同尺寸ImageView中需要不同的缓存
    同一URL的图片,不同变换(圆角/圆形)需要不同的缓存
    

    问题4:BitmapPool是什么?为什么需要它?

    BitmapPool是Bitmap对象的复用池:
    
    作用:
      复用不再使用的Bitmap对象
      避免频繁创建新Bitmap导致的内存抖动和GC
    
    原理:
      1. Bitmap不再使用时放入Pool(而非直接回收)
      2. 需要新Bitmap时先从Pool查找合适的
      3. 通过BitmapFactory.Options.inBitmap实现复用
      4. Android 4.4+只要求被复用Bitmap大小 >= 新Bitmap大小
    
    效果:
      列表滚动100张图片
      无BitmapPool: 频繁GC,内存抖动
      有BitmapPool: 几乎零GC,内存平稳
    

    # 7.3 性能相关面试题

    问题5:Glide如何避免OOM?

    Glide防OOM的多层策略:
    
    1. 下采样:根据ImageView尺寸计算inSampleSize,不加载原始大图
    2. RGB_565:默认使用RGB_565格式,比ARGB_8888节省一半内存
    3. LRU缓存:内存缓存有大小限制,超出自动淘汰
    4. BitmapPool复用:减少内存峰值
    5. 生命周期管理:页面销毁时自动释放资源
    6. 内存修剪:收到系统内存不足通知时主动释放缓存
    

    问题6:列表滚动时如何保证Glide加载性能?

    Glide列表优化策略:
    
    1. 请求取消
       └── ImageView被复用时,自动取消旧请求
       └── 避免旧请求结果导致图片错位
    
    2. 缓存命中
       └── 多级缓存机制保证已加载图片秒显
       └── ActiveResources确保正在显示的图片不被淘汰
    
    3. BitmapPool复用
       └── 列表项的ImageView尺寸通常相同
       └── 复用率极高,几乎不需要新建Bitmap
    
    4. 请求优先级
       └── 可以为不同请求设置优先级
       └── 当前可见的图片优先加载
    
    5. 预加载
       └── RecyclerView可配合Glide预加载机制
       └── 提前加载即将显示的图片
    

    # 参考博客

    • Glide你为何如此秀?
      • https://mp.weixin.qq.com/s/pAazRD9NaLPvBseG51ZOLw
      • https://juejin.cn/post/6844904049595121672
    上次更新: 2026/07/06, 10:11:57
    LeakCanary内存收集
    OkHttp网络框架设计

    ← LeakCanary内存收集 OkHttp网络框架设计→

    最近更新
    01
    audit
    07-27
    02
    C++入门教程全章思考题汇编
    07-24
    03
    12.技术团队建设能力
    07-21
    更多文章>
    Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式