图片加载:Glide
使用 Glide 完成网络图片加载、缓存策略、变换、RecyclerView 列表场景与生命周期绑定,并简要对比 Coil 等现代化方案。
学习目标
- 掌握 Glide 的基本 API:load/into/placeholder
- 理解内存缓存、磁盘缓存策略与配置
- 能够使用 transform / circleCrop 完成图片变换
- 学会在 RecyclerView 列表场景正确使用 Glide
- 理解 Glide 与 Activity/Fragment 生命周期的自动绑定
- 了解 Coil 等现代化方案并对比差异
学习目标
- 掌握 Glide 的基本 API:
load / into / placeholder / error。 - 理解内存缓存、磁盘缓存策略与自定义配置。
- 能够使用
transform / circleCrop / override完成图片变换。 - 学会在 RecyclerView 列表场景正确使用 Glide,避免错位与闪烁。
- 理解 Glide 与 Activity / Fragment 生命周期的自动绑定。
- 了解 Coil 等现代化方案并对比差异。
Glide 图片加载实战
1. 引入依赖
android {
compileSdk 34
defaultConfig {
minSdk 21
}
}
dependencies {
implementation 'com.github.bumptech.glide:glide:4.16.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
// 可选:集成 OkHttp 作为网络栈(推荐与 Retrofit 共用栈)
implementation 'com.github.bumptech.glide:okhttp3-integration:4.16.0'
}
注:Glide 4.16 支持 Java 8,无需特殊配置。如需使用 Generated API(
GlideApp),需annotationProcessor生成本地GlideRequestOptions等。
2. 基本用法
// 网络图片
Glide.with(context)
.load("https://example.com/avatar.png")
.placeholder(R.drawable.ic_avatar_placeholder) // 加载中占位图
.error(R.drawable.ic_avatar_error) // 加载失败图
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) // 缓存策略
.override(200, 200) // 指定目标尺寸
.into(imageView);
// 本地资源
Glide.with(context)
.load(R.drawable.ic_logo)
.into(imageView);
// File 文件
Glide.with(context)
.load(new File(filePath))
.into(imageView);
// 加载为 Bitmap 自定义处理
Glide.with(context)
.asBitmap()
.load("https://example.com/p.png")
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource,
Transition<? super Bitmap> transition) {
// 在 Canvas 上做自定义绘制
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {}
});
3. 缓存策略
Glide 默认开启「内存 + 磁盘」两级缓存,关键参数:
- 内存缓存:以
Key(width, height, transform)为键,相同尺寸才命中。 - 磁盘缓存:
DiskCacheStrategy控制缓存原始数据还是转换后数据。
| 策略 | 含义 |
|---|---|
AUTOMATIC |
默认:远程图片缓存原始 + 转换后;本地图片不缓存 |
ALL |
缓存原始数据 + 转换后数据 |
DATA |
只缓存原始数据(解码前) |
RESOURCE |
只缓存转换后数据(解码后) |
NONE |
不写磁盘缓存,但仍读已有缓存 |
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(false) // true 关闭内存缓存
.into(imageView);
// 关闭内存缓存仅当前请求
Glide.with(context)
.load(url)
.skipMemoryCache(true)
.into(imageView);
4. 变换 transform
Glide 提供链式变换 API,可叠加多个:
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
// 圆角
Glide.with(context)
.load(url)
.transform(new RoundedCornersTransformation(16, 0))
.into(imageView);
// 圆形头像
Glide.with(context)
.load(url)
.circleCrop()
.into(imageView);
// 缩略图 + 主图(先显示低分辨率缩略图,主图加载完后渐变切换)
Glide.with(context)
.load(url)
.thumbnail(0.1f)
.into(imageView);
// 高斯模糊
Glide.with(context)
.load(url)
.transform(new BlurTransformation(25))
.into(imageView);
// 多变换叠加
Glide.with(context)
.load(url)
.transform(
new CircleCrop(),
new GrayscaleTransformation())
.into(imageView);
5. 过渡动画
Glide.with(context)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade(300)) // 淡入 300ms
.into(imageView);
Glide 4.x 默认关闭过渡动画以避免闪烁。如需要平滑切换,显式声明
withCrossFade。
6. RecyclerView 列表场景
RecyclerView 复用 ViewHolder 是图片错位的常见原因:上一次加载完成的图片被错误地显示在新的 item 上。正确写法:
public class AvatarAdapter extends RecyclerView.Adapter<AvatarAdapter.VH> {
private final List<String> urls = new ArrayList<>();
private final Context context;
public AvatarAdapter(Context ctx) { this.context = ctx.getApplicationContext(); }
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new VH(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_avatar, parent, false));
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
String url = urls.get(position);
// 1. 复用前先清除旧图,避免短暂闪烁
Glide.with(context).clear(holder.avatar);
// 2. 设置占位
holder.avatar.setImageResource(R.drawable.ic_avatar_placeholder);
// 3. 重新加载
Glide.with(context)
.load(url)
.placeholder(R.drawable.ic_avatar_placeholder)
.error(R.drawable.ic_avatar_error)
.circleCrop()
.into(holder.avatar);
}
@Override
public int getItemCount() { return urls.size(); }
static class VH extends RecyclerView.ViewHolder {
ImageView avatar;
VH(@NonNull View v) {
super(v);
avatar = v.findViewById(R.id.avatar);
}
}
}
要点:
- 使用
Glide.with(context)传入ApplicationContext或 Activity;with(holder)会自动跟随 ViewHolder 的生命周期。 onBindViewHolder中先clear(holder.avatar)再load(...).into(),避免旧请求结果覆盖新占位图。- 复用场景设置固定尺寸或
override(),避免重复解码不同尺寸的图片。 - 使用
setHasFixedSize(true)配合固定高度 item 提升性能。
7. 与 Fragment / Activity 生命周期绑定
Glide 内部通过 RequestManager 监听 Activity / Fragment 生命周期:
onStop时暂停所有请求。onDestroy时自动取消请求,释放资源。with(view)会向上查找 view 所在的 Fragment / Activity,自动绑定。
// 自动绑定 Activity 生命周期
Glide.with(activity).load(url).into(imageView);
// 自动绑定 Fragment 生命周期
Glide.with(fragment).load(url).into(imageView);
// 通过 View 自动查找其 Fragment 父级
Glide.with(imageView).load(url).into(imageView);
// Application 级别:生命周期不可控,仅用于无 UI 场景
Glide.with(context.getApplicationContext()).load(url).into(imageView);
警告:
with(applicationContext)不会绑定生命周期,请求会一直运行到完成或被手动clear。仅用于后台 / Widget 等无 UI 场景。
8. 自定义 GlideModule 与配置
Java 项目通过 @GlideModule 注解创建 AppGlideModule,启用 Generated API 后可使用 GlideApp:
package com.example.image;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {
// 空实现即可;如需自定义配置可覆盖 applyOptions
}
启用 Generated API 后:
GlideApp.with(context)
.load(url)
.placeholder(R.drawable.placeholder)
.circleCrop()
.into(imageView);
自定义 OkHttp 栈(与 Retrofit 共享):
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context,
@NonNull Glide glide,
@NonNull Registry registry) {
// 用项目单例 OkHttp 替换默认网络栈
OkHttpClient ok = OkHttpFactory.client(context);
registry.replace(GlideUrl.class, InputStream.class,
new com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(ok));
}
@Override
public void applyOptions(@NonNull Context context,
@NonNull GlideBuilder builder) {
// 设置磁盘缓存大小 50MB
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024));
// 内存缓存默认 bitmap pool 大小
builder.setMemoryCache(new LruResourceCache(20 * 1024 * 1024));
}
}
配置后必须在
proguard-rules.pro中保留@GlideModule子类,避免混淆导致模块未注册。
9. 预加载与下载
// 预加载到磁盘缓存
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.preload();
// 仅下载到文件,不渲染到 View
Glide.with(context)
.asFile()
.load(url)
.into(new FileTarget() {
@Override
public void onResourceReady(@NonNull File resource,
Transition<? super File> transition) {
// 处理下载后的文件
}
});
10. 清除缓存
// 内存缓存
Glide.get(context).clearMemory();
// 磁盘缓存:必须在子线程
new Thread(() -> Glide.get(context).clearDiskCache()).start();
11. Glide vs Coil 概览
Coil 是基于 Kotlin 协程的现代化图片加载库,更轻量且依赖更少:
| 维度 | Glide | Coil |
|---|---|---|
| 大小 | ~500KB + | ~200KB |
| 异步模型 | 自定义线程池 | Kotlin Coroutines |
| Java 支持 | 优秀 | 仅 Kotlin |
| API | 链式 RequestOptions |
DSL imageLoader.execute |
| 生命周期 | Fragment Hook | Coroutine Scope |
| 内存 | Bitmap Pool 复用 | 协程 cancel |
| 适用场景 | Java 项目、大型应用 | Kotlin 项目、新应用 |
Java 项目继续用 Glide,Kotlin 项目优先考虑 Coil。两者 API 都很简洁,迁移成本可控。
常见坑与最佳实践
with(this)在 Activity/Fragment 销毁后调用会抛异常:Fragment 已 detach 时不要触发加载,或用with(applicationContext)但记得clear。- RecyclerView 复用错位:必须在
onBindViewHolder调用clear(holder.view)后再load().into()。 - 同一 ImageView 反复加载造成闪烁:使用
signature()区分版本,或关闭transition。 - 大图 OOM:使用
override(targetWidth, targetHeight)限制解码尺寸,避免长图直接解码。 - 加载本地 GIF 占用大量内存:
asGif()显式声明,必要时改asBitmap()取首帧。 - 磁盘缓存路径误删:清理缓存应在子线程,避免主线程 IO 阻塞。
@GlideModule在多模块项目需用@GlideModule的@LibraryGlideModule区分 App 与 Library 模块。- 网络栈与 Retrofit 共用 OkHttp:让缓存与拦截器策略一致,统一日志与超时。
- HTTPS 自签名证书:替换 OkHttp 流时同步替换
SSLSocketFactory与HostnameVerifier。 - PNG 转 WebP 减少体积:可显著降低网络耗时与磁盘缓存占用。
章节小结
- Glide 通过
with().load().into()链式 API 完成图片加载,自动绑定 Activity/Fragment 生命周期。 - 通过
diskCacheStrategy/skipMemoryCache控制缓存策略,circleCrop / transform完成图片变换。 - RecyclerView 场景必须
clear后再into,避免复用错位与闪烁。 @GlideModule+ Generated API 让代码更清晰,并能与项目 OkHttp 栈共享。- Java 项目继续用 Glide;Kotlin 新项目可考虑 Coil。
下一章预告
除了网络图片,应用还会处理用户上传、本地缓存、文档导出等场景。下一章《文件与存储:内部/外部存储、SAF》将系统讲解内部存储、外部存储、分区存储、权限、FileProvider 与 SAF 选择文件的完整方案。