目前我在 FirebaseCrashlytics 中看到很多
NullpointerExceptions
。 NPE 出现在 AndroidComposeView.kt 的这一行中。问题可能是TalkBack 结合AndroidView
引起的,但我无法在本地重现。这是一个已知的问题,没有任何解决方法:
这是堆栈跟踪:
androidx.compose.ui.platform.AndroidComposeView$addAndroidView$1.onInitializeAccessibilityNodeInfo (AndroidComposeView.android.kt:712)
androidx.core.view.AccessibilityDelegateCompat$AccessibilityDelegateAdapter.onInitializeAccessibilityNodeInfo (AccessibilityDelegateCompat.java:91)
android.view.View.onInitializeAccessibilityNodeInfo (View.java:9095)
android.view.View.createAccessibilityNodeInfoInternal (View.java:9056)
android.view.View$AccessibilityDelegate.createAccessibilityNodeInfo (View.java:32397)
android.view.View.createAccessibilityNodeInfo (View.java:9039)
android.view.AccessibilityInteractionController.populateAccessibilityNodeInfoForView (AccessibilityInteractionController.java:440)
android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread (AccessibilityInteractionController.java:383)
android.view.AccessibilityInteractionController.-$$Nest$mfindAccessibilityNodeInfoByAccessibilityIdUiThread (AccessibilityInteractionController.java)
android.view.AccessibilityInteractionController$PrivateHandler.handleMessage (AccessibilityInteractionController.java:1713)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8757)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:571)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1067)
是否有解决方法,或者更好的方法来避免此异常?
现在,我们找到了解决方法。我们仍然使用 FirebaseCrashlytics 跟踪异常作为非致命异常,我们看到它有效。这是解决方法
CrashFixedAndroidView.kt
:
/**
* Workaround to fix NullPointerException in AndroidComposeView$addAndroidView$1.onInitializeAccessibilityNodeInfo().
* It can be used like the default AndroidView.
*/
@Composable
fun <T : View> CrashFixedAndroidView(
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
) {
val crashFix = remember { AccessibilityCrashFix() }
AndroidView(
factory = factory,
modifier = modifier.onPlaced { crashFix.applyFix() },
update = { view ->
crashFix.updateView(view)
update(view)
}
)
}
/**
* Gets the existing AccessibilityDelegate and wraps it in another AccessibilityDelegate to surround its call with a try catch.
*/
private class AccessibilityCrashFix {
private var androidViewReference: WeakReference<View>? = null
private var isFixApplied = false
fun updateView(view: View) {
if (androidViewReference != null) {
return
}
val androidView = view.parent as? View ?: return
androidViewReference = WeakReference(androidView)
}
fun applyFix() {
if (isFixApplied) {
// Prevents multiple wrapping
return
}
val androidView = androidViewReference?.get() ?: return
val crashDelegate = ViewCompat.getAccessibilityDelegate(androidView)
if (crashDelegate != null) {
val catchedDelegate = catchedDelegate(crashDelegate)
ViewCompat.setAccessibilityDelegate(androidView, catchedDelegate)
}
isFixApplied = true
}
private fun catchedDelegate(crashDelegate: AccessibilityDelegateCompat): AccessibilityDelegateCompat {
return object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfoCompat
) {
try {
crashDelegate.onInitializeAccessibilityNodeInfo(host, info)
} catch (e: Exception) {
Log.e(Tags.TAG_SYS, "Catched accessibility crash", e)
FirebaseCrashlytics.getInstance().recordException(CatchedAccessibilityException(e))
}
}
}
}
class CatchedAccessibilityException(cause: Throwable): Exception("Catched crash", cause)
}