Android RecycleView 异步缓存 itemView 提升滑动性能

RecyclerView 是 Android 官方推荐的用于展示大量数据列表的控件,具有高度的可定制性和灵活性。我们可以通过自定义 LayoutManager、ItemDecoration、ItemAnimator 等来实现不同的布局和动画效果,满足各种需求。同时,RecyclerView 支持局部刷新、数据更新等操作,能够提高列表的性能和交互体验,在我们工作当中使用得也非常频繁。

但最近在做性能检测的过程当中,设置了多布局的 RecyclerView 在快速滑动中会有一些卡顿,所以将解决方案在此文记录一下

首先需要复习一下实现 RecyclerView 的两个方法 :onCreateViewHolder 和 onBindViewHolder


Kotlin 复制代码
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
        return MyViewHolder(view)
    }

主要是 View 的渲染工作(耗时)和构建 ViewHolder 并返回


Kotlin 复制代码
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val item = itemList[position]
        holder.bind(item)
    }

主要的工作是将已有的数据设置在 View 上进行显示处理


当 RecyclerView 滑动时,它会根据当前屏幕上显示的 item 数量和位置进行复用已有的 ViewHolder 对象,这种复用的机制可以避免频繁创建和销毁 ViewHolder 对象,提高性能。

也就是说,在理想和一般的情况下,滑动时只调用 onBindViewHolder,并不会调用 onCreateViewHolder(实际上会偶发调用)

但是如果设置了多布局的情况下 ,onCreateViewHolder 就会疯狂调用 ,因为渲染布局是在主线程中进行的,所以在这种情况下快速滑动时会 在主线程大量渲染布局 从而引发卡顿


现在有两个问题:① onCreateViewHolder 频繁得调用 ② 布局的渲染在主线程

解决方案:① 缓存 ② 线程池

Kotlin 复制代码
class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    private val cacheViewSize = 10 // 可根据实际需求调整缓存大小
    private val executors = Executors.newSingleThreadExecutor()
    private val cacheMap = mutableMapOf<Int, LinkedList<View>>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val layoutResId = R.layout.xxx // 不同的 viewType 有不同的布局自行处理即可

        val cacheViewOrGenerateView = getView(parent, layoutResId)
        return MyViewHolder(cacheViewOrGenerateView)
    }

    private fun getView(parent: ViewGroup, layoutRes: Int): View {
        if (cacheMap[layoutRes] == null) {
            cacheMap[layoutRes] = LinkedList()
        }
        cacheMap[layoutRes]?.let { list ->
            if (list.isEmpty()) {
                viewCache(parent, layoutRes)
            } else {
                val view = list.poll()
                if (view != null) {
                    viewCache(parent, layoutRes) // 拿一个补一个
                    return view
                }
            }
        }
        return LayoutInflater.from(parent.context).inflate(layoutRes, parent, false) // 兜底处理
    }

    /**
     * 渲染布局至缓存,直到 cacheViewSize 为止
     */
    private fun viewCache(parent: ViewGroup, layoutRes: Int) {
        executors.execute {
            cacheMap[layoutRes]?.let { list ->
                while (list.size < cacheViewSize) {
                    list.add(parent.getItemView(layoutRes))
                }
            }
        }
    }
    
    // ....
    
    open class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
}

将布局的渲染放线程池里执行,并设置固定缓存,onCreateViewHolder 中的 View 优先从缓存中获取,若没有缓存使用同步渲染作兜底处理

相关推荐
参宿四南河三1 小时前
Android Compose SideEffect(副作用)实例加倍详解
android·app
火柴就是我2 小时前
mmkv的 mmap 的理解
android
没有了遇见2 小时前
Android之直播宽高比和相机宽高比不支持后动态获取所支持的宽高比
android
shenshizhong2 小时前
揭开 kotlin 中协程的神秘面纱
android·kotlin
vivo高启强3 小时前
如何简单 hack agp 执行过程中的某个类
android
沐怡旸3 小时前
【底层机制】 Android ION内存分配器深度解析
android·面试
你听得到113 小时前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter
KevinWang_3 小时前
Android 原生 app 和 WebView 如何交互?
android
用户69371750013844 小时前
Android Studio中Gradle、AGP、Java 版本关系:不再被构建折磨!
android·android studio
杨筱毅4 小时前
【底层机制】Android低内存管理机制深度解析
android·底层机制