RecyclerView实现网格视图显示媒体缩略图滚动卡顿优化求助
解决RecyclerView加载媒体缩略图卡顿的问题
问题根源
你说得没错,onBindViewHolder是在主线程执行的,直接调用MediaStore.Images.Thumbnails.getThumbnail()或者视频的对应方法是同步操作,会阻塞UI线程,导致滚动时出现明显卡顿——毕竟每次滚动都会触发大量的缩略图加载操作,主线程根本忙不过来。
优化方案推荐
1. 用成熟的图片加载库(最省心的方案)
像Glide、Coil或者Picasso这类库,都自带异步加载、内存+磁盘缓存、自动适配RecyclerView复用的特性,完全不用自己处理线程和缓存逻辑,代码还特别简洁。
举个Glide的例子:
// 加载图片缩略图 Glide.with(context) .load(ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, item.getImgId())) .centerCrop() .override(100, 100) // 对应MICRO_KIND的尺寸,大概96x96 .into(holder.thumbnailImageView) // 加载视频缩略图 Glide.with(context) .load(ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, item.getImgId())) .centerCrop() .override(100, 100) .into(holder.thumbnailImageView)
这类库会自动在后台线程加载缩略图,加载完成后切回主线程更新UI,还会缓存已经加载过的缩略图,滚动回来的时候直接取缓存,流畅度拉满。
2. 自己实现异步加载+缓存(适合需要自定义逻辑的场景)
如果不想引入第三方库,可以用Kotlin协程(或者Java的Executor)来做异步加载,再配合LruCache做内存缓存:
第一步:创建内存缓存
private val thumbnailCache = LruCache<Long, Bitmap>(calculateCacheSize()) private fun calculateCacheSize(): Int { // 获取应用可用内存的1/8作为缓存大小 val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt() return maxMemory / 8 }
第二步:在ViewHolder中异步加载
override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val item = mediaList[position] val imgId = item.getImgId() // 先从缓存取 val cachedBitmap = thumbnailCache.get(imgId) if (cachedBitmap != null) { holder.thumbnailImageView.setImageBitmap(cachedBitmap) return } // 缓存没有,异步加载 CoroutineScope(Dispatchers.IO).launch { val bitmap = if (item.isImage()) { MediaStore.Images.Thumbnails.getThumbnail( context.contentResolver, imgId, MediaStore.Images.Thumbnails.MICRO_KIND, null ) } else { MediaStore.Video.Thumbnails.getThumbnail( context.contentResolver, imgId, MediaStore.Video.Thumbnails.MICRO_KIND, // 注意这里要对应Video的常量,你之前写错了! null ) } // 存入缓存 bitmap?.let { thumbnailCache.put(imgId, it) } // 切回主线程更新UI withContext(Dispatchers.Main) { bitmap?.let { holder.thumbnailImageView.setImageBitmap(it) } } } }
注意:你之前加载视频缩略图的时候误用了MediaStore.Images.Thumbnails.MICRO_KIND,应该改成MediaStore.Video.Thumbnails.MICRO_KIND,虽然两者数值可能相同,但规范上要对应媒体类型。
3. 提前预加载(可选进阶优化)
可以在RecyclerView首次加载或者用户滚动到某个位置时,提前加载后续几个item的缩略图,进一步减少滚动时的等待感,但这个属于锦上添花的操作,一般用前两个方案就足够解决卡顿问题了。
额外提示
- 记得在
onViewRecycled方法里取消未完成的加载任务,防止内存泄漏或者ViewHolder复用后显示错误的媒体缩略图(用Glide的话它会自动处理这个问题)。 - 如果是Android 10及以上版本,注意申请
READ_EXTERNAL_STORAGE权限(或者使用MediaStore的新API,不过加载缩略图仍需该权限)。
内容的提问来源于stack exchange,提问作者MJ55
相关产品推荐
相关产品推荐

