我正在使用带有Room livedata的分页库,在某些设备中有时会收到IndexOutOfBoundsException索引超出范围-传递的位置= 75,旧列表大小= 75(始终为75,我的数据库都没有固定大小为75)
问题是日志显示的更像是内部错误而不是我的应用程序错误
致命异常:java.lang.IndexOutOfBoundsException:索引超出范围-传递的位置= 75,旧列表大小= 75
at androidx.recyclerview.widget.DiffUtil$DiffResult.convertOldPositionToNew(DiffUtil.java:672)
at androidx.paging.PagedStorageDiffHelper.transformAnchorIndex(PagedStorageDiffHelper.java:215)
at androidx.paging.AsyncPagedListDiffer.latchPagedList(AsyncPagedListDiffer.java:382)
at androidx.paging.AsyncPagedListDiffer$2$1.run(AsyncPagedListDiffer.java:345)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6543)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:440)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:810)
Is there a workaround for this? or is something related to my app?
OLD ANSWER (recommended 14/2/2020 ):
After many days investigating the option that causes this error is this :
protected val pagedListConfig: PagedList.Config = PagedList.Config.Builder()
.setEnablePlaceholders(true) . <------------ set to false
.setPrefetchDistance(PREFETCH_DISTANCE_SIZE)
.setPageSize(DATABASE_PAGE_SIZE)
.setInitialLoadSizeHint(INITIAL_DATABASE_PAGE_SIZE)
.build()
so just set setEnablePlaceholders(false)
should avoid this case from happing
noting that this will affect the scrolling causing it to flash when loading more data , until a fix from android sdk.
UPDATED 1 (not recommended, see update 2):
The issue happens mainly when restoring a destroyed activity / fragment
for my case i have 4 fragments, I was refreshing all the lists at once after restoring, meaning i insert new data causing the observable to be called multiple times
causing the call of mDiffer.submitList to be called multiple, by right this should not crash as mDiffer do all the work in the background
but my guess that there is a synchronisation issue between the mDiffer Runnables
so my solution was to minimizethe mDiffer.submitList calls by the following methods : - save/restore items tab fragments:
public override fun onSaveInstanceState(savedInstanceState: Bundle) {
super.onSaveInstanceState(savedInstanceState)
supportFragmentManager.putFragment(savedInstanceState,
"itemsFragment1", itemsFragment1)
}
public override fun onRestoreInstanceState(savedInstanceState:
Bundle)
{
super.onRestoreInstanceState(savedInstanceState)
newsMainFragment =
supportFragmentManager.getFragment(savedInstanceState,
"itemsFragment1") as ItemsFragment1?
}
Make sure only one list to be submit at time (ignoring too much changes and keep last one only):
abstract class MyPagedListAdapter<T, VH : RecyclerView.ViewHolder>
(diffCallback: DiffUtil.ItemCallback<T>) : PagedListAdapter<T, VH>
.(diffCallback) {
var submitting = false
val latestPage: Queue<PagedList<T>?> = LinkedList()
val runnable = Runnable {
synchronized(this) {
submitting = false
if (latestPage.size > 0) {
submitFromLatest()
}
}
}
override fun submitList(pagedList: PagedList<T>?) {
synchronized(this) {
if(latestPage.size > 0){
Log.d("MyPagedListAdapter","ignored ${latestPage.size}")
}
latestPage.clear()
latestPage.add(pagedList)
submitFromLatest()
}
}
fun submitFromLatest() {
synchronized(this) {
if (!submitting) {
submitting = true
if (latestPage.size > 1 || latestPage.size < 1) {
Crashlytics.logException(Throwable("latestPage size is ${latestPage.size}"))
Log.d("MyPagedListAdapter","latestPage size is ${latestPage.size}")
}
super.submitList(latestPage.poll(), runnable)
}
}
}
}
UPDATED 2 (fix but not recommended, see update 3):
当尝试Handler.createAsync
使用旧的SDK 调用某些旧设备将创建同步处理程序,这将导致崩溃,此修补程序将捕获错误,但不能阻止该问题的发生,因为它与Android SDK中的Handler类相关
在消耗的适配器中PagedListAdapter
添加以下内容:
init {
try {
val mDiffer = PagedListAdapter::class.java.getDeclaredField("mDiffer")
val excecuter = AsyncPagedListDiffer::class.java.getDeclaredField("mMainThreadExecutor")
mDiffer.isAccessible = true
excecuter.isAccessible = true
val myDiffer = mDiffer.get(this) as AsyncPagedListDiffer<*>
val foreGround = object : Executor {
val mHandler = createAsync(Looper.getMainLooper())
override fun execute(command: Runnable?) {
try {
mHandler.post {
try {
command?.run()
} catch (e: Exception) {
e.printStackTrace()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
excecuter.set(myDiffer, foreGround)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun createAsync(looper: Looper): Handler {
if (Build.VERSION.SDK_INT >= 28) {
return Handler.createAsync(looper)
}
if (Build.VERSION.SDK_INT >= 16) {
try {
return Handler::class.java.getDeclaredConstructor(Looper::class.java, Handler.Callback::class.java,
Boolean::class.javaPrimitiveType)
.newInstance(looper, null, true)
} catch (ignored: IllegalAccessException) {
} catch (ignored: InstantiationException) {
} catch (ignored: NoSuchMethodException) {
} catch (e: InvocationTargetException) {
return Handler(looper)
}
}
return Handler(looper)
}
另外,如果您使用pro-guard或R8,请添加以下规则:
-keep class androidx.paging.PagedListAdapter.** { *; }
-keep class androidx.paging.AsyncPagedListDiffer.** { *; }
ofc这是一种解决方法,因此在更新sdk时,请注意命名不会因反射而更改。
更新3(14/2/2020):
用 :
setEnablePlaceholders(false)
但是将分页库更新为:
implementation 'androidx.paging:paging-runtime-ktx:2.1.1'
闪存问题已解决