最近我发现我的应用程序奇怪的崩溃。我发现,他们是由ListAdapter
造成的 - > DiffUtil
下方。合同说,只有当areContentsTheSame
返回相应的项目真正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
计算在后台线程diff和JUnit
处理断言只在主线程异常
我已经得到来自谷歌作出回应,他们确认有臭虫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
}
}
将更新回答问题时,解决武功