Жилые данные комнаты с разбивкой по страницам получают исключение IndexOutOfBoundsException на некоторых устройствах

Я использую библиотеку подкачки с Room Liveata, на некоторых устройствах иногда я получаю IndexOutOfBoundsException Index за пределами - пройденная позиция = 75, размер старого списка = 75 (всегда с размером 75, ни одна из моих баз данных не имеет фиксированного размера 75)

Проблема в том, что журнал больше похож на внутреннюю ошибку, чем на ошибку моего приложения.

Неустранимое исключение: java.lang.IndexOutOfBoundsException: индекс вне границ - пройденная позиция = 75, размер старого списка = 75

в androidx.recyclerview.widget.DiffUtil $ DiffResult.convertOldPositionToNew (DiffUtil.java:672)
в androidx.paging.PagedStorageDiffHelper.transformAnchorIndex (PagedStorageDiffHelper.java:215) AsferStorageDiffHelper.java:215 .java: 382)
на androidx.paging.AsyncPagedListDiffer $ 2 $ 1.run (AsyncPagedListDiffer.java:345)
на android.os.Handler.handleCallback (Handler.java:790)
на android.os .Handler.dispatchMessage (Handler.java:99)
на android.os.Looper.loop (Looper.java:164)
на android.app.ActivityThread.main (ActivityThread.java:6543)
на java.lang.reflect.Method.invoke (Method.java)
на com.android.internal.os.RuntimeInit $ MethodAndArgsCaller.run (RuntimeInit.java:440)
на com.android.internal.os .ZygoteInit.main (ZygoteInit.java:810)

Есть ли обходной путь для этого? или что-то связано с моим приложением?


person Jordy Mendoza    schedule 06.04.2019    source источник
comment
Можете ли вы опубликовать фрагмент кода, откуда возникает эта ошибка?   -  person tomerpacific    schedule 06.04.2019
comment
Пожалуйста, опубликуйте код, в котором произошло исключение.   -  person Elmira Frhn    schedule 06.04.2019
comment
В этом проблема, я не могу воспроизвести ошибку в своих тестах, но в отчетах об ошибках я получаю это исключение без ссылки на мое приложение.   -  person Jordy Mendoza    schedule 06.04.2019
comment
Я нашел корень исключения, оно вызвано AsyncPagedListDiffer submitList (), но я не могу использовать обходной путь, потому что исключение вызвано в потоке, поэтому я не могу использовать классический try catch   -  person Jordy Mendoza    schedule 07.04.2019
comment
Также начните получать эту ошибку после перехода на androidx   -  person Ahmed na    schedule 20.06.2019
comment
@JordyMendoza не удалось найти обходной путь?   -  person Ahmed na    schedule 22.06.2019
comment
Я сообщил об issueetracker.google.com/issues/135628748   -  person Ahmed na    schedule 25.06.2019


Ответы (1)


СТАРЫЙ ОТВЕТ (рекомендуется 14 февраля 2020 г.):

После многих дней изучения варианта, вызывающего эту ошибку, следующее:

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()

поэтому просто установите setEnablePlaceholders(false), чтобы избежать того, чтобы этот случай не заметил, что это повлияет на прокрутку, заставив ее мигать при загрузке дополнительных данных, пока не будет исправлено из android sdk.

ОБНОВЛЕНО 1 (не рекомендуется, см. обновление 2):
Проблема возникает в основном при восстановлении уничтоженной активности / фрагмента. В моем случае у меня 4 фрагмента, я обновлял все списки сразу после восстановление, то есть я вставляю новые данные, вызывая многократный вызов наблюдаемого, вызывая многократный вызов mDiffer.submitList, по праву это не должно сбой, поскольку mDiffer выполняет всю работу в фоновом режиме, но я предполагаю, что есть синхронизация проблема между mDiffer Runnables

поэтому я решил минимизировать вызовы mDiffer.submitList следующими способами:

  • сохранение / восстановление фрагментов вкладки элементов:

    общественное переопределение удовольствие onSaveInstanceState (savedInstanceState: Пачка) {super.onSaveInstanceState (savedInstanceState) supportFragmentManager.putFragment (savedInstanceState, itemsFragment1, itemsFragment1)} общественное переопределение удовольствие onRestoreInstanceState (savedInstanceState: Пачка) {super.onRestoreInstanceState (savedInstanceState) newsMainFragment = supportFragmentManager.getFragment (savedInstanceState , itemsFragment1) как ItemsFragment1? }

  • Убедитесь, что за раз отправляется только один список (игнорируя слишком много изменений и сохраняйте только последний):

      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)
              }
          }
        }
      } 
    

ОБНОВЛЕНИЕ 2 (исправлено, но не рекомендуется, см. обновление 3):

Это исправление обнаружит ошибку, но не предотвратит возникновение проблемы, поскольку она связана с классом обработчика из Android SDK, когда при попытке вызвать Handler.createAsync некоторые старые устройства со старым SDK создадут синхронизированный обработчик, который приведет к сбою

В вашем адаптере, который расширяет 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.02.2020):

использовать :

 setEnablePlaceholders(false)

но после обновления библиотеки подкачки до:

 implementation 'androidx.paging:paging-runtime-ktx:2.1.2'

проблема со вспышкой исправлена

person Ahmed na    schedule 03.07.2019