AccessibilityNodeInfo setText引起的ClickableSpan crash

开启辅助功能引起的Crash,报错信息如下

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:11695)
        at android.view.View.onInitializeAccessibilityNodeInfo(View.java:8386)
        at android.view.View.createAccessibilityNodeInfoInternal(View.java:8345)
        at android.view.View.createAccessibilityNodeInfo(View.java:8330)
 public void setText(CharSequence text) {
        enforceNotSealed();
        mOriginalText = text;
        // Replace any ClickableSpans in mText with placeholders
        if (text instanceof Spanned) {
            ClickableSpan[] spans =
                    ((Spanned) text).getSpans(0, text.length(), ClickableSpan.class);
            if (spans.length > 0) {
                Spannable spannable = new SpannableStringBuilder(text);
                for (int i = 0; i < spans.length; i++) {
                    ClickableSpan span = spans[i];
                    if ((span instanceof AccessibilityClickableSpan)
                            || (span instanceof AccessibilityURLSpan)) {
                        // We've already done enough
                        break;
                    }
                    int spanToReplaceStart = spannable.getSpanStart(span);
                    int spanToReplaceEnd = spannable.getSpanEnd(span);
                    int spanToReplaceFlags = spannable.getSpanFlags(span);
                    spannable.removeSpan(span);
                    ClickableSpan replacementSpan = (span instanceof URLSpan)
                            ? new AccessibilityURLSpan((URLSpan) span)
                            : new AccessibilityClickableSpan(span.getId());
                    spannable.setSpan(replacementSpan, spanToReplaceStart, spanToReplaceEnd,
                            spanToReplaceFlags);
                }
                mText = spannable;
                return;
            }
由于AccessibliltyNodeInfo 不是将text类型转换成SpannableString,而是新建一个SpannableStringBuilder对象: Spannable spannable = new SpannableStringBuilder(text);

spannable的Spans数组没有初始化是null,只有在第一次setSpan(obj what,start,end,...)的时候才能将what也就是 ClickableSpan和start end 存入到Spans数组内

// setSpan(obj what,start,end,...)
        mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
        mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
        mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
        mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
        mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
        invalidateIndex(mSpanCount);
        mSpanCount++;
        mSpanInsertCount++;
.....
 if (send) {
            restoreInvariants();
            sendSpanAdded(what, nstart, nend);
        }
//在restoreInvariants()中:
....
 for (int i = mLowWaterMark; i < mSpanCount; i++) {
            Integer existing = mIndexOfSpan.get(mSpans[i]);
            if (existing == null || existing != i) {
                mIndexOfSpan.put(mSpans[i], i);
            }
        }
        mLowWaterMark = Integer.MAX_VALUE;

这个时候mIndexOfSpan内才会存储mSpans的一系列参数,否则mIndexOfSpan就为null,所以在 SpannableStringBuilder中getSpanStart 会return-1,进而在checkSpan中报错

public int getSpanStart(Object what) {
        if (mIndexOfSpan == null) return -1;
        Integer i = mIndexOfSpan.get(what);
        return i == null ? -1 : resolveGap(mSpanStarts[i]);
    }

解决办法:直接在XML中设置 android:importantForAccessibility="noHideDescendants",防止AccessibilityNodeInfo获取View信息

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容