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 |
| 大图加载 | 一般 | 一般 | 优秀 |
2
3
4
5
6
7
8
9
10
11
# 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显示图片
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等)
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:网络状态监听
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签名
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. 回调获取结果或错误
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()
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:不使用磁盘缓存
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进行精确缩放
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):指定淡入时长
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
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. 线程是如何调度的?哪些在主线程,哪些在工作线程?
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加载引擎
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;
}
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; // 返回自身,支持链式调用
}
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)
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尺寸不同。
如果共享同一个缓存,可能导致图片模糊或内存浪费。
因此不同尺寸需要不同的缓存。
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)
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,按顺序执行
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资源,还可能导致:
- 回调中访问已销毁的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()
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中
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 // 加载选项
}
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压力)
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 → 重新解码(不复用)
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. 通过回调通知上层
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);
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
// 支持自定义解码器
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. 静态常量方式提供预定义策略,使用方便
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同时观察生命周期和请求状态
// 这是观察者模式的多层嵌套应用
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淘汰
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、视频帧等各种格式
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
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
每个阶段独立处理,失败则交给下一阶段
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);
}
}
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();
}
}
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);
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 → 网络
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的状态
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的图片,不同变换(圆角/圆形)需要不同的缓存
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,内存平稳
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. 内存修剪:收到系统内存不足通知时主动释放缓存
2
3
4
5
6
7
8
问题6:列表滚动时如何保证Glide加载性能?
Glide列表优化策略:
1. 请求取消
└── ImageView被复用时,自动取消旧请求
└── 避免旧请求结果导致图片错位
2. 缓存命中
└── 多级缓存机制保证已加载图片秒显
└── ActiveResources确保正在显示的图片不被淘汰
3. BitmapPool复用
└── 列表项的ImageView尺寸通常相同
└── 复用率极高,几乎不需要新建Bitmap
4. 请求优先级
└── 可以为不同请求设置优先级
└── 当前可见的图片优先加载
5. 预加载
└── RecyclerView可配合Glide预加载机制
└── 提前加载即将显示的图片
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