Android 9.0中的新功能 -PrecomputedText
PrecomputedText 如字面意義一樣,是用來預先計算文本的。它的誕生也是因為計算文本是一個耗時操作,它需要根據字型大小、字體、樣式、換行等去計算,並且這個計算時間隨著文字數量的增加而增加。如果這時顯示的列表中恰好是這種多行的文字,那麼滑動起來豈不是會掉幀,影響著用戶體驗。比如微博這類的產品,列表就非常的複雜。
其實在Android 4.0 中底層就有引入TextLayoutCache來解決這個問題,每個測量過的文字都被添加到緩存中,下次需要相同的文字時,可以從緩存中獲取,不用在測量。不過緩存大小隻有0.5 MB。並且在沒有緩存之前,我們的首次滑動還是UI線程耗時的。為瞭解決這類問題,Android 9.0中添加了PrecomputedText 。據說測量的耗時減少了95%,具體對比可以參看文末的鏈接。
使用方法
- compileSdkVersion為28以上,appcompat庫28.0.0或androidx appcompat 1.0.0以上
- 使用AppCompatTextView來替換TextView
- 使用setTextFuture 替換 setText 方法
代碼如下:
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」)
實現原理
其實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結果圖我就不全部展示了,這裡展示一下中速滑動前後結果。