You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:42:06