引言

在Android开发中,RecyclerView是一种常用的列表控件,用于展示很多数据。然而,随着数据量的添加,RecyclerView的功用或许会受到影响,导致卡顿、内存走漏等问题。本文将介绍一些优化技巧,协助大家进步RecyclerView的功用,使其在各种状况下都能坚持流通。

优化思路

RecyclerView 功用优化的核心思路可以概括为以下几个方面:

  1. 布局优化: 优化 RecyclerView 的布局结构,削减嵌套层级,进步布局效率。
  2. 削减制作: 尽或许削减视图的制作次数,防止过度制作带来的功用耗费。
  3. 滑动优化: 在滑动过程中,尽或许的削减耗时操作,防止影响滑动作用。
  4. 预加载: 预加载行将显示的视图,进步展示功用。
  5. 内存优化: 削减内存的耗费,合理开释内存,防止内存走漏。

下面针对这些分别给出具体的优化战略。

布局优化

  1. 削减布局嵌套

防止在RecyclerView的Item布局中运用过多的嵌套布局和杂乱的层次结构,这会添加烘托的时刻和耗费。尽量运用简略的布局结构,并合理运用ConstraintLayout等高效布局。

<!-- item_layout.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <!-- 其他视图组件 -->
</androidx.constraintlayout.widget.ConstraintLayout>
  1. 运用merge标签来合并布局

运用merge标签可以将多个布局文件合并为一个,削减布局层级,进步制作功用。

<!-- 运用merge标签合并布局 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image" />
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Text" />
</merge>
  1. 启用setHasFixedSize

设置 setHasFixedSize(true) 后,RecyclerView会假定所有的Item的高度是固定的,不会因为Item的变化而触发重新核算布局,防止requestLayout导致的资源浪费。

val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.setHasFixedSize(true)

需求留意的是,运用 setHasFixedSize(true)适用于所有Item高度固定且不会发生变化的状况。假如Item高度不固定或许会发生变化,应该防止运用该办法,不然或许导致布局显示异常。

削减制作

  1. 运用DiffUtil进行数据更新

在数据集变化时,运用DiffUtil进行差异核算可以削减不必要的UI更新,进步功用。DiffUtil可以在后台线程中高效地核算数据集的差异,并将成果运用到RecyclerView中。

class MyDiffCallback(private val oldList: List<String>, private val newList: List<String>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return oldList.size
    }
    override fun getNewListSize(): Int {
        return newList.size
    }
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}
// 在Adapter中运用DiffUtil
val diffResult = DiffUtil.calculateDiff(MyDiffCallback(oldList, newList))
diffResult.dispatchUpdatesTo(this)
  1. 限制列表项的数量

假如列表中的数据量非常大,可以考虑进行分页加载或许只加载可见范围内的数据,以削减内存占用和烘托时刻。

// 仅加载可见范围内的数据
recyclerView.layoutManager?.setInitialPrefetchItemCount(10)

滑动优化

  1. 在onCreateViewHolder中进行必要的初始化操作

在ViewHolder的创立阶段,进行必要的初始化操作,如设置监听器等,防止在onBindViewHolder()中进行耗时操作,进步翻滚功用。

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
    val viewHolder = ViewHolder(view)
    // 进行必要的初始化操作
    return viewHolder
}
  1. 滑动中止加载操作

可以经过 RecyclerView.addOnScrollListener(listener) 办法添加一个翻滚监听器,然后在监听器中进行相应的操作,进一步优化滑动的作用。

val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
val adapter = MyAdapter(dataList)
recyclerView.adapter = adapter
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)
        // 判断翻滚状态是否为中止翻滚状态
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            startLoaidng()
        } else {
            // 履行中止加载操作,例如中止图片加载等
            stopLoading()
        }
    }
})

预加载

  1. 发动calculateExtraLayoutSpace

calculateExtraLayoutSpace 办法可以用来添加RecyclerView预留的额定空间,有助于提早加载屏幕外的Item,防止滑动过程中的卡顿。

您可以经过重写calculateExtraLayoutSpace办法来返回额定的空间巨细,以便RecyclerView在滑动过程中预加载屏幕外的Item。

class CustomLayoutManager : LinearLayoutManager {
    constructor(context: Context) : super(context)
    constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
    override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
        super.calculateExtraLayoutSpace(state, extraLayoutSpace)
        // 设置额定的布局空间,可以依据需求动态核算
        extraLayoutSpace[0] = 200 
        extraLayoutSpace[1] = 200
    }
}
  1. 重写collectAdjacentPrefetchPositions

collectAdjacentPrefetchPositions办法是RecyclerView中的一个维护办法,用于搜集与给定方位相邻的预取方位。这个办法首要用于RecyclerView的预取机制,用于在滑动过程中预取与当前方位相邻的Item数据,进步滑动的流通度。

你可以在自定义LayoutManager中重写collectAdjacentPrefetchPositions办法来实现相邻方位的预取逻辑。

class CustomLayoutManager : LinearLayoutManager {
    constructor(context: Context) : super(context)
    constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
    override fun collectAdjacentPrefetchPositions(dx: Int, dy: Int, state: RecyclerView.State?, layoutPrefetchRegistry: LayoutPrefetchRegistry) {
        super.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry)
        // 依据滑动方向(dx, dy)搜集相邻的预取方位
        val anchorPos = findFirstVisibleItemPosition()
        if (dy > 0) {
            // 向下滑动,预取下面的Item数据
            for (i in anchorPos + 1 until state?.itemCount ?: 0) {
                layoutPrefetchRegistry.addPosition(i, 0)
            }
        } else {
            // 向上滑动,预取上面的Item数据
            for (i in anchorPos - 1 downTo 0) {
                layoutPrefetchRegistry.addPosition(i, 0)
            }
        }
    }
}

内存优化

  1. 共用RecyclerViewPool

假如多个 RecycledViewAdapter 是一样的,可以让RecyclerView之间同享一个RecycledViewPool以进步功用

// 创立一个同享的RecycledViewPool
val recycledViewPool = RecyclerView.RecycledViewPool()
// 设置同享的RecycledViewPool给多个RecyclerView
recyclerView1.setRecycledViewPool(recycledViewPool)
recyclerView2.setRecycledViewPool(recycledViewPool)

这种做法特别适用于多个RecyclerView之间的数据或布局结构有较大相似性的状况下,经过同享RecycledViewPool可以进一步进步功用。

  1. 运用Adapter.setHasStableIds(true)进步Item稳定性

设置Adapter的setHasStableIds(true)可以进步Item的稳定性,协助RecyclerView更好地辨认和复用ViewHolder,防止频频创立和毁掉ViewHolder,削减内存耗费。

adapter.setHasStableIds(true)
  1. 运用RecyclerView.setItemViewCacheSize(size)设置缓存巨细

经过设置RecyclerView的setItemViewCacheSize(size)办法来设置缓存巨细,可以控制RecyclerView中缓存ViewHolder的数量,防止过多的缓存占用过多内存。

recyclerView.setItemViewCacheSize(20) // 设置缓存巨细为20
  1. 同享事情

例如点击事情,可以创立一个共用的监听器对象,并将其设置给所有的ItemView。然后依据ID来区别履行不同的操作。从而防止了对每个Item都创立监听器对象,优化了资源耗费。

// 共用的监听器对象
val itemClickListener = View.OnClickListener { view ->
    // 依据view的ID来履行不同的操作
    when (view.id) {
        R.id.button -> {
            // 履行按钮点击操作
        }
        R.id.imageView -> {
            // 履行图片点击操作
        }
        // 其他ID的处理...
    }
}
// 在ViewHolder中为ItemView设置共用的监听器
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    init {
        // 为所有需求的ItemView设置共用的监听器
        itemView.setOnClickListener(itemClickListener)
    }
}
  1. 重写RecyclerView.onViewRecycled()收回资源

onViewRecycled(holder: ViewHolder) 办法中,咱们可以履行一些资源开释操作,例如开释ViewHolder中的图片资源、移除监听器等,以便在ViewHolder被收回时及时开释相关资源,防止内存走漏和资源浪费。

override fun onViewRecycled(holder: ViewHolder) {
    super.onViewRecycled(holder)
    // 开释ViewHolder中的图片资源
    holder.imageView.setImageDrawable(null)
    // 移除ViewHolder中的监听器
    holder.itemView.setOnClickListener(null)
}

总结

经过挑选适宜的优化布局、削减制作、滑动优化、预加载与内存优化战略,可以有效进步RecyclerView的功用,使其在各种状况下都能坚持流通。在实践开发中,还需求依据具体状况挑选适宜的优化战略,并进行恰当的测试和调整,以达到最佳的功用作用。

推荐

android_startup: 提供一种在运用发动时可以愈加简略、高效的方法来初始化组件,优化发动速度。不仅支撑Jetpack App Startup的悉数功用,还提供额定的同步与异步等候、线程控制与多进程支撑等功用。

AwesomeGithub: 根据Github的客户端,纯练习项目,支撑组件化开发,支撑账户暗码与认证登陆。运用Kotlin语言进行开发,项目架构是根据JetPack&DataBinding的MVVM;项目中运用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等盛行开源技术。

flutter_github: 根据Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合具体的Demo来全面解析Android相关的知识点, 协助读者可以更快的把握与理解所论述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。