记一例 Android 无障碍服务(Accessibility)引发的崩溃
Logs
来自线上用户的一个神奇崩溃,日志如下:
java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0
at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1330)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:684)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:676)
at android.view.accessibility.AccessibilityNodeInfo.setText(AccessibilityNodeInfo.java:2645)
at android.widget.TextView.onInitializeAccessibilityNodeInfoInternal(TextView.java:11652)
at android.view.View.onInitializeAccessibilityNodeInfo(View.java:8257)
at android.view.View.createAccessibilityNodeInfoInternal(View.java:8216)
at android.view.View.createAccessibilityNodeInfo(View.java:8201)
at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1204)
at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchAccessibilityNodeInfos(AccessibilityInteractionController.java:1029)
at android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread(AccessibilityInteractionController.java:341)
at android.view.AccessibilityInteractionController.access$400(AccessibilityInteractionController.java:75)
at android.view.AccessibilityInteractionController$PrivateHandler.handleMessage(AccessibilityInteractionController.java:1393)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
当用户触发无障碍服务 (Accessibility) 时,会遍历可点击的 TextView。TextView 再去创建 AccessibilityNodeInfo 传递给无障碍服务,但 AccessibilityNodeInfo 在获取文本用于构造 SpannableStringBuilder 时却发生了异常—— java.lang.IndexOutOfBoundsException
Why?
下面这段摘抄自 AccessibilityNodeInfo.java 的代码告诉了我们原因:
1 | public void setText(CharSequence text) { |
上述代码关键是在替换 text中的 ClickableSpan 对象为 AccessibilityURLSpan或者AccessibilityClickableSpan:
- 首先,从原始的
text中获取的ClickableSpan对象数组spans。 - 其次,遍历获取每个
ClickableSpan在原始text中的位置。 - 最后,替换掉
Spannable对应位置的ClickableSpan。
崩溃就发生最最后一步 spannable.setSpan(...)。程序执行到这里的时候, spanToReplaceStart和spanToReplaceEnd都是 -1,就是说对应的 ClickableSpan 在经过 SpannableStringBuilder 拷贝后不见了 !!
why ???




