1.2026金三银四 Android Glide 23连问终极拆解:生命周期、三级缓存、Bitmap复用,大厂面试官到底想听什么?

1 阅读8分钟

Glide 资深工程师面试答案(完整23题·精简·核心版)

确保23题完整,每题包含:一句话总结 + 核心要点 + 流程图 + 关键源码


第一优先级:绝对核心(Q1-Q10)

Q1. 为什么用Glide?有什么好处?

一句话: Google推荐的图片加载库,自动绑定生命周期,内存优化好。

核心要点:

  • ✅ 页面销毁自动取消加载 → 不内存泄漏
  • ✅ 默认RGB_565 → 比ARGB_8888省一半内存
  • ✅ 支持GIF/WebP/视频缩略图
  • ✅ 三级缓存,滑动不卡

源码:

// 默认配置
DEFAULT_MEMORY_CACHE_SIZE = 内存的1/8
DEFAULT_FORMAT = RGB_565  // 2字节/像素,ARGB_8888是4字节

Q2. 如何管理生命周期?

一句话: 添加一个透明的空白Fragment,通过Fragment生命周期回调来控制加载。

流程图:

glide.png

核心代码:

// RequestManagerFragment.java
public void onStart() { lifecycle.onStart(); }   // 恢复
public void onStop() { lifecycle.onStop(); }     // 暂停
public void onDestroy() { lifecycle.onDestroy(); } // 取消

Q3. glide调用fragment。是否还会创建一个空白的fragment?

一句话:会! 同一个Activity只会创建一个,后续复用。

源码验证:

RequestManagerFragment current = fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
    current = new RequestManagerFragment();  // 创建空白Fragment
    fm.beginTransaction().add(current, FRAGMENT_TAG).commit();
}

Q4. 缓存机制如何?LruCache原理?

一句话: 三级缓存(活动→内存→磁盘),LruCache基于LinkedHashMap实现最近最少使用淘汰。

三级缓存:

缓存实现存什么
活动缓存WeakReference正在使用的图片
内存缓存LruCache最近用但当前没用
磁盘缓存DiskLruCache本地文件

LruCache原理:

// LinkedHashMap第三个参数true = 按访问排序
LinkedHashMap<K,V> map = new LinkedHashMap<>(100, 0.75f, true);
// 每次get/put自动移到尾部,头部是最久未使用的
// 超限时移除头部

流程图:

graph TD
    A[访问图片] --> B[移到LinkedHashMap尾部]
    B --> C{缓存大小超限?}
    C -->|是| D[移除头部元素<br/>最久未使用]

Q5. Glide为什么要设计“活动缓存”和“内存缓存”两层?它们存和取的顺序如何?

一句话: 避免频繁操作LruCache(要加锁),正在用的放活动缓存,不用的才放LruCache。

存取顺序:

操作顺序
活动缓存 → 内存缓存 → 磁盘
磁盘/网络 → 内存缓存 → 活动缓存(使用时)

流程图:

graph TD
    subgraph 读取
        R1[开始] --> R2{活动缓存有?} -->|有| R3[直接返回]
        R2 -->|无| R4{内存缓存有?} -->|有| R5[移出内存→放入活动]
    end
    subgraph 释放
        L1[图片不再使用] --> L2[从活动移除] --> L3[放入内存缓存]
    end

核心代码:

// 图片释放时:活动缓存 → 内存缓存
void release(Resource resource) {
    activeResources.remove(key);  // 从活动移除
    cache.put(key, resource);     // 放入LRU缓存
}

Q6. 如何节约内存?如何防止OOM?Glide做了哪些内存优化?

一句话: RGB_565 + Bitmap复用池 + 按需采样 + 生命周期回收。

防OOM五板斧:

策略效果
RGB_565内存减半(2字节 vs 4字节)
BitmapPool复用Bitmap,减少GC
按需采样只加载ImageView实际需要的尺寸
生命周期页面销毁自动回收
LRU淘汰超限自动移除

采样压缩代码:

// 4000px的图要放到400px的View,采样率=10
int sampleSize = Math.max(inWidth/outWidth, inHeight/outHeight);

Q7. 如何实现图片的复用?

一句话: 通过BitmapPool池子,利用Android的inBitmap特性复用内存。

流程图:

graph TD
    A[需要新Bitmap] --> B{池子里有合适的?}
    B -->|有| C[取出并设置inBitmap]
    B -->|无| D[创建新Bitmap]
    C --> E[解码时复用内存]
    F[图片释放] --> G[放回池子]

核心代码:

Bitmap reusable = bitmapPool.get(width, height, config);
if (reusable != null) {
    options.inBitmap = reusable;  // 关键!复用内存
    options.inMutable = true;
}

Q8. Glide怎么做大图加载?

一句话: 采样压缩 + 超大图用RegionDecoder分块解码。

流程图:

graph TD
    A[加载大图] --> B[获取尺寸不加载内存]
    B --> C{图片超大?}
    C -->|是| D[RegionDecoder<br/>只解码可见区域]
    C -->|否| E[采样压缩]

配置:

Glide.with(context)
     .load(url)
     .override(1080, 1920)  // 限制最大尺寸
     .downsample(DownsampleStrategy.AT_LEAST)

Q9. 它的整体架构是怎么样的?三步骤原理?

一句话: with()绑生命周期 → load()配参数 → into()执行加载。

架构图:

graph TD
    A[with] --> B[RequestManager<br/>生命周期管理]
    B --> C[load]
    C --> D[RequestBuilder<br/>参数构建]
    D --> E[into]
    E --> F[Engine引擎<br/>缓存+解码+显示]

源码调用链:

// 步骤1: 绑定生命周期
RequestManager mgr = Glide.with(activity);

// 步骤2: 构建请求
RequestBuilder builder = mgr.load(url);

// 步骤3: 执行加载
builder.into(imageView);  // 触发缓存查询→解码→显示

Q10. 如果让你设计一个图片框架,你会怎么考虑?

一句话: 生命周期管理 + 三级缓存 + Bitmap复用 + 线程池 + 易用API。

设计架构:

graph TD
    A[API层] --> B[生命周期管理]
    A --> C[请求构建]
    B --> D[引擎层]
    C --> D
    D --> E[缓存模块<br/>活动/内存/磁盘]
    D --> F[加载模块<br/>线程池/请求合并]
    D --> G[解码模块<br/>采样/复用/变换]

核心考虑点:

  1. 生命周期自动绑定(Fragment方案)
  2. 三级缓存(活动+内存+磁盘)
  3. Bitmap复用池(inBitmap)
  4. 多线程池分离(网络/磁盘/解码)
  5. 链式调用API

第二优先级:进阶核心(Q11-Q17)

Q11. 图片 URL 不变但内容更新,如何让 Glide 重新加载?

一句话:signature()加版本号,签名变了缓存Key就变了。

代码:

Glide.with(context)
     .load(url)
     .signature(new ObjectKey(System.currentTimeMillis()))  // 时间戳
     .into(imageView);

原理:

// 缓存Key = URL + 宽高 + 签名 + 变换
signature变化 → Key变化 → 旧缓存失效 → 重新下载

Q12. 2个不同大小的imageView,400400和200200,加载一个大小400*400的图片,流程有什么不一样?

一句话: 生成不同缓存Key,各自独立缓存,互不影响。

流程图:

graph LR
    A[400x400原图] --> B[400x400 View]
    A --> C[200x200 View]
    B --> B1[Key: URL+400+400<br/>采样率=1]
    C --> C1[Key: URL+200+200<br/>采样率=2]

核心: 宽高参与Key生成,所以是两个独立缓存。


Q13. Glide加载一个一兆的图片(100100),是否会压缩后再加载,放到一个200200的view上会怎样,1000*1000呢,图片会很模糊,怎么处理?

一句话: Glide默认不放大,小图放大就是会模糊。解决方案是加载更大的图。

场景分析:

原图ImageViewGlide行为结果
100x100200x200拉伸放大❌ 模糊
1000x1000200x200采样压缩✅ 清晰

解决方案:

// 强制加载更大尺寸
Glide.with(context)
     .load(url)
     .override(400, 400)  // 加载大图,让Glide缩小
     .into(imageView);

Q14. Glide 缓存 Key 如何生成?

一句话: URL + 宽高 + 签名 + 变换,任一不同则Key不同。

源码:

messageDigest.update(url.getBytes());      // URL
messageDigest.update(intToBytes(width));   // 宽度
messageDigest.update(intToBytes(height));  // 高度
signature.updateDiskCacheKey(messageDigest); // 签名
// 所有参数参与生成唯一Key

Q15. 如何加载Gif图片的?

一句话: 自动识别,用GifDrawable + Handler定时切换帧。

流程图:

graph TD
    A[GIF文件] --> B[解析每一帧]
    B --> C[Handler定时切换]
    C --> D[刷新View]
    D --> E{是否循环} -->|是| C

使用:

// 自动识别
Glide.with(context).load(gifUrl).into(imageView);

// 只取第一帧
Glide.with(context).load(gifUrl).asBitmap().into(imageView);

Q16. glide调用在子线程会怎么样?

一句话:会抛异常! into(ImageView)必须在主线程调用。

源码:

public ViewTarget into(ImageView view) {
    Util.assertMainThread();  // 非主线程抛异常
}

Q17. 磁盘的缓存策略是怎么样的?

一句话: 四种策略,默认ALL(原始图+处理图都缓存)。

策略缓存内容
ALL原始图 + 处理图
DATA只缓存原始图
RESOURCE只缓存处理图
NONE不缓存
Glide.with(context)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy.DATA)  // 只缓存原图
     .into(imageView);

Q18. Glide 默认的缓存策略是什么?

一句话: 内存缓存 + 磁盘缓存ALL策略(原始图+处理图都缓存)。

// 默认配置
内存缓存大小 = 设备内存的 1/8
磁盘缓存大小 = 250MB
磁盘策略 = DiskCacheStrategy.AUTOMATIC (等价于ALL)

Q19. 如何自定义 Glide 的磁盘缓存路径和大小?

一句话: 通过AppGlideModule自定义配置。

@GlideModule
public class MyGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 自定义磁盘缓存路径和大小
        builder.setDiskCache(new ExternalDiskCacheFactory(context, "my_cache", 100 * 1024 * 1024));
        
        // 自定义内存缓存大小
        builder.setMemoryCache(new LruResourceCache(50 * 1024 * 1024));
    }
}

Q20. Glide 的 Transition(过渡动画)有什么用?

一句话: 控制图片从占位符到目标图片的过渡效果。

Glide.with(context)
     .load(url)
     .transition(DrawableTransitionOptions.withCrossFade())  // 淡入淡出
     .into(imageView);

Q21. Glide 如何监听加载进度?

一句话: 官方没有直接提供,需要通过自定义ModelLoader + 进度包装流实现。

实现思路:

  1. 自定义ProgressInputStream包装原始流
  2. 用OkHttp拦截器添加进度监听
  3. 注册自定义ModelLoader

Q22. Glide 的 RequestOptions 有哪些常用方法?

一句话: 配置加载参数的核心类,常用方法如下:

RequestOptions options = new RequestOptions()
    .placeholder(R.drawable.placeholder)     // 占位图
    .error(R.drawable.error)                 // 错误图
    .override(300, 300)                      // 指定尺寸
    .centerCrop()                            // 居中裁剪
    .circleCrop()                            // 圆形裁剪
    .skipMemoryCache(true)                   // 跳过内存缓存
    .diskCacheStrategy(DiskCacheStrategy.ALL) // 磁盘策略
    .priority(Priority.HIGH);                // 优先级

Glide.with(context).load(url).apply(options).into(imageView);

Q23. Glide 和 Fresco 对比

一句话: 常规项目用Glide,超大图/低端机用Fresco。

特性GlideFresco
包体积较小较大
内存占用中等极低(5.0以下用Ashmem)
GIF支持
生命周期绑定✅ 自动❌ 需手动
学习成本中等
Google推荐

快速记忆卡片(23题全)

#面试题一句话答案
Q1为什么用Glide生命周期自动绑定,内存优化好
Q2生命周期管理加透明Fragment
Q3会创建空白Fragment吗
Q4缓存机制/LRU原理三级缓存,LinkedHashMap按访问排序
Q5两层缓存原因避免频繁锁操作
Q6防OOMRGB_565 + BitmapPool + 采样
Q7图片复用BitmapPool + inBitmap
Q8大图加载采样压缩 + RegionDecoder
Q9整体架构with→load→into
Q10设计图片框架生命周期+三级缓存+复用+线程池
Q11URL内容更新signature()加版本号
Q12不同大小View缓存Key不同,独立缓存
Q13小图放大模糊会模糊,用override加载大图
Q14缓存Key生成URL+宽高+签名+变换
Q15GIF加载Handler定时切换帧
Q16子线程调用抛异常,必须主线程
Q17磁盘策略ALL/DATA/RESOURCE/NONE
Q18默认缓存策略内存1/8 + 磁盘ALL
Q19自定义缓存路径AppGlideModule配置
Q20Transition动画占位符到目标图过渡
Q21加载进度监听官方无,需自定义
Q22RequestOptions占位图/尺寸/裁剪等配置
Q23vs Fresco常规Glide,超大图Fresco