Android 9.0中的新功能 - PrecomputedText

PrecomputedText 如字面意義同樣,是用來預先計算文本的。它的誕生也是由於計算文本是一個耗時操做,它須要根據字號、字體、樣式、換行等去計算,而且這個計算時間隨着文字數量的增長而增長。若是這時顯示的列表中剛好是這種多行的文字,那麼滑動起來豈不是會掉幀,影響着用戶體驗。好比微博這類的產品,列表就很是的複雜。java

其實在Android 4.0 中底層就有引入TextLayoutCache來解決這個問題,每一個測量過的文字都被添加到緩存中,下次須要相同的文字時,能夠從緩存中獲取,不用在測量。不過緩存大小隻有0.5 MB。而且在沒有緩存以前,咱們的首次滑動仍是UI線程耗時的。爲了解決這類問題,Android 9.0中添加了PrecomputedText 。聽說測量的耗時減小了95%,具體對比能夠參看文末的連接。android

使用方法

  • compileSdkVersion爲28以上,appcompat庫28.0.0或androidx appcompat 1.0.0以上git

  • 使用AppCompatTextView來替換TextViewgithub

  • 使用setTextFuture 替換 setText 方法緩存

代碼以下:性能優化

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

textView.setTextFuture(future);
複製代碼

固然若是你使用kotlin,那麼利用拓展方法會更加酸爽。app

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

// 一行調用
textView.setTextFuture(「text」)
複製代碼

實現原理

其實PrecomputedText實現原理很簡單,就是將耗時的測量放到了異步去執行。異步

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

                executor = sExecutor;
            }
        }

        executor.execute(task);
        return task;
    }
複製代碼

經過調用consumeTextFutureAndSetBlocking方法的future.get()阻塞計算線程來獲取計算結果,最終setText到對用的TextView上。性能

public void setTextFuture(@NonNull Future<PrecomputedTextCompat> future) {
        this.mPrecomputedTextFuture = future;
        this.requestLayout();
    }

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

	private void consumeTextFutureAndSetBlocking() {
        if (this.mPrecomputedTextFuture != null) {
            try {
                Future<PrecomputedTextCompat> future = this.mPrecomputedTextFuture;
                this.mPrecomputedTextFuture = null;
                TextViewCompat.setPrecomputedText(this, (PrecomputedTextCompat)future.get());
            } catch (ExecutionException | InterruptedException var2) {
                ;
            }
        }

    }
複製代碼

新的問題

在看PrecomputedText時,在Github上找到了一個相關的Demo,這其中發現使用後形成了負優化。測試

這個例子中,一個item上有三個AppCompatTextView而且字號都很小,致使一屏幕能夠看到十段左右的文字,固然使用了PrecomputedText優化後,onBindViewHolder方法的執行時間大大的縮短了,可是卻檢測到了新的問題。

首先咱們要了解滑動列表的速度越快,那麼單位時間內測量繪製的內容也就越多。我對使用先後進行了三種速度的測試,分別是慢速(1s滑動1次,力度小)、中速(1s滑動2次,力度中)、快速(1s滑動3次,力度大)獲得了下面的結論。(純手工滑動,真的累。。。)

具體的Systrace結果圖我就不所有展現了,這裏展現一下中速滑動先後結果。

表明Animation和Input的淺綠色豎條增高了。

在這裏插入圖片描述
在這裏插入圖片描述
最終的統計以下:

問題/速度 慢速 中速 快速
Scheduling delay 4 -> 46 5 -> 39 8 -> 17
Long View#draw() 18 -> 12 37 -> 30 50 -> 48
Expensive measure/layout pass 1 -> 0 0 0

Scheduling delay 就是一個線程在處理一塊運算的時候,在很長一段時間都沒有被CPU調度,從而致使這個線程在很長一段時間都沒有完成工做。

能夠看到使用PrecomputedText後,Scheduling delay 問題會有必定機率激增,甚至更加嚴重。對比發現激增點都是由於dequeueBuffer 這裏等待時間過長,好比下面 dequeueBuffer 的片斷cpu實際執行了0.119ms,可是總耗時了10.035ms。

在這裏插入圖片描述

其實仔細觀察,dequeueBuffer 在一開始就已經執行完成,可是卻處在等待cpu調度來執行下一步的地方。這裏其實就是等待SurfaceFlinger執行致使的。以下圖:

在這裏插入圖片描述

這裏的耗時,會使通知 CPU 延遲,致使的Scheduling delay 。具體爲什麼高几率觸發這類問題的緣由這裏還不清楚。猜想是文本自己很複雜,一段文字中不一樣字號、顏色、樣式,而且頁面上同時存在十多個這樣的段落。這樣的話就短期內會有十屢次線程的切換來實現文字的異步測量,勢必會有性能影響。

我後面將文字字體設置大了之後,發現這個問題就好多了。因此PrecomputedText的使用仍是須要根據場景來使用,不然會矯枉過正。

總結

  • 不要濫用PrecomputedText,對於一兩行文字來講並無很大的提高,反而會形成沒必要要的Scheduling delay,建議文本200個字符以上使用。

  • 不要在TextViewCompat.getTextMetricsParams()方法後修改textview的屬性。好比設置字號放到前面執行。

  • PrecomputedTextCompat在9.0以上使用PrecomputedText優化,5.0~9.0使用StaticLayout優化,5.0如下的不作處理。

  • 若是您已禁用RecyclerView的預取(Prefetch),則PrecomputedText無效。若是您使用自定義LayoutManager,請確保它實現 collectAdjacentPrefetchPositions()以便RecyclerView知道要預取的項目。所以ListView 沒法享受到PrecomputedText帶來的性能優化。

具體kotlin示例能夠看 PrecomputedTextCompatExample ,裏面也有使用協程的優化方案。我也寫了一個對應的java示例

效果以下:

normal PrecomputedText future PrecomputedText coroutine

最後若是對你有幫助,但願點贊支持!!

參考

相關文章
相關標籤/搜索