Android SpannableStringBuilder抛出IndexOutOfBoundsException崩溃问题求解
根因定位
这个是Android 8.0(API 26)~ Android 12(API 31)的系统原生无障碍模块bug,触发逻辑为:当继承自TextView的组件(含CheckBox、Button)被设置空文本(""/null)时,若无障碍服务(读屏软件、自动点击工具、带无障碍权限的安全软件等)正在采集视图节点信息,系统内部计算Span起止下标时异常返回-1,最终触发越界崩溃。
此前设置android:importantForAccessibility="no"未生效的原因是:部分厂商定制的无障碍服务会强制采集所有视图节点,无视单个组件的无障碍属性配置。
排查思路
- 排查当前页面所有TextView、CheckBox、Button组件的赋值逻辑,确认是否存在动态设置空文本的场景,包括直接调用
setText("")、setText(null)、数据绑定框架自动给空字段赋值为空文本的场景 - 提取崩溃用户的特征信息,确认是否90%以上的崩溃用户都开启了无障碍权限,进一步验证根因
- 可通过Hook或者字节码插桩的方式给所有TextView的无障碍方法加日志,确认崩溃触发时对应的组件文本内容,缩小问题范围
可用解决方案
1. 最快兜底方案(无业务侵入,立即可用)
自定义安全的TextView、CheckBox、Button组件,重写无障碍相关方法捕获系统异常,避免崩溃:
// 安全TextView示例,CheckBox、Button按相同逻辑继承对应父类重写即可 class SafeTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatTextView(context, attrs, defStyleAttr) { override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) { try { super.onInitializeAccessibilityNodeInfo(info) } catch (ignore: IndexOutOfBoundsException) { // 捕获系统异常,不做额外处理 } } override fun onInitializeAccessibilityEvent(event: AccessibilityEvent) { try { super.onInitializeAccessibilityEvent(event) } catch (ignore: IndexOutOfBoundsException) { // 捕获系统异常,不做额外处理 } } }
将布局文件中对应组件全部替换为自定义组件即可,无需修改其他业务逻辑。
2. 根因规避方案
如果排查到确实存在动态设置空文本的逻辑,将空文本替换为单个空格:setText(" "),避免系统计算Span时出现负下标的情况,从触发条件上规避bug。
3. 全局禁用无障碍方案(仅适用于不需要支持无障碍功能的页面)
在对应Activity的onCreate方法中给顶层视图设置全局无障碍隐藏属性,优先级高于单个组件的配置,大部分厂商定制服务也会遵守该规则:
// Java示例 getWindow().getDecorView().setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
// Kotlin示例 window.decorView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
内容的提问来源于stack exchange,提问作者Milan Cerovský
相关产品推荐
相关产品推荐

