Kotlin练习开发音乐应用时RecyclerView滚动卡顿问题如何解决?
问题根因
MediaStore.Images.Media.getBitmap属于磁盘IO操作,耗时通常在几十到几百毫秒不等,不管是在onBindViewHolder中实时加载(滚动时频繁触发主线程耗时操作导致卡顿),还是主线程一次性全量加载(阻塞UI导致界面冻结),本质都是主线程执行了耗时IO任务导致的。
可选解决方案
方案1:使用Kotlin原生图片加载库Coil(最优解)
图片加载库已经默认实现了异步IO加载、内存/磁盘双缓存、ViewHolder复用错位校验、Bitmap自适应压缩、滚动时自动取消无效加载等全量优化,仅需少量代码即可完成需求,无需自行处理复杂的异步逻辑:
// 引入Coil后直接调用ImageView的扩展方法加载Uri即可 holder.song_image.load(songList[position].albumUri) { // 可选配置:设置加载占位图、错误图 placeholder(R.drawable.default_album) error(R.drawable.album_load_fail) }
方案2:自行用协程实现轻量异步加载
注意:协程并不是仅能处理复杂多任务场景,对于这种简单的异步IO任务,协程的写法比原生Thread更简洁,还能自动绑定生命周期避免内存泄漏,完全适配你的需求。
实现代码示例如下:
override fun onBindViewHolder(holder: SongHolder, position: Int) { val currentSong = songList[position] // 先设置占位图,避免滑动时显示上一个Holder的旧图片 holder.song_image.setImageResource(R.drawable.default_album) // 绑定当前View的生命周期启动协程,View被复用/销毁时自动取消加载任务 holder.itemView.findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.IO) { // 先查内存缓存,有缓存直接用 val cachedBitmap = memoryCache.get(currentSong.id) val targetBitmap = if (cachedBitmap != null) { cachedBitmap } else { // IO线程执行Bitmap加载操作,不会阻塞主线程 MediaStore.Images.Media.getBitmap(contentResolver, currentSong.albumUri).also { // 加载完成后存入内存缓存 memoryCache.put(currentSong.id, it) } } // 切回主线程设置图片 withContext(Dispatchers.Main) { // 校验当前Holder的位置是否匹配,避免ViewHolder复用导致的图片错位 if (holder.bindingAdapterPosition == position) { holder.song_image.setImageBitmap(targetBitmap) } } } }
补充:上述代码中的memoryCache可以直接使用Android系统提供的LruCache实现,限制最大缓存容量避免OOM即可。
额外优化建议
- 不要直接加载原始尺寸的专辑封面:可以先获取ImageView的尺寸,把Bitmap压缩到和控件尺寸一致后再加载,能大幅减少内存占用和加载耗时
- 替换已废弃的
getBitmap方法:Android 10及以上版本中MediaStore.Images.Media.getBitmap已被废弃,推荐使用ContentResolver.openInputStream配合BitmapFactory.decodeStream实现加载,兼容性更好 - 如果歌曲数量较多,不要一次性全量加载所有Bitmap:即便是子线程加载全量Bitmap也会占用大量内存,甚至触发OOM,按需加载+缓存的方案适配性更强
内容的提问来源于stack exchange,提问作者hema ezzat
相关产品推荐
相关产品推荐

