Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 75492afb authored by Seigo Nonaka's avatar Seigo Nonaka
Browse files

Refactor MeasuredText

This refactoring contains:
- Add lots of comments.
- Mark private the internal fields and introduce accessors and helper
  methods.
- Factor out the auto grow array implementation to another class.
- Use SynchronizedPool for pool implementation.
- Introduce three build methods for each use case.
- Hide addStyleRun and compute all necessary informations in build method.

Locally verified that this doesn't cause performance regressions.
Here is a raw performance test result on walleye-userdebug.

StaticLayoutPerfTest (median, N=100):
createRandom:          7,846,449 -> 8,003,903 (+2.01%)
createRandom Balanced: 7,810,436 -> 7,919,200 (+1.40%)

TextViewOnMeasurePerfTest (median, N=100):
measure_AtMost:       94,276,376 ->  94,124,658 (-0.16%)
measure_Exactly:      91,629,352 ->  91,617,639 (-0.01%)
measure_Unspecified: 151,006,181 -> 150,957,598 (-0.03%)

Test: bit CtsTextTestCases:*
Test: bit CtsGraphicsTestCases:*
Test: bit CtsWidgetTestCases:*
Test: bit FrameworksCoreTests:android.text.StaticLayoutTest
Bug: 65024629

Change-Id: I58d3020a3fa560d05576e18888fbfe46e2975e8f
parent fb19b383
Loading
Loading
Loading
Loading
+374 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text;

import android.annotation.IntRange;
import android.annotation.NonNull;

import com.android.internal.util.ArrayUtils;

import libcore.util.EmptyArray;

/**
 * Implements a growing array of int primitives.
 *
 * These arrays are NOT thread safe.
 *
 * @hide
 */
public final class AutoGrowArray {
    private static final int MIN_CAPACITY_INCREMENT = 12;
    private static final int MAX_CAPACITY_TO_BE_KEPT = 10000;

    /**
     * Returns next capacity size.
     *
     * The returned capacity is larger than requested capacity.
     */
    private static int computeNewCapacity(int currentSize, int requested) {
        final int targetCapacity = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2)
                ?  MIN_CAPACITY_INCREMENT : currentSize >> 1);
        return targetCapacity > requested ? targetCapacity : requested;
    }

    /**
     * An auto growing byte array.
     */
    public static class ByteArray {

        private @NonNull byte[] mValues;
        private @IntRange(from = 0) int mSize;

        /**
         * Creates an empty ByteArray with the default initial capacity.
         */
        public ByteArray() {
            this(10);
        }

        /**
         * Creates an empty ByteArray with the specified initial capacity.
         */
        public ByteArray(@IntRange(from = 0) int initialCapacity) {
            if (initialCapacity == 0) {
                mValues = EmptyArray.BYTE;
            } else {
                mValues = ArrayUtils.newUnpaddedByteArray(initialCapacity);
            }
            mSize = 0;
        }

        /**
         * Changes the size of this ByteArray. If this ByteArray is shrinked, the backing array
         * capacity is unchanged.
         */
        public void resize(@IntRange(from = 0) int newSize) {
            if (newSize > mValues.length) {
                ensureCapacity(newSize - mSize);
            }
            mSize = newSize;
        }

        /**
         * Appends the specified value to the end of this array.
         */
        public void append(byte value) {
            ensureCapacity(1);
            mValues[mSize++] = value;
        }

        /**
         * Ensures capacity to append at least <code>count</code> values.
         */
        private void ensureCapacity(@IntRange int count) {
            final int requestedSize = mSize + count;
            if (requestedSize >= mValues.length) {
                final int newCapacity = computeNewCapacity(mSize, requestedSize);
                final byte[] newValues = ArrayUtils.newUnpaddedByteArray(newCapacity);
                System.arraycopy(mValues, 0, newValues, 0, mSize);
                mValues = newValues;
            }
        }

        /**
         * Removes all values from this array.
         */
        public void clear() {
            mSize = 0;
        }

        /**
         * Removes all values from this array and release the internal array object if it is too
         * large.
         */
        public void clearWithReleasingLargeArray() {
            clear();
            if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) {
                mValues = EmptyArray.BYTE;
            }
        }

        /**
         * Returns the value at the specified position in this array.
         */
        public byte get(@IntRange(from = 0) int index) {
            return mValues[index];
        }

        /**
         * Sets the value at the specified position in this array.
         */
        public void set(@IntRange(from = 0) int index, byte value) {
            mValues[index] = value;
        }

        /**
         * Returns the number of values in this array.
         */
        public @IntRange(from = 0) int size() {
            return mSize;
        }

        /**
         * Returns internal raw array.
         *
         * Note that this array may have larger size than you requested.
         * Use size() instead for getting the actual array size.
         */
        public @NonNull byte[] getRawArray() {
            return mValues;
        }
    }

    /**
     * An auto growing int array.
     */
    public static class IntArray {

        private @NonNull int[] mValues;
        private @IntRange(from = 0) int mSize;

        /**
         * Creates an empty IntArray with the default initial capacity.
         */
        public IntArray() {
            this(10);
        }

        /**
         * Creates an empty IntArray with the specified initial capacity.
         */
        public IntArray(@IntRange(from = 0) int initialCapacity) {
            if (initialCapacity == 0) {
                mValues = EmptyArray.INT;
            } else {
                mValues = ArrayUtils.newUnpaddedIntArray(initialCapacity);
            }
            mSize = 0;
        }

        /**
         * Changes the size of this IntArray. If this IntArray is shrinked, the backing array
         * capacity is unchanged.
         */
        public void resize(@IntRange(from = 0) int newSize) {
            if (newSize > mValues.length) {
                ensureCapacity(newSize - mSize);
            }
            mSize = newSize;
        }

        /**
         * Appends the specified value to the end of this array.
         */
        public void append(int value) {
            ensureCapacity(1);
            mValues[mSize++] = value;
        }

        /**
         * Ensures capacity to append at least <code>count</code> values.
         */
        private void ensureCapacity(@IntRange(from = 0) int count) {
            final int requestedSize = mSize + count;
            if (requestedSize >= mValues.length) {
                final int newCapacity = computeNewCapacity(mSize, requestedSize);
                final int[] newValues = ArrayUtils.newUnpaddedIntArray(newCapacity);
                System.arraycopy(mValues, 0, newValues, 0, mSize);
                mValues = newValues;
            }
        }

        /**
         * Removes all values from this array.
         */
        public void clear() {
            mSize = 0;
        }

        /**
         * Removes all values from this array and release the internal array object if it is too
         * large.
         */
        public void clearWithReleasingLargeArray() {
            clear();
            if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) {
                mValues = EmptyArray.INT;
            }
        }

        /**
         * Returns the value at the specified position in this array.
         */
        public int get(@IntRange(from = 0) int index) {
            return mValues[index];
        }

        /**
         * Sets the value at the specified position in this array.
         */
        public void set(@IntRange(from = 0) int index, int value) {
            mValues[index] = value;
        }

        /**
         * Returns the number of values in this array.
         */
        public @IntRange(from = 0) int size() {
            return mSize;
        }

        /**
         * Returns internal raw array.
         *
         * Note that this array may have larger size than you requested.
         * Use size() instead for getting the actual array size.
         */
        public @NonNull int[] getRawArray() {
            return mValues;
        }
    }

    /**
     * An auto growing float array.
     */
    public static class FloatArray {

        private @NonNull float[] mValues;
        private @IntRange(from = 0) int mSize;

        /**
         * Creates an empty FloatArray with the default initial capacity.
         */
        public FloatArray() {
            this(10);
        }

        /**
         * Creates an empty FloatArray with the specified initial capacity.
         */
        public FloatArray(@IntRange(from = 0) int initialCapacity) {
            if (initialCapacity == 0) {
                mValues = EmptyArray.FLOAT;
            } else {
                mValues = ArrayUtils.newUnpaddedFloatArray(initialCapacity);
            }
            mSize = 0;
        }

        /**
         * Changes the size of this FloatArray. If this FloatArray is shrinked, the backing array
         * capacity is unchanged.
         */
        public void resize(@IntRange(from = 0) int newSize) {
            if (newSize > mValues.length) {
                ensureCapacity(newSize - mSize);
            }
            mSize = newSize;
        }

        /**
         * Appends the specified value to the end of this array.
         */
        public void append(float value) {
            ensureCapacity(1);
            mValues[mSize++] = value;
        }

        /**
         * Ensures capacity to append at least <code>count</code> values.
         */
        private void ensureCapacity(int count) {
            final int requestedSize = mSize + count;
            if (requestedSize >= mValues.length) {
                final int newCapacity = computeNewCapacity(mSize, requestedSize);
                final float[] newValues = ArrayUtils.newUnpaddedFloatArray(newCapacity);
                System.arraycopy(mValues, 0, newValues, 0, mSize);
                mValues = newValues;
            }
        }

        /**
         * Removes all values from this array.
         */
        public void clear() {
            mSize = 0;
        }

        /**
         * Removes all values from this array and release the internal array object if it is too
         * large.
         */
        public void clearWithReleasingLargeArray() {
            clear();
            if (mValues.length > MAX_CAPACITY_TO_BE_KEPT) {
                mValues = EmptyArray.FLOAT;
            }
        }

        /**
         * Returns the value at the specified position in this array.
         */
        public float get(@IntRange(from = 0) int index) {
            return mValues[index];
        }

        /**
         * Sets the value at the specified position in this array.
         */
        public void set(@IntRange(from = 0) int index, float value) {
            mValues[index] = value;
        }

        /**
         * Returns the number of values in this array.
         */
        public @IntRange(from = 0) int size() {
            return mSize;
        }

        /**
         * Returns internal raw array.
         *
         * Note that this array may have larger size than you requested.
         * Use size() instead for getting the actual array size.
         */
        public @NonNull float[] getRawArray() {
            return mValues;
        }
    }
}
+14 −15
Original line number Diff line number Diff line
@@ -1907,22 +1907,14 @@ public abstract class Layout {

    private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
            TextDirectionHeuristic textDir) {
        MeasuredText mt = MeasuredText.obtain();
        MeasuredText mt = null;
        TextLine tl = TextLine.obtain();
        try {
            mt.setPara(text, start, end, textDir);
            Directions directions;
            int dir;
            if (mt.mEasy) {
                directions = DIRS_ALL_LEFT_TO_RIGHT;
                dir = Layout.DIR_LEFT_TO_RIGHT;
            } else {
                directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
                    0, mt.mChars, 0, mt.mLen);
                dir = mt.mDir;
            }
            char[] chars = mt.mChars;
            int len = mt.mLen;
            mt = MeasuredText.buildForBidi(text, start, end, textDir, mt);
            final char[] chars = mt.getChars();
            final int len = chars.length;
            final Directions directions = mt.getDirections(0, len);
            final int dir = mt.getParagraphDir();
            boolean hasTabs = false;
            TabStops tabStops = null;
            // leading margins should be taken into account when measuring a paragraph
@@ -1955,7 +1947,9 @@ public abstract class Layout {
            return margin + Math.abs(tl.metrics(null));
        } finally {
            TextLine.recycle(tl);
            MeasuredText.recycle(mt);
            if (mt != null) {
                mt.recycle();
            }
        }
    }

@@ -2272,6 +2266,11 @@ public abstract class Layout {
    private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
    private int mJustificationMode;

    /** @hide */
    @IntDef({DIR_LEFT_TO_RIGHT, DIR_RIGHT_TO_LEFT})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Direction {}

    public static final int DIR_LEFT_TO_RIGHT = 1;
    public static final int DIR_RIGHT_TO_LEFT = -1;