开启辅助功能引起的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信息