A
第 24 章JAVA30 分钟

图片加载: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 都很简洁,迁移成本可控。

常见坑与最佳实践

  1. with(this) 在 Activity/Fragment 销毁后调用会抛异常:Fragment 已 detach 时不要触发加载,或用 with(applicationContext) 但记得 clear。
  2. RecyclerView 复用错位:必须在 onBindViewHolder 调用 clear(holder.view) 后再 load().into()。
  3. 同一 ImageView 反复加载造成闪烁:使用 signature() 区分版本,或关闭 transition。
  4. 大图 OOM:使用 override(targetWidth, targetHeight) 限制解码尺寸,避免长图直接解码。
  5. 加载本地 GIF 占用大量内存:asGif() 显式声明,必要时改 asBitmap() 取首帧。
  6. 磁盘缓存路径误删:清理缓存应在子线程,避免主线程 IO 阻塞。
  7. @GlideModule 在多模块项目需用 @GlideModule 的 @LibraryGlideModule 区分 App 与 Library 模块。
  8. 网络栈与 Retrofit 共用 OkHttp:让缓存与拦截器策略一致,统一日志与超时。
  9. HTTPS 自签名证书:替换 OkHttp 流时同步替换 SSLSocketFactory 与 HostnameVerifier。
  10. 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 选择文件的完整方案。