背景
闲来无事,敲代码,忽然一个Exception引起了我的注意。
我们在设置中打开拼写检查工具功能的时候,SuggesttionsPopupWindow这个系统弹窗,就会在我们编辑EditText的时候,自动弹出,提示我们补全。
分析
看trackstack的信息,应该是点击其中一项候选词的时候发生的奔溃。让我们先看看SuggesttionsPopupWindow在处理点击事件的时候做了什么。
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SuggestionInfo suggestionInfo = mSuggestionInfos[position];
replaceWithSuggestion(suggestionInfo);
hideWithCleanUp();
}
代码很简洁,我们继续看看replaceWithSuggestion
这个方法,代码挺长的,只保留重点
private void replaceWithSuggestion(@NonNull final SuggestionInfo suggestionInfo) {
···
//suggestion是点击选中的候选词
final String suggestion = suggestionInfo.mText.subSequence(
suggestionStart, suggestionEnd).toString();
//替换
mTextView.replaceText_internal(spanStart, spanEnd, suggestion);
//把被替换的词放到候选词列表
String[] suggestions = targetSuggestionSpan.getSuggestions();
suggestions[suggestionInfo.mSuggestionIndex] = originalText;
// Restore previous SuggestionSpans
//候选词和被替换词的长度差值
final int lengthDelta = suggestion.length() - (spanEnd - spanStart);
for (int i = 0; i < length; i++) {
// Only spans that include the modified region make sense after replacement
// Spans partially included in the replaced region are removed, there is no
// way to assign them a valid range after replacement
if (suggestionSpansStarts[i] <= spanStart && suggestionSpansEnds[i] >= spanEnd) {
//这里在计算setSpan_internal的时候end加上了候选词和被替换词的长度差值,其实就是默认
//候选词替换后可以完全显示,假如我们设置了长度限制,例如maxlength,就会引发上述的奔溃
mTextView.setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i],
suggestionSpansEnds[i] + lengthDelta, suggestionSpansFlags[i]);
}
}
// Move cursor at the end of the replaced word
final int newCursorPosition = spanEnd + lengthDelta;
//这里也有同样的问题
mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition);
}
解决方案
因此,这个问题的根本原因是,replaceWithSuggestion这个方法,并没有考虑EditText限制长度的情况。既然如此,我们只好做一下边界保护。
public class SuggestionEditText extends EditText {
public SuggestionEditText(Context context) {
super(context);
}
public SuggestionEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SuggestionEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* Sets a span on the specified range of text
*/
protected void setSpan_internal(Object span, int start, int end, int flags) {
getText().setSpan(span, start, Math.min(end, getText().length()), flags);
}
/**
* Moves the cursor to the specified offset position in text
*/
protected void setCursorPosition_internal(int start, int end) {
Selection.setSelection(getText(), Math.min(start, getText().length()),
Math.min(end, getText().length()));
}
}
彩蛋
可能有人会说,这2个方法可是标记为hide,是不是不大稳当。那么还有保底一个方法就是设置InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
禁用该功能。