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 设计的目标
如果让你来设计一个图片加载框架,核心目标应该包括:
- 高效加载:利用多级缓存(内存→磁盘→网络)减少重复加载,使用采样率和下采样减少内存占用
- 简洁API:通过链式调用简化使用,如
Glide.with(context).load(url).into(imageView) - 生命周期感知:自动绑定Activity/Fragment的生命周期,页面销毁时自动取消请求
- 灵活配置:支持自定义缓存策略、图片变换、网络组件、解码格式等
- 内存安全:通过BitmapPool复用、适当的图片格式和采样率控制内存使用
- 强大扩展性:通过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资源,还可能导致:
- 回调中访问已销毁的View → Crash
- 加载结果设置到已复用的ImageView → 图片错位
- 内存无法及时释放 → 内存增长
# 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保护activeEngineResourcesHashMap 的并发访问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