PrecomputedTextCompat用法及原理

一、官方介绍

       文本展示非常复杂,其涵盖的特性有:多种字体、行间距、字间距、文本方向、断行、字符连接等。为了测量及布局给定文本,TextView 必须做很多工作,例如读取字体文件、查找字形、决定形状、测量边界框以及将文本缓存在内部文本缓存中。更重要的是,所有这些工作都在 UI 线程中进行,这就有可能导致 app 帧数下降。

       我们发现文本测量花费的时间占据文本设置的90%。为解决这一问题,在 Android P 中,以及作为 Jetpack 的一部分,我们推出了一个新的 API: PrecomputedText。该 API 早先在 API 14中便可以通过 PrecomputedTextCompat 访问。

       PrecomputedTextCompat能够使 app 可以事先甚至在后台线程中执行文本布局最耗费时间的部分工作,以缓存布局结果,并返回宝贵的测量数据。然后,可以在 TextView 中设置结果。这样,只有大约10%的工作留给 TextView 执行。

二、使用方法

  • compileSdkVersion不低于28,appcompat-v7 28.0.0或androidx appcompat 1.0.0及以上
  • 使用AppCompatTextView来替换TextView
  • 使用setTextFuture 替换 setText 方法

       Java示例:

Future<PrecomputedTextCompat> future = PrecomputedTextCompat.getTextFuture(“text”,             
        textView.getTextMetricsParamsCompat(), null);

textView.setTextFuture(future);

       Kotlin示例:

fun AppCompatTextView.setTextFuture(charSequence: CharSequence){
    this.setTextFuture(PrecomputedTextCompat.getTextFuture(
            charSequence,
            TextViewCompat.getTextMetricsParams(this),
            null
    ))
}

textView.setTextFuture(“text”)

三、实现原理

       PrecomputedTextCompat将耗时的测量放到FutureTask异步执行:

@UiThread
public static Future<PrecomputedTextCompat> getTextFuture(
        @NonNull final CharSequence charSequence, @NonNull PrecomputedTextCompat.Params params,
        @Nullable Executor executor) {
    PrecomputedTextFutureTask task = new PrecomputedTextFutureTask(params, charSequence);
    if (executor == null) {
        synchronized (sLock) {
            if (sExecutor == null) {
                sExecutor = Executors.newFixedThreadPool(1);
            }
            executor = sExecutor;
        }
    }
    executor.execute(task);
    return task;
}

       AppCompatTextView在onMeasure方法调用consumeTextFutureAndSetBlocking()时,future.get()阻塞线程获取测量的结果。最终setText到TextView上显示。

public void setTextFuture(@Nullable Future<PrecomputedTextCompat> future) {
    mPrecomputedTextFuture = future;
    if (future != null) {
        requestLayout();
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    consumeTextFutureAndSetBlocking();
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

private void consumeTextFutureAndSetBlocking() {
    if (mPrecomputedTextFuture != null) {
        try {
            Future<PrecomputedTextCompat> future = mPrecomputedTextFuture;
            mPrecomputedTextFuture = null;
            TextViewCompat.setPrecomputedText(this, future.get());
        } catch (InterruptedException | ExecutionException e) {
            // ignore
        }
    }
}
  1. Android 9.0以上,使用PrecomputedText实现。
  2. Android 5.0~9.0,使用StaticLayout实现。
  3. Android 5.0以下,不做处理。
/**
 * A helper class for computing text layout in background
 */
private static class PrecomputedTextFutureTask extends FutureTask<PrecomputedTextCompat> {
    private static class PrecomputedTextCallback implements Callable<PrecomputedTextCompat> {
        private PrecomputedTextCompat.Params mParams;
        private CharSequence mText;

        PrecomputedTextCallback(@NonNull final PrecomputedTextCompat.Params params,
                @NonNull final CharSequence cs) {
            mParams = params;
            mText = cs;
        }

        @Override
        public PrecomputedTextCompat call() throws Exception {
            return PrecomputedTextCompat.create(mText, mParams);
        }
    }

    PrecomputedTextFutureTask(@NonNull final PrecomputedTextCompat.Params params,
            @NonNull final CharSequence text) {
        super(new PrecomputedTextCallback(params, text));
    }
}
/**
 * Create a new {@link PrecomputedText} which will pre-compute text measurement and glyph
 * positioning information.
 * <p>
 * This can be expensive, so computing this on a background thread before your text will be
 * presented can save work on the UI thread.
 * </p>
 *
 * Note that any {@link android.text.NoCopySpan} attached to the text won't be passed to the
 * created PrecomputedText.
 *
 * @param text the text to be measured
 * @param params parameters that define how text will be precomputed
 * @return A {@link PrecomputedText}
 */
public static PrecomputedTextCompat create(@NonNull CharSequence text, @NonNull Params params) {
    Preconditions.checkNotNull(text);
    Preconditions.checkNotNull(params);

    try {
        TraceCompat.beginSection("PrecomputedText");

        // 省略此处代码

        // No framework support for PrecomputedText
        // Compute text layout and throw away StaticLayout for the purpose of warming up the
        // internal text layout cache.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            StaticLayout.Builder.obtain(text, 0, text.length(), params.getTextPaint(),
                    Integer.MAX_VALUE)
                    .setBreakStrategy(params.getBreakStrategy())
                    .setHyphenationFrequency(params.getHyphenationFrequency())
                    .setTextDirection(params.getTextDirection())
                    .build();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            new StaticLayout(text, params.getTextPaint(), Integer.MAX_VALUE,
                    Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        } else {
            // There is no way of precomputing text layout on API 20 or before
            // Do nothing
        }

        return new PrecomputedTextCompat(text, params, result);
    } finally {
        TraceCompat.endSection();
    }
}
// null on API 27 or before. Non-null on API 29 or later
private final @Nullable PrecomputedText mWrapped;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值