1、Glide简介:
| 性维度 | Glide 的核心实现与优势 | 关键面试解读 |
|---|---|---|
| 图片解码与内存 | 默认解码格式为 RGB_565,内存占用约为ARGB_8888的一半;支持智能的 Bitmap 池复用。 | 取舍思维:以轻微的色彩损失换取大幅内存节省,适合图片海量的App。 |
| 生命周期集成 | 通过注入一个无UI的 Fragment 监听 Activity/Fragment 生命周期,实现请求的自动暂停、恢复和销毁。 | 架构设计思想:将生命周期管理内化,对开发者透明,是防止内存泄漏的关键。 |
| 缓存策略 | 四级缓存:活动资源、内存、磁盘资源(缓存的)、磁盘文件(数据)。其中活动资源缓存是独到设计。 | 性能优化精髓:活动资源缓存直接引用正在使用的图片,避免频繁回收导致的卡顿。 |
| 加载流程 | 统一的 ModelLoader 注册机制,支持多种数据源(URL、File、ContentProvider等)无缝扩展。 | 设计模式应用:典型的责任链模式,体现了高度的可扩展性和模块化设计。 |
2、高效的四级缓存机制
缓存流程:活动资源 -> 内存缓存 -> 磁盘资源 -> 磁盘文件 -> 网络/源加载
- 活动资源缓存:一个
WeakReference的HashMap,持有当前正在展示的图片引用。这是为了避免同一图片在列表快速滚动时被重复加载和回收,直接解决界面卡顿。 - 内存缓存:一个
LruCache,存储最近加载过的图片,采用LRU算法管理。 - 磁盘资源缓存:存储经过解码、转换后的图片。
- 磁盘文件缓存:存储原始的图片数据(如网络下载的原始文件)。
3、 高效的Bitmap管理与复用
Bitmap池:Glide维护了一个
BitmapPool(通常是LruBitmapPool)。当Bitmap从内存缓存中被移除或ImageView被复用时,其Bitmap对象不会直接被回收,而是放入BitmapPool。当需要加载一张新图片时,Glide会尝试从BitmapPool中找一个可复用的Bitmap对象,直接复用其内存,从而避免频繁的Bitmap内存分配与回收,减少GC停顿和内存抖动
4、Glide的加载流程是怎样的?
参考答案:从Glide.with().load().into()开始,主要流程包括:
- 请求构建:创建RequestBuilder,封装图片URL、尺寸、变换等参数
- 缓存检查:依次检查活动资源缓存(弱引用)、内存缓存(LruCache)、磁盘缓存
- 数据获取:若缓存未命中,通过ModelLoader获取数据源(网络、文件、资源等)
- 解码转换:在后台线程通过DecodeJob进行图片解码、采样、变换处理
- 显示结果:回到主线程通过Target将Bitmap设置到ImageView
- 缓存存储:将处理后的图片存入内存缓存和磁盘缓存
5、Glide如何保证不内存泄漏?
- A: 通过向宿主
Activity/Fragment添加一个无UI的Fragment,利用其生命周期回调,在页面销毁时自动取消所有未完成的请求并清理资源。这是其设计的核心安全机制。
6、Glide和Picasso在加载同一张图片到相同大小的ImageView时,内存占用可能不同,为什么?
- A: 关键在于默认的
Bitmap配置不同。Picasso默认使用ARGB_8888,每个像素占4字节。而Glide默认使用RGB_565,每个像素占2字节。因此,在相同分辨率下,Glide加载的图片内存占用约为Picasso的一半。当然,Glide可以通过.format()方法更改配置。
7、如何配置磁盘缓存策略?
参考答案:通过DiskCacheStrategy配置:
- ALL:缓存原始数据和转换后数据
- NONE:不缓存任何数据
- DATA:只缓存原始数据
- RESOURCE:只缓存转换后数据
- AUTOMATIC:根据数据源智能选择(默认策略)
8、如何优化Glide加载性能?
参考答案:
- 尺寸优化:使用
override(width, height)指定精确尺寸,避免加载过大图片 - 缓存策略:根据业务场景选择合适的DiskCacheStrategy
- 预加载:对即将显示的图片使用
preload()提前加载到缓存 - 列表优化:在RecyclerView快速滑动时使用
pauseRequests()暂停加载 - 采样率优化:Glide会自动根据ImageView尺寸计算inSampleSize,减少内存占用
9、Glide如何防止OOM?
参考答案:
- 自动采样:根据ImageView的宽高和图片原始尺寸,自动计算合适的inSampleSize进行采样
- 尺寸适配:通过
override()指定目标尺寸,避免加载过大的Bitmap - 内存缓存控制:使用LruCache限制内存缓存大小,超出时自动回收
- 弱引用管理:活动资源缓存使用弱引用,当内存紧张时会被GC回收
- Bitmap复用:在Android 4.4+使用BitmapPool复用Bitmap内存
10、常用的图片变换有哪些?如何实现圆角图片?
参考答案:
- 内置变换:
CenterCrop(居中裁剪)、FitCenter(居中缩放)、CircleCrop(圆形裁剪) - 圆角实现:
Glide.with(context)
.load(url)
.transform(RoundedCorners(radius)) // 使用内置圆角变换
.into(imageView)
11、Glide支持哪些图片格式?
参考答案:支持JPEG、PNG、GIF、WebP等常见格式,通过不同的Decoder进行解码。对于GIF,需要使用asGif()明确指定格式。
12、加载失败如何处理?
参考答案:
- 占位图设置:使用
placeholder()设置加载中占位图,error()设置加载失败占位图 - 监听回调:通过
listener()添加RequestListener,在onLoadFailed()中处理失败逻辑 - 重试机制:可自定义ModelLoader实现重试逻辑
13、Glide与Picasso、Fresco的对比
参考答案(从几个维度对比):
- 功能特性:Glide功能最全面(支持GIF、视频帧等),Picasso轻量,Fresco专攻大图
- 内存占用:Glide通过BitmapPool优化内存,Picasso较简单,Fresco使用Native内存
- 缓存机制:Glide三级缓存最完善,Picasso两级缓存,Fresco使用Native缓存
- API设计:Glide链式调用灵活,Picasso简洁,Fresco配置复杂
- 适用场景:Glide适合大多数场景,Picasso适合简单需求,Fresco适合大图加载
14、列表快速滑动时如何优化?
参考答案:
- 在RecyclerView的
onScrollStateChanged()中监听滑动状态 - 当快速滑动时调用
Glide.with(context).pauseRequests()暂停加载 - 滑动停止时调用
resumeRequests()恢复加载 - 避免在快速滑动时加载大量图片导致卡顿
15、使用Glide怎么加载大图片
加载大图片时,确实需要特别注意内存优化和显示效果。下面这个表格汇总了核心方法和关键配置,方便你快速把握要点。
| 优化维度 | 核心方法/配置 | 作用说明 |
|---|---|---|
| 📏 尺寸控制 | override(width, height) | 指定精确的加载尺寸,避免解码原始大图。 |
| 🖼️ 渐进加载 | thumbnail(thumbnailRequest) | 先快速加载小图作为占位,再全尺寸加载,提升体验。 |
| 💾 缓存策略 | diskCacheStrategy(DiskCacheStrategy.RESOURCE) | 对于大图,推荐只缓存处理后的资源。 |
skipMemoryCache(true) | 跳过内存缓存,防止大图挤占宝贵的内存空间。 | |
| 🎨 解码格式 | format(DecodeFormat.PREFER_RGB_565) | 使用RGB_565格式(每个像素占2字节),比默认的ARGB_8888(4字节)节省一半内存。 |
| 🧩 专用视图 | SubsamplingScaleImageView | 第三方库,支持图片局部显示和手势操作,是加载超长图或超高分辨率图的最佳选择。 |
16、自定义View步骤时
主要分六步:
- 继承合适的View类;
- 定义并解析自定义属性;
- 重写
onMeasure正确测量自身尺寸; - 如果是容器,还需重写
onLayout安排子View; - 重写绘制方法 onDraw,使用Canvas和Paint进行绘制
- 处理触摸事件 onTouchEvent。
17、内存优化 —— 防泄漏、减开销、防OOM
目标是稳定、高效的内存使用曲线。
| 优化方向 | 核心策略与工具 | 关键实现要点 |
|---|---|---|
| 内存泄漏防治 | 工具:LeakCanary(自动)、Android Profiler(手动堆转储)。 场景:Activity/Fragment 被长生命周期对象(单例、静态变量、后台线程)持有。 | 1. 使用 Application Context 替代 Activity Context。 2. Handler 用静态内部类 + WeakReference,并在 onDestroy 中移除回调。 3. 监听器、BroadcastReceiver、SensorManager 及时反注册。 |
| 大图与Bitmap优化 | 工具:Memory Profiler、adb shell dumpsys meminfo。 | 1. 使用 Glide/Picasso 等库(自带内存缓存、尺寸适配)。 2. 加载大图时,使用 BitmapFactory.Options 的 inSampleSize 进行采样压缩。 3. 使用 Bitmap.Config.RGB_565(非透明图)。 4. 在 onLowMemory 回调中主动清理缓存。 |
| 内存抖动避免 | 工具:Memory Profiler观察分配曲线。 | 1. 避免在循环或高频回调(如onDraw)中创建对象。 2. 使用对象池(如 Pools.SimplePool)复用 Message、Rect 等对象。 |
| 数据结构与集合 | - | 1. 根据场景选择最优集合(SparseArray 替代 HashMap<Integer, Object>)。 2. 避免使用枚举(Enum),改用 @IntDef/@StringDef。 |
18、卡顿与UI渲染优化 —— 保流畅(60fps,即16ms/帧)
核心是减轻主线程负担。
| 优化方向 | 核心策略与工具 | 关键实现要点 |
|---|---|---|
| 布局层级优化 | 工具:Layout Inspector、Systrace。 | 1. 使用 ConstraintLayout 减少嵌套,实现扁平化。 2. 善用 <include>、<merge>、ViewStub。 3. 使用 AsyncLayoutInflater 异步加载复杂布局。 |
| 测量/布局/绘制优化 | 工具:GPU过度绘制调试、Profile GPU Rendering。 | 1. 自定义View时,优化onDraw:避免创建对象、使用canvas.clipRect()限制绘制区域。 2. 简化View的onMeasure/onLayout逻辑。 |
| 列表滑动优化 | 工具:RecyclerView专用分析工具。 | 1. RecyclerView 优化: - 使用 DiffUtil 进行增量更新。 - 预加载:setInitialPrefetchItemCount()。 - 优化 ViewHolder 创建,复用池调优。 - 使用 payload 进行局部刷新,避免整项重绑。 2. 分页加载,避免一次性加载所有数据。 |
| 主线程耗时操作 | 工具:Systrace、Perfetto。 | 1. 严格遵循:网络、文件、数据库等I/O操作必须在子线程。使用协程(withContext(Dispatchers.IO))、RxJava等。 2. 复杂计算(如JSON解析、列表排序)移出主线程。 |
| 动画优化 | - | 1. 优先使用属性动画而非补间动画(更高效)。 2. 使用 硬件加速层(View.setLayerType)但要谨慎,会增大内存开销。 |
19、耗电与网络优化 —— 保续航、省流量
目标是减少不必要的后台活动和网络请求。
| 优化方向 | 核心策略与工具 | 关键实现要点 |
|---|---|---|
| 后台任务管控 | 工具:Android Vitals、Battery Historian。 | 1. 使用 WorkManager 处理可延迟的后台任务,系统会批量执行。 2. 使用 JobScheduler 在特定条件(如充电、有网络)下执行任务。 3. 及时释放 WakeLock,避免屏幕关闭后CPU仍被占用。 |
| 网络请求优化 | 工具:Charles/Fiddler抓包、Network Profiler。 | 1. 合并请求与减少轮询:使用 WebSocket、长连接替代频繁短连接。 2. 数据压缩与缓存:使用GZIP压缩、合理设置HTTP缓存头、对非实时数据做本地缓存。 3. 图片优化:根据网络状况(Wi-Fi/4G)下发不同分辨率的图片,使用WebP格式。 |
| 传感器与定位 | - | 1. 使用后及时注销传感器监听。 2. 根据精度需求选择合适的定位模式(GPS vs NETWORK),获取到位置后及时关闭。 |
20、某电商App商品列表页,快速滑动时卡顿,且退出后内存居高不下。
分析过程:
-
定位:使用 Systrace 发现滑动时主线程有大量
inflate和onBindViewHolder耗时;使用 Memory Profiler 发现退出后Activity实例未被回收。 -
根因:
- 卡顿:
RecyclerView的Item布局层级过深(6层),且onBindViewHolder中有同步的图片加载和复杂计算。 - 内存泄漏:在
ViewHolder中注册了一个全局事件总线监听器,但未在视图销毁时反注册。
- 卡顿:
-
解决方案:
- UI渲染:将
Item布局重构为ConstraintLayout(降至3层)。将onBindViewHolder中的计算移至后台线程,使用Glide的into(Target)异步加载图片。 - 内存:在
ViewHolder的onViewRecycled或Activity的onDestroy中,反注册事件监听,并清除对ImageView的引用。 - 列表性能:引入
DiffUtil和ListAdapter,并使用payload进行局部刷新。
- UI渲染:将
-
结果:滑动帧率从 45fps 提升至 58fps,退出页面后内存即时释放。
“我会将性能优化看作一个由 ‘度量、分析、优化、监控’ 构成的闭环。具体实施围绕三大支柱展开:
- 内存优化:核心是防泄漏(利用LeakCanary)和减开销(Bitmap优化、避免内存抖动),目标是稳定的内存曲线。
- 卡顿优化:核心是为主线程减负(布局扁平化、列表优化、异步化),利用Systrace和GPU渲染工具,确保60fps的流畅体验。
- 耗电与网络优化:核心是减少不必要的后台活动(使用WorkManager)和网络请求(合并、压缩、缓存)。