Introduce PremeasuredText
By measuring the character widths beforehand, we can save at least 40%
of the StaticLayout construction time which typically happens on UI
thread.
Also verified this doesn't cause performance regression for not
premeasured text.
Raw performance score (Not premeasured -> premeasured, median, N=100)
No Style, Greedy, Hyphenation OFF: 7,812,975 -> 503,245 (-93.6%)
No Style, Balanced, Hyphenation OFF: 7,843,254 -> 396,892 (-95.0%)
No Style, Greedy, Hyphenation ON : 19,134,214 -> 11,658,928 (-39.1%)
No Style, Balanced, Hyphenation ON : 19,348,062 -> 11,634,942 (-39.9%)
Styled, Greedy, Hyphenation OFF: 14,353,673 -> 572,840 (-96.0%)
Raw performance score (w/o patch -> w/ patch, median, N=100):
No Style, Greedy, Hyphenation OFF: 7,732,894 -> 7,812,975 (+1.04%)
No Style, Balanced, Hyphenation OFF: 7,884,510 -> 7,843,254 (-0.52%)
No Style, Greedy, Hyphenation ON : 18,986,958 -> 19,134,214 (+0.78%)
No Style, Balanced, Hyphenation ON : 19,232,791 -> 19,348,062 (+0.60%)
Styled, Greedy, Hyphenation OFF: 14,319,690 -> 14,353,673 (+0.24%)
Bug: 67504091
Test: bit CtsTextTestCases:*
Test: bit CtsGraphicsTestCases:*
Test: bit CtsWidgetTestCases:*
Test: FrameworksCoreTests:android.text.MeasuredTextTest
Change-Id: I0b46f04b42cc012606a9c722eca0d51147a0dcc7
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 53ddd16..2e10fe8d 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -610,8 +610,8 @@
/* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
final CharSequence source = b.mText;
- int bufStart = b.mStart;
- int bufEnd = b.mEnd;
+ final int bufStart = b.mStart;
+ final int bufEnd = b.mEnd;
TextPaint paint = b.mPaint;
int outerWidth = b.mWidth;
TextDirectionHeuristic textDir = b.mTextDir;
@@ -634,10 +634,6 @@
Paint.FontMetricsInt fm = b.mFontMetricsInt;
int[] chooseHtv = null;
- Spanned spanned = null;
- if (source instanceof Spanned)
- spanned = (Spanned) source;
-
final int[] indents;
if (mLeftIndents != null || mRightIndents != null) {
final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
@@ -660,16 +656,34 @@
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
indents, mLeftPaddings, mRightPaddings);
- MeasuredText measured = null;
+ PremeasuredText premeasured = null;
+ final Spanned spanned;
+ if (source instanceof PremeasuredText) {
+ premeasured = (PremeasuredText) source;
+
+ final CharSequence original = premeasured.getText();
+ spanned = (original instanceof Spanned) ? (Spanned) original : null;
+
+ if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
+ // The buffer position has changed. Re-measure here.
+ premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+ } else {
+ // We can use premeasured information.
+
+ // Overwrite with the one when premeasured.
+ // TODO: Give an option for developer not to overwrite and measure again here?
+ textDir = premeasured.getTextDir();
+ paint = premeasured.getPaint();
+ }
+ } else {
+ premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+ spanned = (source instanceof Spanned) ? (Spanned) source : null;
+ }
+
try {
- int paraEnd;
- for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
- if (paraEnd < 0) {
- paraEnd = bufEnd;
- } else {
- paraEnd++;
- }
+ for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
+ final int paraStart = premeasured.getParagraphStart(paraIndex);
+ final int paraEnd = premeasured.getParagraphEnd(paraIndex);
int firstWidthLineCount = 1;
int firstWidth = outerWidth;
@@ -735,8 +749,7 @@
}
}
- measured = MeasuredText.buildForStaticLayout(
- paint, source, paraStart, paraEnd, textDir, measured);
+ final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
final char[] chs = measured.getChars();
final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
final int[] fmCache = measured.getFontMetrics().getRawArray();
@@ -887,7 +900,8 @@
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
&& mLineCount < mMaximumVisibleLineCount) {
- measured = MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, measured);
+ final MeasuredText measured =
+ MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
paint.getFontMetricsInt(fm);
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
@@ -901,9 +915,6 @@
ellipsizedWidth, 0, paint, false);
}
} finally {
- if (measured != null) {
- measured.recycle();
- }
nFinish(nativePtr);
}
}