Skip to content

Commit 52edaa9

Browse files
Gilles DebunneAndroid (Google) Code Review
authored andcommitted
Merge "Bug 5250788: TextView gets slower as the text length grows"
2 parents 71bfec4 + 945ee9b commit 52edaa9

File tree

2 files changed

+120
-47
lines changed

2 files changed

+120
-47
lines changed

core/java/android/text/SpannableStringBuilder.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,6 @@ public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
709709
T ret1 = null;
710710

711711
for (int i = 0; i < spanCount; i++) {
712-
if (!kind.isInstance(spans[i])) continue;
713-
714712
int spanStart = starts[i];
715713
int spanEnd = ends[i];
716714

@@ -735,6 +733,9 @@ public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
735733
continue;
736734
}
737735

736+
// Expensive test, should be performed after the previous tests
737+
if (!kind.isInstance(spans[i])) continue;
738+
738739
if (count == 0) {
739740
// Safe conversion thanks to the isInstance test above
740741
ret1 = (T) spans[i];

core/java/android/text/TextLine.java

Lines changed: 117 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
import com.android.internal.util.ArrayUtils;
3232

33+
import java.lang.reflect.Array;
34+
3335
/**
3436
* Represents a line of styled text, for measuring in visual order and
3537
* for rendering.
@@ -850,6 +852,73 @@ private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
850852
return runIsRtl ? -ret : ret;
851853
}
852854

855+
private static class SpanSet<E> {
856+
final int numberOfSpans;
857+
final E[] spans;
858+
final int[] spanStarts;
859+
final int[] spanEnds;
860+
final int[] spanFlags;
861+
862+
@SuppressWarnings("unchecked")
863+
SpanSet(Spanned spanned, int start, int limit, Class<? extends E> type) {
864+
final E[] allSpans = spanned.getSpans(start, limit, type);
865+
final int length = allSpans.length;
866+
// These arrays may end up being too large because of empty spans
867+
spans = (E[]) Array.newInstance(type, length);
868+
spanStarts = new int[length];
869+
spanEnds = new int[length];
870+
spanFlags = new int[length];
871+
872+
int count = 0;
873+
for (int i = 0; i < length; i++) {
874+
final E span = allSpans[i];
875+
876+
final int spanStart = spanned.getSpanStart(span);
877+
final int spanEnd = spanned.getSpanEnd(span);
878+
if (spanStart == spanEnd) continue;
879+
880+
final int spanFlag = spanned.getSpanFlags(span);
881+
final int priority = spanFlag & Spanned.SPAN_PRIORITY;
882+
if (priority != 0 && count != 0) {
883+
int j;
884+
885+
for (j = 0; j < count; j++) {
886+
final int otherPriority = spanFlags[j] & Spanned.SPAN_PRIORITY;
887+
if (priority > otherPriority) break;
888+
}
889+
890+
System.arraycopy(spans, j, spans, j + 1, count - j);
891+
System.arraycopy(spanStarts, j, spanStarts, j + 1, count - j);
892+
System.arraycopy(spanEnds, j, spanEnds, j + 1, count - j);
893+
System.arraycopy(spanFlags, j, spanFlags, j + 1, count - j);
894+
895+
spans[j] = span;
896+
spanStarts[j] = spanStart;
897+
spanEnds[j] = spanEnd;
898+
spanFlags[j] = spanFlag;
899+
} else {
900+
spans[i] = span;
901+
spanStarts[i] = spanStart;
902+
spanEnds[i] = spanEnd;
903+
spanFlags[i] = spanFlag;
904+
}
905+
906+
count++;
907+
}
908+
numberOfSpans = count;
909+
}
910+
911+
int getNextTransition(int start, int limit) {
912+
for (int i = 0; i < numberOfSpans; i++) {
913+
final int spanStart = spanStarts[i];
914+
final int spanEnd = spanEnds[i];
915+
if (spanStart > start && spanStart < limit) limit = spanStart;
916+
if (spanEnd > start && spanEnd < limit) limit = spanEnd;
917+
}
918+
return limit;
919+
}
920+
}
921+
853922
/**
854923
* Utility function for handling a unidirectional run. The run must not
855924
* contain tabs or emoji but can contain styles.
@@ -883,66 +952,70 @@ private float handleRun(int start, int measureLimit,
883952
return 0f;
884953
}
885954

955+
if (mSpanned == null) {
956+
TextPaint wp = mWorkPaint;
957+
wp.set(mPaint);
958+
final int mlimit = measureLimit;
959+
return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top,
960+
y, bottom, fmi, needWidth || mlimit < measureLimit);
961+
}
962+
963+
final SpanSet<MetricAffectingSpan> metricAffectingSpans = new SpanSet<MetricAffectingSpan>(
964+
mSpanned, mStart + start, mStart + limit, MetricAffectingSpan.class);
965+
final SpanSet<CharacterStyle> characterStyleSpans = new SpanSet<CharacterStyle>(
966+
mSpanned, mStart + start, mStart + limit, CharacterStyle.class);
967+
886968
// Shaping needs to take into account context up to metric boundaries,
887969
// but rendering needs to take into account character style boundaries.
888970
// So we iterate through metric runs to get metric bounds,
889971
// then within each metric run iterate through character style runs
890972
// for the run bounds.
891-
float ox = x;
973+
final float originalX = x;
892974
for (int i = start, inext; i < measureLimit; i = inext) {
893975
TextPaint wp = mWorkPaint;
894976
wp.set(mPaint);
895977

896-
int mlimit;
897-
if (mSpanned == null) {
898-
inext = limit;
899-
mlimit = measureLimit;
900-
} else {
901-
inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit,
902-
MetricAffectingSpan.class) - mStart;
903-
904-
mlimit = inext < measureLimit ? inext : measureLimit;
905-
MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i,
906-
mStart + mlimit, MetricAffectingSpan.class);
907-
spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);
908-
909-
if (spans.length > 0) {
910-
ReplacementSpan replacement = null;
911-
for (int j = 0; j < spans.length; j++) {
912-
MetricAffectingSpan span = spans[j];
913-
if (span instanceof ReplacementSpan) {
914-
replacement = (ReplacementSpan)span;
915-
} else {
916-
// We might have a replacement that uses the draw
917-
// state, otherwise measure state would suffice.
918-
span.updateDrawState(wp);
919-
}
920-
}
921-
922-
if (replacement != null) {
923-
x += handleReplacement(replacement, wp, i,
924-
mlimit, runIsRtl, c, x, top, y, bottom, fmi,
925-
needWidth || mlimit < measureLimit);
926-
continue;
927-
}
978+
inext = metricAffectingSpans.getNextTransition(mStart + i, mStart + limit) - mStart;
979+
int mlimit = Math.min(inext, measureLimit);
980+
981+
ReplacementSpan replacement = null;
982+
983+
for (int j = 0; j < metricAffectingSpans.numberOfSpans; j++) {
984+
// Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
985+
// empty by construction. This special case in getSpans() explains the >= & <= tests
986+
if ((metricAffectingSpans.spanStarts[j] >= mStart + mlimit) ||
987+
(metricAffectingSpans.spanEnds[j] <= mStart + i)) continue;
988+
MetricAffectingSpan span = metricAffectingSpans.spans[j];
989+
if (span instanceof ReplacementSpan) {
990+
replacement = (ReplacementSpan)span;
991+
} else {
992+
// We might have a replacement that uses the draw
993+
// state, otherwise measure state would suffice.
994+
span.updateDrawState(wp);
928995
}
929996
}
930997

931-
if (mSpanned == null || c == null) {
998+
if (replacement != null) {
999+
x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
1000+
bottom, fmi, needWidth || mlimit < measureLimit);
1001+
continue;
1002+
}
1003+
1004+
if (c == null) {
9321005
x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top,
9331006
y, bottom, fmi, needWidth || mlimit < measureLimit);
9341007
} else {
9351008
for (int j = i, jnext; j < mlimit; j = jnext) {
936-
jnext = mSpanned.nextSpanTransition(mStart + j,
937-
mStart + mlimit, CharacterStyle.class) - mStart;
938-
939-
CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
940-
mStart + jnext, CharacterStyle.class);
941-
spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);
1009+
jnext = characterStyleSpans.getNextTransition(mStart + j, mStart + mlimit) -
1010+
mStart;
9421011

9431012
wp.set(mPaint);
944-
for (int k = 0; k < spans.length; k++) {
945-
CharacterStyle span = spans[k];
1013+
for (int k = 0; k < characterStyleSpans.numberOfSpans; k++) {
1014+
// Intentionally using >= and <= as explained above
1015+
if ((characterStyleSpans.spanStarts[k] >= mStart + jnext) ||
1016+
(characterStyleSpans.spanEnds[k] <= mStart + j)) continue;
1017+
1018+
CharacterStyle span = characterStyleSpans.spans[k];
9461019
span.updateDrawState(wp);
9471020
}
9481021

@@ -952,7 +1025,7 @@ private float handleRun(int start, int measureLimit,
9521025
}
9531026
}
9541027

955-
return x - ox;
1028+
return x - originalX;
9561029
}
9571030

9581031
/**
@@ -997,8 +1070,7 @@ float ascent(int pos) {
9971070
}
9981071

9991072
pos += mStart;
1000-
MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1,
1001-
MetricAffectingSpan.class);
1073+
MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class);
10021074
if (spans.length == 0) {
10031075
return mPaint.ascent();
10041076
}

0 commit comments

Comments
 (0)