android - DiffUtil 违反 areContentTheSame 的合同 [下一个版本中的修复]
问题描述
最近我在我的应用程序中发现了奇怪的崩溃。我发现它们是由ListAdapter
->DiffUtil
下面引起的。合同说只有在相应项目返回trueareContentsTheSame
时才会调用回调。对从未调用过的项目调用问题。areItemsTheSame
areContentsTheSame
areItemsTheSame
我正在对String
项目进行测试,因此它不应该与我自己的回收站实施相关。如果是我的错(现在几乎没有逻辑)或DiffUtil
工具中的错误,我真的很困惑
我创建了简单的仪器测试,但在提到的情况下失败了 - 有经验的人可以看看:
package com.example.diffutilbug
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
internal class ExampleUnitTest {
@Test
fun testDiffUtil4() {
val handler = CoroutineExceptionHandler { _, exception ->
throw exception
}
// adapter compare items :
// areItemsTheSame -> compare length of String
// areContentsTheSame -> compare content with ==
val adapter = StringAdapterJunit(handler)
runBlocking {
adapter.submitList(
mutableListOf<String>(
"1",//1,
"22",//2,
"333",//3,
"4444",//4,
"55555",//5,
"666666",//6,
"7777777",//7,
"88888888",//8,
"999999999",//9,
"55555",//5,
"1010101010",//10,
"1010109999",//10,
"55555",//5,
"1313131313",//10,
"1414141414",//10,
"55555",//5,
"1313131313",//10,
"1414141414",//10,
"55555"//5
)
)
delay(40)
adapter.submitList(
mutableListOf<String>(
"55555",//5,
"1010101010",//10,
"1010109999",//10,
"55555",//11,
"1313131313",//10,
"1414141414",//10,
"11111111111"//11
)
)
delay(500)
}
}
}
// Stub Adapter for Strings that uses DiffUtil underneath.
// logs all callbacks to logcat
class StringAdapterJunit(val handler: CoroutineExceptionHandler) : ListAdapter<String, RecyclerView.ViewHolder>(object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
Log.e("DiffUtilTest", "areItemsTheSame comparing $oldItem with $newItem = ${oldItem.length == newItem.length}")
return oldItem.length == newItem.length
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
//should be called only if areContentsTheSame == true
Log.e(
"DiffUtilTest",
"areContentsTheSame error = ${oldItem.length != newItem.length} comparing $oldItem with $newItem"
)
runBlocking {
GlobalScope.launch(handler + Dispatchers.Main) {
assertTrue("areContentsTheSame can be called only if areItemsTheSame return true" , areItemsTheSame(oldItem, newItem))
}.join()
}
return oldItem == newItem
}
override fun getChangePayload(oldItem: String, newItem: String): Any? {
//should be called only if areItemsTheSame = true and areContentsTheSame = false
Log.e(
"DiffUtilTest",
"getChangePayload error = ${oldItem.length == newItem.length && oldItem == newItem} $oldItem with $newItem"
)
return null
}
}) {
// stub implementation on adapter - never used
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = object : RecyclerView.ViewHolder(View(null)) {}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}
override fun getItemViewType(position: Int): Int = getItem(position).length
}
以及它所需的 gradle 依赖项:
dependencies {
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
//coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
}
请注意,您需要添加
android.useAndroidX=true
android.enableJetifier=true
在你的gradle.properties
添加了协程和异常处理程序,因为DiffUtil
在后台线程上计算差异并JUnit
仅在主线程上处理断言
==================================================== ===
在下一个 alpha 中修复:将在 alpha 3 中发布 - PR 以照顾https://android-review.googlesource.com/c/platform/frameworks/support/+/1253271 谢谢,等不及要删除所有解决方法!
解决方案
我得到了谷歌的回复,他们确认DiffUtil
列表包含重复项(空值、相同对象等)时存在错误
我目前的解决方法是在执行之前自己检查“合同”,所以:
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
return compare items
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
//should be called only if areContentsTheSame == true
return areItemsTheSame(oldItem, newItem) && compare items contents
}
override fun getChangePayload(oldItem: String, newItem: String): Any? {
//should be called only if areItemsTheSame = true and areContentsTheSame = false
if(areItemsTheSame(oldItem, newItem) && !areContentsTheSame(oldItem, newItem)) {
return compute changePayload
} else {
return null
}
}
问题解决后将更新答案
推荐阅读
- javascript - 当父链接为(#)时如何从第一个孩子插入第一个孩子的链接
- javascript - JavaScript ES6 到基本 JavaScript
- python - Errno 24 附加到大型 CSV 文件时打开的文件过多
- javascript - d3 强制链接未连接
- ios - Xcode 不会在模拟器上运行应用程序
- javascript - 一个框架
在某些设备的某些浏览器上失真 - javascript - react-draft-wysiwyg 从动态创建的编辑器更新编辑器状态
- android - WorkContinuation 的第二个 WorkRequest 不起作用
- c# - 扩展继承的属性 c#
- javascript - 使用移动设备/其他笔记本电脑从本地主机登录 Google 登录以进行测试