Performance improvements for long text edition.
Limit each parse to batches of a few words, to keep the UI thread responsive. Possible optimizations for the future: - SpellCheck in a thread, but that requires some locking mecanism - Only spell check what is visible on screen. Will require additional spans to tag the pieces of text. This is a cherry pick of 145656 into ICS-MR1 Patch Set 2: Make the Runnable shared and stop it when detached. Change-Id: Ibf8e98274bda84b7176aac181ff267fc1f1fa4cb
This commit is contained in:
@@ -43,7 +43,18 @@ import java.util.Locale;
|
|||||||
*/
|
*/
|
||||||
public class SpellChecker implements SpellCheckerSessionListener {
|
public class SpellChecker implements SpellCheckerSessionListener {
|
||||||
|
|
||||||
private final static int MAX_SPELL_BATCH_SIZE = 50;
|
// No more than this number of words will be parsed on each iteration to ensure a minimum
|
||||||
|
// lock of the UI thread
|
||||||
|
public static final int MAX_NUMBER_OF_WORDS = 50;
|
||||||
|
|
||||||
|
// Rough estimate, such that the word iterator interval usually does not need to be shifted
|
||||||
|
public static final int AVERAGE_WORD_LENGTH = 7;
|
||||||
|
|
||||||
|
// When parsing, use a character window of that size. Will be shifted if needed
|
||||||
|
public static final int WORD_ITERATOR_INTERVAL = AVERAGE_WORD_LENGTH * MAX_NUMBER_OF_WORDS;
|
||||||
|
|
||||||
|
// Pause between each spell check to keep the UI smooth
|
||||||
|
private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
|
||||||
|
|
||||||
private final TextView mTextView;
|
private final TextView mTextView;
|
||||||
|
|
||||||
@@ -71,6 +82,8 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
|
|
||||||
private TextServicesManager mTextServicesManager;
|
private TextServicesManager mTextServicesManager;
|
||||||
|
|
||||||
|
private Runnable mSpellRunnable;
|
||||||
|
|
||||||
public SpellChecker(TextView textView) {
|
public SpellChecker(TextView textView) {
|
||||||
mTextView = textView;
|
mTextView = textView;
|
||||||
|
|
||||||
@@ -141,6 +154,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
mSpellParsers[i].finish();
|
mSpellParsers[i].finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mSpellRunnable != null) {
|
||||||
|
mTextView.removeCallbacks(mSpellRunnable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int nextSpellCheckSpanIndex() {
|
private int nextSpellCheckSpanIndex() {
|
||||||
@@ -254,6 +271,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount);
|
System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount);
|
||||||
textInfos = textInfosCopy;
|
textInfos = textInfosCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
|
mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
|
||||||
false /* TODO Set sequentialWords to true for initial spell check */);
|
false /* TODO Set sequentialWords to true for initial spell check */);
|
||||||
}
|
}
|
||||||
@@ -288,14 +306,30 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleNewSpellCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleNewSpellCheck() {
|
||||||
|
if (mSpellRunnable == null) {
|
||||||
|
mSpellRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
final int length = mSpellParsers.length;
|
final int length = mSpellParsers.length;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
final SpellParser spellParser = mSpellParsers[i];
|
final SpellParser spellParser = mSpellParsers[i];
|
||||||
if (!spellParser.isFinished()) {
|
if (!spellParser.isFinished()) {
|
||||||
spellParser.parse();
|
spellParser.parse();
|
||||||
|
break; // run one spell parser at a time to bound running time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
mTextView.removeCallbacks(mSpellRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
mTextView.postDelayed(mSpellRunnable, SPELL_PAUSE_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo,
|
private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo,
|
||||||
SpellCheckSpan spellCheckSpan) {
|
SpellCheckSpan spellCheckSpan) {
|
||||||
@@ -383,7 +417,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
// Iterate over the newly added text and schedule new SpellCheckSpans
|
// Iterate over the newly added text and schedule new SpellCheckSpans
|
||||||
final int start = editable.getSpanStart(mRange);
|
final int start = editable.getSpanStart(mRange);
|
||||||
final int end = editable.getSpanEnd(mRange);
|
final int end = editable.getSpanEnd(mRange);
|
||||||
mWordIterator.setCharSequence(editable, start, end);
|
|
||||||
|
int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
|
||||||
|
mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
|
||||||
|
|
||||||
// Move back to the beginning of the current word, if any
|
// Move back to the beginning of the current word, if any
|
||||||
int wordStart = mWordIterator.preceding(start);
|
int wordStart = mWordIterator.preceding(start);
|
||||||
@@ -408,11 +444,16 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
|
SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
|
||||||
SuggestionSpan.class);
|
SuggestionSpan.class);
|
||||||
|
|
||||||
int nbWordsChecked = 0;
|
int wordCount = 0;
|
||||||
boolean scheduleOtherSpellCheck = false;
|
boolean scheduleOtherSpellCheck = false;
|
||||||
|
|
||||||
while (wordStart <= end) {
|
while (wordStart <= end) {
|
||||||
if (wordEnd >= start && wordEnd > wordStart) {
|
if (wordEnd >= start && wordEnd > wordStart) {
|
||||||
|
if (wordCount >= MAX_NUMBER_OF_WORDS) {
|
||||||
|
scheduleOtherSpellCheck = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// A new word has been created across the interval boundaries with this edit.
|
// A new word has been created across the interval boundaries with this edit.
|
||||||
// Previous spans (ended on start / started on end) removed, not valid anymore
|
// Previous spans (ended on start / started on end) removed, not valid anymore
|
||||||
if (wordStart < start && wordEnd > start) {
|
if (wordStart < start && wordEnd > start) {
|
||||||
@@ -448,17 +489,20 @@ public class SpellChecker implements SpellCheckerSessionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (createSpellCheckSpan) {
|
if (createSpellCheckSpan) {
|
||||||
if (nbWordsChecked == MAX_SPELL_BATCH_SIZE) {
|
|
||||||
scheduleOtherSpellCheck = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
addSpellCheckSpan(editable, wordStart, wordEnd);
|
addSpellCheckSpan(editable, wordStart, wordEnd);
|
||||||
nbWordsChecked++;
|
|
||||||
}
|
}
|
||||||
|
wordCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterate word by word
|
// iterate word by word
|
||||||
|
int originalWordEnd = wordEnd;
|
||||||
wordEnd = mWordIterator.following(wordEnd);
|
wordEnd = mWordIterator.following(wordEnd);
|
||||||
|
if ((wordIteratorWindowEnd < end) &&
|
||||||
|
(wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
|
||||||
|
wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
|
||||||
|
mWordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd);
|
||||||
|
wordEnd = mWordIterator.following(originalWordEnd);
|
||||||
|
}
|
||||||
if (wordEnd == BreakIterator.DONE) break;
|
if (wordEnd == BreakIterator.DONE) break;
|
||||||
wordStart = mWordIterator.getBeginning(wordEnd);
|
wordStart = mWordIterator.getBeginning(wordEnd);
|
||||||
if (wordStart == BreakIterator.DONE) {
|
if (wordStart == BreakIterator.DONE) {
|
||||||
|
|||||||
Reference in New Issue
Block a user