温馨提示:本文翻译自stackoverflow.com,查看原文请点击:android - Room livedata with paging getting IndexOutOfBoundsException in some devices
android kotlin android-room android-paging

android - 在某些设备中,带有分页的会议室实时数据获取IndexOutOfBoundsException

发布于 2020-03-27 12:01:06

我正在使用带有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?

查看更多

查看更多

提问者
Jordy Mendoza
被浏览
223
Ahmed na 2020-02-14 11:37

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'

闪存问题已解决