Android TextView 預渲染詳解
Android中的TextView是整個framework中最複雜的控制元件之一,負責Android中顯示文字的大部分工作,framwork中 的許多控制元件也直接或者間接的繼承於TextView,例如Button,EditText等。其內部實現也相當複雜,單論程式碼行數來說,android- 22中TextView有足足9509行,另外,TextView中許多操作都非常繁重,例如setText操作,需要設定 SpanWatcher,或者需要重現建立一個SpannableString,還需要根據情況重新建立Text Layout,這些操作加起來之後令一次setText操作非常耗時。為了提升TextView的渲染效率,最近研究了一下預渲染的方法,接下來給大家講 解一下原理。
TextView渲染基本原理
首先來介紹下TextView的基本渲染原理,總的來說,TextView中負責渲染文字的主要是這三個類:
- BoringLayout
主要負責顯示單行文字,並提供了isBoring方法來判斷是否滿足單行文字的條件。 - DynamicLayout
當文字為Spannable的時候,TextView就會使用它來負責文字的顯示,在內部設定了SpanWatcher,當檢測到span改變的時候,會進行reflow,重新計算佈局。 - StaticLayout
當文字為非單行文字,且非Spannable的時候,就會使用StaticLayout,內部並不會監聽span的變化,因此效率上會比 DynamicLayout高,只需一次佈局的建立即可,但其實內部也能顯示SpannableString,只是不能在span變化之後重新進行佈局而 已。
另外,以上三個類都繼承於Layout類,在此類中統一負責文字的具體繪製,在Layout.draw方法中,會對文字一行一行的進行渲染:
TextLine tl = TextLine.obtain(); // Draw the lines, one at a time. // The baseline is the top of the following line minus the current line's descent. for (int i = firstLine; i <= lastLine; i++) { .... Directions directions = getLineDirections(i); if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) { // XXX: assumes there's nothing additional to be done canvas.drawText(buf, start, end, x, lbaseline, paint); } else { tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops); tl.draw(canvas, x, ltop, lbaseline, lbottom); } } TextLine.recycle(tl);
可以看出來對於Spannble,或者包含emoji的文字的話,實際渲染操作是交給了TextLine去繪製,否則直接使用canvas.drawText,TextLine負責單行復雜文字的繪製,其中Spannable, Emoji之類的繪製邏輯都包含在裡面,TextLine的繪製邏輯也並非十分高效,這裡後續將會繼續說明其應該如何優化。
TextLayoutCache
Canvas在drawText的時候,如果需要每次都計算字型的大小,邊距等之類的話,就會非常耗時,導致 drawText時間會拉的很長,為了提高效率,android在4.0之後引入了TextLayoutCache,使用LRU Cache快取了字形,邊距等資料,提升了drawText的速度,在4.4中,這個cache的大小是0.5M,全域性使用,並且會在Activity的configurationChanged,onResume,lowMemory,updateVisibility等時機,會呼叫Canvas.freeTextLayoutCache來釋放這部分記憶體。由於這部分的cache是系統底層控制的,我們無法做具體的控制。
TextView的預渲染優化
從TextView的渲染原理來看,如果只是單純的顯示文字,我們根本不需要另外設定SpanWatcher來監聽span的變化,因此我們可以直 接使用BoringLayout或者StaticLayout來直接顯示文字內容,但是BoringLayout只能顯示單行文字,因此這裡最好的選擇是 直接用StaticLayout
我們選擇了自定義View,並希望最終有這樣的一個介面:
public class StaticLayoutView extends View { private Layout layout = null; public void setLayout(Layout layout) { this.layout = layout; requestLayout(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); if (layout != null) { layout.draw(canvas, null, null, 0); } canvas.restore(); } }
我們可以直接通過設定這個view的Layout來繪製文字,並在onDraw方法中直接使用這個Layout物件來繪製文字。在這裡我們摒棄了setText方法,直接通過Layout來繪製文字,而這裡的Layout物件,我們可以通過預先建立之後才設定進去(這裡可以放到單獨的一個執行緒中建立),這樣對比起普通TextView的setText方法,我們減少了setText中的許多消耗,可以大幅度的提升效率。
StaticLayout的建立非常簡單,只需要給定文字,寬度等就能直接建立。另外,為了預先填充TextLayoutCache,我們也可以在建立完StaticLayout物件之後,預先在一個dummy canvas中draw出來:
StaticLayout layout = new StaticLayout(TestSpan.getSpanString(i), textPaint, hardCodeWidth, alignment, 1.0f, 0f, true); layout.draw(dummyCanvas);
效能對比
接下來我們測試一下具體的效能,這裡的testcase放到了Github上:StaticLayoutView
testcase的內容為,在一個ListView中,顯示300個Item,每個item都是一段純文字,裡面全都是包含有大量 ImageSpan的SpannableString,進行兩邊的對比,一邊是直接使用StaticLayout,一邊是使用普通的TextView,並 且這300段文字不全相同,長度不同,隨機生成,在StaticLayout的testcase中,StaticLayout都是預先在另外一個執行緒建立 好之後才設定進去的,另外SpannableString也是預先生成好的。
另外,在這裡為了模擬真實app繁重的後臺工作,另外建立了3個執行緒,不停在做浮點預算以嘗試搶佔CPU資源。
測量效能的指標為,ListView連續向下滾動,測量其平均幀率為多少,分別測量五次,計算其平均值,最終效能測試結果如下:
這裡測試的機器是MX3,左側是直接使用StaticLayout的方案,右側是系統的預設方案,Y軸是FPS,可以看出來,使用優化之後的方案,幀率提升了許多。
References
Improving Comment Rendering on Android 這篇文章介紹了Instagram如何優化他們的TextView渲染的效率,這也是這裡優化方法的來源,Instagram也是直接使用 StaticLayout並通過預先建立Layout的方法來減少了ListView滾動過程中的掉幀率,並且效果非常顯著。這篇文章算是給出了這裡的原 理解析以及一個簡單的實現。
相關文章
- Android元件詳解—TextViewAndroid元件TextView
- android炫酷的textviewAndroidTextView
- Android 高亮關鍵字TextViewAndroidTextView
- Android:TextView maxWidth maxLines maxLength maxEmsAndroidTextView
- Android中TextView及其子類AndroidTextView
- Android TextView 富文字之 android.text.style.xxxSpanAndroidTextView
- Android AsyncTask 詳解Android
- Android拖拽詳解Android
- Android開發筆記——TextView 多行時 ellipsizeAndroid筆記TextView
- Android入門教程 | Button,TextView背景設定AndroidTextView
- 教你如何實現 Android TextView 文字輪播效果AndroidTextView
- Android Textview 一行居中 兩行居左AndroidTextView
- Android TextView 在指定位置自動省略字元AndroidTextView字元
- Android工程gradle詳解AndroidGradle
- Android Service詳解(一)Android
- Android AIDL使用詳解AndroidAI
- Android Service詳解(二)Android
- Android-Application詳解AndroidAPP
- Android混淆(Proguard)詳解Android
- Android SecureRandom漏洞詳解Androidrandom
- Android 向量圖詳解Android
- Android BroadcastReceiver使用詳解AndroidAST
- Android:動畫詳解Android動畫
- Android Gson使用詳解Android
- Android-Service詳解Android
- Chromium CC渲染層工作流詳解
- 詳解 OpenGL ES 2.x 渲染流程
- Vue 服務端渲染 & 預渲染Vue服務端
- Android更換APP字型—TextView各種字型樣式AndroidAPPTextView
- Android 訊息機制詳解(Android P)Android
- Android FlexboxLayout 佈局詳解AndroidFlex
- Android輸入事件詳解Android事件
- Android-SharedPreferences 使用詳解Android
- Android Bluetooth HCI log 詳解Android
- [轉]Android 通知Notification 詳解Android
- Android 加密知識詳解Android加密
- Android Studio 新特性詳解Android
- android效能調優詳解Android
- Android系統架構詳解(2)--Android RuntimeAndroid架構