利用DiffUtil提高应用性能
移动应用程序的性能是决定用户体验的最重要方面之一。性能指的是应用程序在各种事件中加载和刷新数据的能力。
当你想在RecyclerView中更新数据时,你可能会使用notifyOnDataSetChanged() 或notifyOnItemPositionChanged() 方法。虽然这些方法在处理少量数据时效果很好,但对于你的应用程序来说,更新和完全重新加载大量的数据会变得很麻烦。
这很容易导致ANR(App Not Responding)异常,使你的应用程序的用户感到烦恼。一个恼火的用户总是会给出负面的反馈
通过使用DiffUtil ,我们可以轻松地处理数据集的变化,并即时反映在RecyclerView上。
DiffUtil是一个实用类,它计算两组数据之间的变化,并返回一个更新操作的列表,将旧列表改为新列表。它使用Eugene W. Myers的算法来计算将一个数据集转换为另一个数据集需要多少次更新。
由于Myers的技术不能处理已经被移动的对象,DiffUtil在输出上做了第二次尝试,以识别已经被重新定位的元素。这就节省了时间和设备的资源,同时为用户提供了更好的体验。
前提条件
本文假设你有以下知识。
- 使用[Android Studio]创建Android应用程序。
- [Kotlin编程语言]的基础知识。
- 在Android中使用命令式范式--使用
XML和视图组,如[Constraint Layout]。 - 视图绑定和/或[数据绑定]。
- [安卓回收器视图]的基础知识。
建立一个Android项目
打开Android Studio,用你选择的模板创建一个项目。确保你选择的API版本不低于16,以便使你的应用与DiffUtil兼容。
启用viewBinding
ViewBinding允许我们使用各自的绑定类从XML 布局文件访问视图和视图组。
// in the build.gradle file (module level)
android{
buildFeatures{
viewBinding true
}
}
设置RecyclerView
在这一点上,我们将创建一个RecyclerView,并将其与核心组件设置在一起。
用户界面
打开你的XML 布局文件,粘贴以下代码。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--NOTE: This file is named activity_main.xml-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<Button
android:id="@+id/btnUpdate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/update_data"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/recyclerview"
app:layout_constraintStart_toStartOf="@id/recyclerview"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.96" />
</androidx.constraintlayout.widget.ConstraintLayout>
这包括一个按钮,当点击时,将对Recyclerview进行更新操作。
行项目
这是决定Recyclerview中数据表示格式的单元构建块。出于兼容性和必要性的考虑,它也会跟随应用程序中的数据模型。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp">
<!--NOTE: This file is named student_card.xml-->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<TextView
android:id="@+id/studentId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.25"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="00" />
<TextView
android:id="@+id/studentName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.25"
app:layout_constraintStart_toEndOf="@id/studentId"
app:layout_constraintTop_toTopOf="parent"
tools:text="Example Name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
在这里,我们创建了一个学生项目卡,它持有两个textViews。
应用程序的数据模型
一个数据集定义了适配器工作的数据对象的类型。创建一个名为Student 的Koltin数据类,并粘贴以下内容。
data class Student(
val id: String,
val name: String
)
回收器视图适配器
适配器完成了将数据集连接到Recyclerview的艰巨任务。创建一个名为StudentAdapter 的Kotlin类并粘贴以下内容。
class StudentAdapter : RecyclerView.Adapter<StudentAdapter.CardViewHolder>() {
private var oldList = emptyList<Student>()
inner class CardViewHolder(val card: StudentCardBinding) : RecyclerView.ViewHolder(card.root)
override fun getItemCount(): Int = oldList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
return CardViewHolder(
StudentCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
holder.card.apply {
studentId.text = oldList[position].id
studentName.text = oldList[position].name
}
}
}
我们以后会用这个适配器来实现DiffUtil回调。
创建DiffUtil回调类
现在,继续创建一个名为MyDiffUtil 的Kotlin类并粘贴以下内容。
class MyDiffUtil(
private val oldList: List<Student>,
private val newList: List<Student>
) : DiffUtil.Callback() { // extending the Callback class
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].id == newList[newItemPosition].id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return when {
oldList[oldItemPosition].id == newList[newItemPosition].id -> true
oldList[oldItemPosition].name == newList[newItemPosition].name -> true
else -> false
}
}
}
了解DiffUtil的回调
getOldListSize返回在类的构造函数中传递的旧列表的大小。getNewListSize返回构造函数中传递的新或更新列表的大小。areItemsTheSame返回一个布尔值,即相应位置的项目是否相同。areContentsTheSame检查相应位置上的项目的内容是否相同。
注意: oldList和newList都应该持有相同类型的元素。
将DiffUtil附加到一个适配器上
导航到StudentAdapter.kt 文件,并创建一个公共函数,我们将用它来向适配器分配更新。
fun setData(newList: List<Student>) {
val diffUtil = MyDiffUtil(oldList, newList)
val diffUtilResults = DiffUtil.calculateDiff(diffUtil)
oldList = newList
diffUtilResults.dispatchUpdatesTo(this)
}
使用适配器
适配器已经准备好被使用了。如下图所示,把它附加到Recyclerview上。
class MainActivity : AppCompatActivity() {
private var binding: ActivityMainBinding? = null
private val studentAdapter: StudentAdapter = StudentAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding!!.root)
updateDataWhenClicked()
binding!!.recyclerview.apply {
val newList: List<Student> = listOf(
Student("1", "Milan Gans"),
Student("2", "Renda Slaugh"),
Student("3", "Basil Levison"),
Student("4", "Tina Travers"),
)
adapter = studentAdapter.also { adapter ->
adapter.setData(newList)
}
}
}
private fun updateDataWhenClicked() {
binding!!.btnUpdate.setOnClickListener {
val newList: List<Student> = listOf(
Student("1", "Milan Gans"),
Student("2", "Renda Slaugh"),
Student("3", "Basil Levison"),
Student("4", "Tina Travers"),
Student("5", "Lil Mosey"),
Student("6", "Maurine Alexa"),
)
studentAdapter.setData(newList)
}
}
}
在这里,我们用一个假的列表初始化了适配器。当更新按钮被点击时,RecyclerView就会被更新为一个新的假列表。
提示:当在一个生产项目上工作时,你应该从一个数据库中获取数据,最好是[房间数据库]或[远程数据库]。
测试应用程序
运行应用程序后,你应该看到与此类似的东西。

注意,只有新的项目被更新。这样一来,设备的资源,如电池和内存,就被节省了。因此,用户有一个快速和无缝的体验。
结论
就这样吧!你现在可以使用DiffUtil提高你的应用程序性能。