题目
优化RecyclerView加载大量高分辨率图片导致的卡顿与OOM问题
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
内存管理,图片加载优化,滚动性能优化,异步处理,资源回收
快速回答
核心优化方案:
- 使用专业图片库(如Glide/Picasso)实现异步加载与多层缓存
- 动态调整图片分辨率(采样率/缩放)匹配视图尺寸
- 监听滚动状态:快速滑动时暂停加载,停止后恢复
- 优化ViewHolder复用机制,避免内存泄漏
- 配置LRU内存缓存与磁盘缓存策略
- 采用合适图片格式(WebP/AVIF)和硬件加速
问题本质分析
当RecyclerView加载大量高分辨率图片时,主要引发两类问题:
- 滚动卡顿:主线程解码Bitmap阻塞UI渲染,频繁触发GC
- 内存溢出(OOM):每张高分辨率图片占用数MB内存(如4000x3000的ARGB_8888图片约占用48MB)
系统化解决方案
1. 图片加载优化(核心)
代码示例(Glide最佳实践):
Glide.with(context)
.load(imageUrl)
.override(targetWidth, targetHeight) // 匹配ImageView尺寸
.format(DecodeFormat.PREFER_RGB_565) // 减少内存50%
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.skipMemoryCache(false) // 启用内存缓存
.into(imageView)原理说明:
- 采样率优化:通过
BitmapFactory.Options.inSampleSize降低解码分辨率 - 内存格式:优先使用RGB_565(每个像素2字节)替代ARGB_8888(4字节)
2. 滚动性能优化
动态加载控制:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
when (newState) {
RecyclerView.SCROLL_STATE_DRAGGING -> Glide.with(context).pauseRequests()
RecyclerView.SCROLL_STATE_IDLE -> Glide.with(context).resumeRequests()
}
}
})硬件加速:
<!-- 在RecyclerView的item布局中启用 -->
<ImageView
android:layerType="hardware" /> <!-- 避免滚动时重绘 -->3. 内存管理策略
缓存配置:
// 自定义Glide内存缓存(AppGlideModule)
@GlideModule
class CustomGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
val memoryCacheSize = (Runtime.getRuntime().maxMemory() * 0.3).toLong()
builder.setMemoryCache(LruResourceCache(memoryCacheSize))
}
}资源回收:
- 在
onViewRecycled()中主动清理:Glide.with(context).clear(viewHolder.imageView) - 使用
WeakReference持有Context避免泄漏
最佳实践
- 图片格式选择:优先使用WebP(比PNG小30%)或AVIF(HDR支持)
- 大图处理:对超过屏幕尺寸2倍的图片启用
BitmapRegionDecoder分块加载 - 监控工具:结合Android Profiler的Memory Tracker和Allocation Tracker定位泄漏点
常见错误
- 主线程解码:未使用异步加载导致ANR
- 尺寸不匹配:加载原图却显示在100x100的ImageView中
- 缓存滥用:无限增长的缓存引发OOM
- 未取消请求:View销毁后未终止网络请求
扩展知识
- Native内存管理:Android 8.0后Bitmap内存移至Native堆,需关注
android:largeHeap和VMRuntime.getRuntime().setTargetHeapUtilization() - Downsampling技术:Glide在解码前先进行数学采样,比
inSampleSize更高效 - Preferch机制:
RecyclerView.setItemViewCacheSize(20)配合Glide的preload()