编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

    • 库的解读

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

      • 智能硬件

    • iOS开发和进阶

    • Web开发和进阶

    • Linux应用开发

    • 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 加载进度监听思考
    • 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 回调和计算加载速度
    • 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
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # 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等)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    # 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:网络状态监听
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    # 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签名
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 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. 回调获取结果或错误
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 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()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    # 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:不使用磁盘缓存
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    疑惑:为什么需要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进行精确缩放
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    # 2.8 图片显示设计

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

    图片显示流程:
    
    DecodeJob完成解码
      ↓
    EngineJob回调onResourceReady()
      ↓
    切换到主线程(通过Handler)
      ↓
    Target.onResourceReady(resource, transition)
      ├── ImageViewTarget:
      │   ├── 设置Drawable到ImageView
      │   ├── 执行过渡动画(如CrossFade淡入)
      │   └── 通知请求完成
      └── CustomTarget/ViewTarget等其他Target
    
    过渡动画类型:
    ├── NoTransition:无动画
    ├── DrawableCrossFadeTransition:淡入淡出
    ├── GenericTransitionOptions:自定义过渡
    └── withCrossFade(duration):指定淡入时长
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 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
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 03.Glide原理思考

    # 3.1 要思考一些问题

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

    # 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加载引擎
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    # 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;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    为什么在非主线程时使用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;  // 返回自身,支持链式调用
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    # 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)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    # 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尺寸不同。
          如果共享同一个缓存,可能导致图片模糊或内存浪费。
          因此不同尺寸需要不同的缓存。
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31

    # 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)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    # 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,按顺序执行
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

    # 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()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    # 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中
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 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           // 加载选项
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # 4.5 LruCache淘汰策略

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

    # 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 → 重新解码(不复用)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    # 4.7 加载进度监听思考

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

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

    # 05.Glide优秀的设计模式

    # 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);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    # 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
    // 支持自定义解码器
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    # 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. 静态常量方式提供预定义策略,使用方便
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    # 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同时观察生命周期和请求状态
    // 这是观察者模式的多层嵌套应用
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    # 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淘汰
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53

    # 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、视频帧等各种格式
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49

    # 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
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    设计模式在Glide中的协作:
    
    用户调用 Glide.with().load().into()
      ↓
    建造者模式 → 构建RequestBuilder、RequestOptions
      ↓
    工厂模式 → Registry查找ModelLoader、Decoder
      ↓
    策略模式 → 选择缓存策略、采样策略
      ↓
    享元模式 → 从BitmapPool获取可复用Bitmap
      ↓
    观察者模式 → Target接收加载结果
    
    责任链模式(类似):
    DecodeJob按阶段执行:
      RESOURCE_CACHE → DATA_CACHE → SOURCE
      每个阶段独立处理,失败则交给下一阶段
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    # 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);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 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();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    # 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);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59

    # 07.面试高频问题深度解析

    # 7.1 经典面试题

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

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

    问题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的状态
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    # 7.2 进阶面试题

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

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

    问题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,内存平稳
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    # 7.3 性能相关面试题

    问题5:Glide如何避免OOM?

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

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

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

    # 参考博客

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

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

    最近更新
    01
    信号崩溃快速排查
    06-15
    02
    CoreDump破案
    06-15
    03
    perf火焰图实战
    06-15
    更多文章>
    Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式