getParcelable() 由于 AGP 8 中的 ifTable 为空而崩溃

idu*_*olz 3 android parcelable android-r8 android-gradle-plugin-8.0

我最近更新到了 AGP 8,并在 Play 商店上发布了更新。我收到一些最近未更改的代码区域发生崩溃的报告。我相当有信心这是由 AGP 8 升级引起的崩溃,特别是在 R8 全模式周围。

崩溃周围的代码正在恢复布局管理器的状态。

恢复代码

if (savedInstanceState != null) {
    oldRecyclerLayoutState = savedInstanceState.getParcelableCompat(
        SIS_RECYCLER_LAYOUT_STATE,
    )
}
Run Code Online (Sandbox Code Playgroud)

保存实例代码

private lateinit var layoutManager: LinearLayoutManager
...
override fun onSaveInstanceState(outState: Bundle) {
    outState.putParcelable(SIS_RECYCLER_LAYOUT_STATE, layoutManager.onSaveInstanceState())
    super.onSaveInstanceState(outState)

}
Run Code Online (Sandbox Code Playgroud)

Class.isAssignableFrom()崩溃报告表明,读取 Parcelable 会崩溃,因为调用类时其 ifTable 为 null 。从AOSP中,我可以看到 anifTable是一个接口表。我假设isAssignableFrom正在使用 来ifTable确定可分配性,但是由于类为空,它无法读取它。不幸的是,该错误没有告诉我哪个类为空。

完整的堆栈跟踪:

Fatal Exception: java.lang.NullPointerException: Attempt to read from field 'java.lang.Object[] java.lang.Class.ifTable' on a null object reference in method 'boolean java.lang.Class.isAssignableFrom(java.lang.Class)'
       at java.lang.Class.isAssignableFrom(Class.java:579)
       at android.os.Parcel.readParcelableCreatorInternal(Parcel.java:4865)
       at android.os.Parcel.readParcelableInternal(Parcel.java:4778)
       at android.os.Parcel.readValue(Parcel.java:4544)
       at android.os.Parcel.readValue(Parcel.java:4324)
       at android.os.Parcel.-$$Nest$mreadValue()
       at android.os.Parcel$LazyValue.apply(Parcel.java:4422)
       at android.os.Parcel$LazyValue.apply(Parcel.java:4381)
       at android.os.BaseBundle.getValueAt(BaseBundle.java:394)
       at android.os.BaseBundle.getValue(BaseBundle.java:374)
       at android.os.BaseBundle.getValue(BaseBundle.java:357)
       at android.os.BaseBundle.get(BaseBundle.java:696)
       at android.os.Bundle.getParcelable(Bundle.java:947)
       at com.ggstudios.lolcatalyst.util.ext.BundleExtKt.getParcelableCompat(BundleExt.kt:21)
       at com.ggstudios.lolcatalyst.summonerlookup.SummonerProfileFragment.onViewCreated(SummonerProfileFragment.kt:422)
       at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3137)
       at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
       at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
       at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
       at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1435)
       at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2979)
       at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2890)
       at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3138)
       at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
       at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
       at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
       at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1435)
       at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2979)
       at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2897)
       at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:263)
       at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:351)
       at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:251)
       at com.ggstudios.lolcatalyst.activity.abs.BaseActivity.onStart(BaseActivity.kt:90)
       at com.ggstudios.lolcatalyst.activity.MainActivity.onStart(MainActivity.kt)
       at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1510)
       at android.app.Activity.performStart(Activity.java:8603)
       at android.app.ActivityThread.handleStartActivity(ActivityThread.java:4191)
       at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
       at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
       at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
       at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2571)
       at android.os.Handler.dispatchMessage(Handler.java:106)
       at android.os.Looper.loopOnce(Looper.java:226)
       at android.os.Looper.loop(Looper.java:313)
       at android.app.ActivityThread.main(ActivityThread.java:8741)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
Run Code Online (Sandbox Code Playgroud)

如果有人知道原因或解决方法是什么,将不胜感激!

idu*_*olz 9

tl;dr 修复方法是使用BundleCompat.getParcelable(Bundle, String, Class)fromandroidx.core:core-ktx:1.10.0而不是getParcelable(String, Class).

我认为这次事故是由两件事不幸结合造成的。

  1. AGP 8 默认启用 R8 的完整模式。R8 的完整模式剥离了默认构造函数并执行更积极的优化。
  2. getParcelable(String, Class)如果 Parcelable 没有以特定方式定义,则 API 33 中引入的新方法会存在一些错误。此处记录了这一点。

看来getParcelable(String, Class)R8 优化某些代码的脆弱实现导致了这次崩溃。目前的修复似乎是使用getParcelable(String)API 33 中的旧方法。Google 表示他们已经getParcelable(String, Class)在 API 34 中修复了该问题。

更新androidx.core:core-ktx:1.10.0包含此问题的修复。它包含BundleCompat.getParcelable(Bundle, String, Class)只会getParcelable(Bundle, Class)在Android U及以上版本上调用新功能。