4343 */
4444public class SpellChecker implements SpellCheckerSessionListener {
4545
46- private final static int MAX_SPELL_BATCH_SIZE = 50 ;
46+ // No more than this number of words will be parsed on each iteration to ensure a minimum
47+ // lock of the UI thread
48+ public static final int MAX_NUMBER_OF_WORDS = 50 ;
49+
50+ // Rough estimate, such that the word iterator interval usually does not need to be shifted
51+ public static final int AVERAGE_WORD_LENGTH = 7 ;
52+
53+ // When parsing, use a character window of that size. Will be shifted if needed
54+ public static final int WORD_ITERATOR_INTERVAL = AVERAGE_WORD_LENGTH * MAX_NUMBER_OF_WORDS ;
55+
56+ // Pause between each spell check to keep the UI smooth
57+ private final static int SPELL_PAUSE_DURATION = 400 ; // milliseconds
4758
4859 private final TextView mTextView ;
4960
@@ -71,6 +82,8 @@ public class SpellChecker implements SpellCheckerSessionListener {
7182
7283 private TextServicesManager mTextServicesManager ;
7384
85+ private Runnable mSpellRunnable ;
86+
7487 public SpellChecker (TextView textView ) {
7588 mTextView = textView ;
7689
@@ -141,6 +154,10 @@ public void closeSession() {
141154 for (int i = 0 ; i < length ; i ++) {
142155 mSpellParsers [i ].finish ();
143156 }
157+
158+ if (mSpellRunnable != null ) {
159+ mTextView .removeCallbacks (mSpellRunnable );
160+ }
144161 }
145162
146163 private int nextSpellCheckSpanIndex () {
@@ -254,6 +271,7 @@ private void spellCheck() {
254271 System .arraycopy (textInfos , 0 , textInfosCopy , 0 , textInfosCount );
255272 textInfos = textInfosCopy ;
256273 }
274+
257275 mSpellCheckerSession .getSuggestions (textInfos , SuggestionSpan .SUGGESTIONS_MAX_SIZE ,
258276 false /* TODO Set sequentialWords to true for initial spell check */ );
259277 }
@@ -288,13 +306,29 @@ public void onGetSuggestions(SuggestionsInfo[] results) {
288306 }
289307 }
290308
291- final int length = mSpellParsers .length ;
292- for (int i = 0 ; i < length ; i ++) {
293- final SpellParser spellParser = mSpellParsers [i ];
294- if (!spellParser .isFinished ()) {
295- spellParser .parse ();
296- }
309+ scheduleNewSpellCheck ();
310+ }
311+
312+ private void scheduleNewSpellCheck () {
313+ if (mSpellRunnable == null ) {
314+ mSpellRunnable = new Runnable () {
315+ @ Override
316+ public void run () {
317+ final int length = mSpellParsers .length ;
318+ for (int i = 0 ; i < length ; i ++) {
319+ final SpellParser spellParser = mSpellParsers [i ];
320+ if (!spellParser .isFinished ()) {
321+ spellParser .parse ();
322+ break ; // run one spell parser at a time to bound running time
323+ }
324+ }
325+ }
326+ };
327+ } else {
328+ mTextView .removeCallbacks (mSpellRunnable );
297329 }
330+
331+ mTextView .postDelayed (mSpellRunnable , SPELL_PAUSE_DURATION );
298332 }
299333
300334 private void createMisspelledSuggestionSpan (Editable editable , SuggestionsInfo suggestionsInfo ,
@@ -383,7 +417,9 @@ public void parse() {
383417 // Iterate over the newly added text and schedule new SpellCheckSpans
384418 final int start = editable .getSpanStart (mRange );
385419 final int end = editable .getSpanEnd (mRange );
386- mWordIterator .setCharSequence (editable , start , end );
420+
421+ int wordIteratorWindowEnd = Math .min (end , start + WORD_ITERATOR_INTERVAL );
422+ mWordIterator .setCharSequence (editable , start , wordIteratorWindowEnd );
387423
388424 // Move back to the beginning of the current word, if any
389425 int wordStart = mWordIterator .preceding (start );
@@ -408,11 +444,16 @@ public void parse() {
408444 SuggestionSpan [] suggestionSpans = editable .getSpans (start - 1 , end + 1 ,
409445 SuggestionSpan .class );
410446
411- int nbWordsChecked = 0 ;
447+ int wordCount = 0 ;
412448 boolean scheduleOtherSpellCheck = false ;
413449
414450 while (wordStart <= end ) {
415451 if (wordEnd >= start && wordEnd > wordStart ) {
452+ if (wordCount >= MAX_NUMBER_OF_WORDS ) {
453+ scheduleOtherSpellCheck = true ;
454+ break ;
455+ }
456+
416457 // A new word has been created across the interval boundaries with this edit.
417458 // Previous spans (ended on start / started on end) removed, not valid anymore
418459 if (wordStart < start && wordEnd > start ) {
@@ -448,17 +489,20 @@ public void parse() {
448489 }
449490
450491 if (createSpellCheckSpan ) {
451- if (nbWordsChecked == MAX_SPELL_BATCH_SIZE ) {
452- scheduleOtherSpellCheck = true ;
453- break ;
454- }
455492 addSpellCheckSpan (editable , wordStart , wordEnd );
456- nbWordsChecked ++;
457493 }
494+ wordCount ++;
458495 }
459496
460497 // iterate word by word
498+ int originalWordEnd = wordEnd ;
461499 wordEnd = mWordIterator .following (wordEnd );
500+ if ((wordIteratorWindowEnd < end ) &&
501+ (wordEnd == BreakIterator .DONE || wordEnd >= wordIteratorWindowEnd )) {
502+ wordIteratorWindowEnd = Math .min (end , originalWordEnd + WORD_ITERATOR_INTERVAL );
503+ mWordIterator .setCharSequence (editable , originalWordEnd , wordIteratorWindowEnd );
504+ wordEnd = mWordIterator .following (originalWordEnd );
505+ }
462506 if (wordEnd == BreakIterator .DONE ) break ;
463507 wordStart = mWordIterator .getBeginning (wordEnd );
464508 if (wordStart == BreakIterator .DONE ) {
0 commit comments