UI 優(yōu)化系列專題辞居,來聊一聊 Android 渲染相關知識右锨,主要涉及 UI 渲染背景知識辫封、如何優(yōu)化 UI 渲染兩部分內容厉熟。
UI 優(yōu)化系列專題
- UI 渲染背景知識
《View 繪制流程之 setContentView() 到底做了什么息拜?》
《View 繪制流程之 DecorView 添加至窗口的過程》
《深入 Activity 三部曲(3)View 繪制流程》
《Android 之 LayoutInflater 全面解析》
《關于渲染溉潭,你需要了解什么净响?》
《Android 之 Choreographer 詳細分析》
- 如何優(yōu)化 UI 渲染
《Android 之如何優(yōu)化 UI 渲染(上)》
《Android 之如何優(yōu)化 UI 渲染(下)》
從產品和設計師的角度,他們自然希望應用可以使用豐富的圖形元素喳瓣、更炫酷的動畫來實現流暢的用戶體驗馋贤。但是 Android 系統(tǒng)很有可能無法及時完成這些復雜界面的渲染操作,這個時候就會出現掉幀畏陕。也正因如此配乓,我們才需要做 UI 優(yōu)化。UI 優(yōu)化要解決的核心問題是惠毁,由渲染性能本身造成用戶感知的卡頓犹芹,可以把它理解為卡頓優(yōu)化的一個子集。
工欲善其事鞠绰,必先利其器腰埂,在做 UI 優(yōu)化之前,我們必須先要找到由渲染性能引起卡頓的問題點蜈膨,有哪些工具可以幫助我們分析和測量呢屿笼?今天我們先來聊一聊 UI 渲染的測量方法。
布局分析
在 Android 中翁巍,絕大多數視圖元素都是通過 xml 布局完成的刁卜,接下來我們就從布局優(yōu)化開始逐步的排查和分析。
1. Hierarchy Viewer
說到布局優(yōu)化曙咽,不得不提 Hierarchy Viewer 這款利器蛔趴,它是 Android Device Monitor 中內置的一種布局性能分析工具,它通過柵格化獲取當前布局界面的顯示基元例朱,并將其轉換為屏幕像素的過程孝情。
Tree View:顯示視圖層次結構的樹狀視圖。通過鼠標或底層的縮放控件來拖動和縮放 View 樹洒嗤。每個節(jié)點都指示它的 View 類名和 ID 名稱箫荡。
Tree Overview:應用界面完整視圖層次結構。移動灰色矩形可更改 Tree View 中可見視圖的窗口渔隶。
Layout View:顯示布局線框視圖羔挡。當前所選視圖的輪廓為紅色,其父視圖為淺紅色间唉。
Hierarchy Viewer 可以測量每個視圖節(jié)點相對于其他同級視圖的性能绞灼,因此分析結果中總是會有紅色節(jié)點,紅色節(jié)點并不一定意味著該節(jié)點的布局性能表現不佳呈野。不過它卻是當前視圖層級中表現最差的低矮。
所選節(jié)點的每個子級都有三個圓點,可以是綠色被冒、黃色或紅色军掂。
左側圓點表示渲染管道的繪制過程轮蜕;
中間圓點表示布局階段;
右邊圓點表示執(zhí)行階段蝗锥。
這些圓點大致對應繪制流程的測量跃洛、布局和繪制階段。圓點的顏色表示在當前層級內相對于其他節(jié)點的性能對比终议。
綠色:表示視圖的渲染速度至少比其他一半的視圖要快税课;
黃色:表示視圖渲染速度在當前層級處于中間階段;
紅色:表示視圖渲染速度在當前層級處于最慢之一痊剖。
需要注意韩玩,如果應用的運行速度出乎意料的慢,則紅色節(jié)點可能是有問題的陆馁;在同一個層級內找颓,總有一個最慢的視圖節(jié)點,但是我們只需要確保它是按照我們“預期”的結果即可叮贩。那該如何正確理解是符合預期的紅色圓點呢击狮?
查找子節(jié)點中的紅色圓點或僅包含少數子節(jié)點的視圖容器。
如果一個視圖容器包含許多子視圖和一個紅色圓點益老,此時需要進一步了解子視圖的渲染情況彪蓬。
如果一個視圖容器具有紅色測量階段、紅色布局階段和黃色繪制階段捺萌,這就屬于比較典型的情況档冬,因為它是所有子視圖的父容器,并且要在所有子視圖完成之后其布局才會完成桃纯。
如果具有 20 個以上的視圖容器中酷誓,某個子視圖包含紅色繪制階段,則表示存在問題态坦。需要進一步檢查它的繪制操作盐数。
雖然 Hierarchy Viewer 并不能真實反映設備上的實際渲染性能,但是我們可以通過多次分析以了解平均測量結果伞梯∶登猓總的來看, 它還是能夠幫助我們檢查布局結構中每個視圖模塊的渲染性能谜诫,以及查找出冗余或導致性能瓶頸的視圖節(jié)點漾峡。
Layout Inspector
不過,在 Android Studio 3.1 及以后猜绣, Hierarchy Viewer 已經被棄用灰殴,此時 Android 推薦使用 Layout Inspector 來檢查應用的視圖層次結構。
使用 Layout Inspector 可以將應用布局與設計模型進行比較掰邢、顯示應用的放大視圖牺陶,并在運行時檢查其布局細節(jié)。
View Tree:布局中視圖的層次結構辣之;
Layout Inspector 工具欄:Layout Inspector 包含的工具掰伸;
屏幕截圖:設備上顯示的應用布局的屏幕截圖,其中顯示了每個視圖的布局邊界怀估;
Properties Table:選定視圖的布局屬性狮鸭。
整體來看,Layout Inspector 并沒有太多亮點功能多搀,相反它還取消了 Hierarchy Viewer 中有關布局性能的分析信息歧蕉。Hierarchy Viewer 接入真機調試也非常容易,具體你可以參考這里康铭。
2. Use Lint
在布局文件中運行 Lint 工具惯退,以搜索視圖結構中可能潛在的問題,將始終是一個好的習慣从藤!Lint 取代了早期 Layoutopt 工具催跪,并且已經默認被集成到 Android Studio。
無論何時編譯項目 Lint 都會自動運行夷野,檢查 Android 源文件是否存在潛在的錯誤懊蒸,并提供修改建議以及可以直接跳轉到問題代碼進行審查。那 Lint 可以幫助我們檢查哪些布局問題呢悯搔?
- 使用復合繪圖 — 僅包含一個 ImageView 和 TextView 的 LinearLayout 可以替換為更有效的復合 Drawable骑丸,具體可以參考 Compound Drawables。
TextView tv = (TextView) findViewById( R.id.textView );
tv.setCompoundDrawablesWithIntrinsicBounds( 0, R.drawable.ic_launcher, 0, 0 );
合并根視圖 — 如果 FrameLayout 是布局的根視圖妒貌,并且不提供背景或填充者娱,那么此時可以使用 merge 標簽來替換它。
無用的子視圖 — 沒有子元素或沒有背景的布局通乘沾В可以被刪除(因為它是不可見的)黄鳍,從而獲得更加扁平、有效的布局結構平匈。
無用的父視圖 — 一個沒有子視圖的的布局元素框沟,如果不是 ScrollView 或者跟視圖,也沒有 Background 是可以被移除的增炭。并且讓它的子元素直接移動到父視圖中忍燥。從而獲得更加扁平、有效的布局結構隙姿。
布局深度 — 布局層級越深展開花費時間就越長梅垄,應盡可能保持布局扁平化設計,如 RelativeLayout 或 GridLayout 來提高性能输玷。它們默認最大深度為 10队丝。不過現在 Android 更加推薦使用 ConstraintLayout靡馁。
下圖展示了 Lint 工具如何檢查應用源文件,源文件包括:Java机久、Kotlin 和 XML 文件臭墨、圖標以及 ProGuard 配置文件等。有關 Lint 的更多配置你可以參考《使用 Lint 檢查改進您的代碼》
渲染測量
1. Show GPU Overdraw
在 Android 4.2膘盖,系統(tǒng)增加了檢測過渡繪制(Overdraw)的工具胧弛,通過對應用界面進行顏色編碼來幫助我們識別過渡繪制。具體可以參考《檢查 GPU 渲染速度和繪制過渡》侠畔。
當應用界面在同一幀內多次繪制同一個像素時结缚,便會發(fā)生過渡繪制。這種可視化會工具可以顯示出應用界面不必要的渲染工作软棺;因為红竭,這樣的繪制任務往往對用戶是不可見的。因此码党,我們應該盡可能避免過渡繪制的場景德崭。
過渡繪制顏色等級劃分為 5 個階段:
真彩色:沒有發(fā)生過渡繪制;每個像素點僅繪制 1 次揖盘;
藍色:過渡繪制 1 次眉厨,這部分像素在屏幕上繪制了 2 次;
綠色:過渡繪制 2 次兽狭,這部分像素在屏幕上繪制了3 次憾股;
粉色:過渡繪制 3 次,這部分像素在屏幕上繪制了 4 次箕慧;
紅色:過渡繪制 4 次或更多次服球,這部分像素在屏幕上繪制了 5 次以上。
注意颠焦,有些過渡繪制可能是不可避免的斩熊。在實際開發(fā)過程中,我們應盡可能保證更多的原色或藍色區(qū)域伐庭,極少部分的綠色或粉色粉渠,最好不要出現紅色。
- 減少布局層級是根絕 Overdraw 最有效的手段
2. Profile GPU Rendering
我們還可以開啟 Profile GPU Rendering 檢查界面的渲染性能圾另。該工具以滾動柱狀圖的形式霸株,直觀地展示了渲染界面每幀所花費的時間(以每幀 16ms 的速度作為對比基準)。
在 Android 6.0(API Level 23)之后集乔,會輸出下面的計算和繪制每個階段的耗時:
在 4.0(API Level 14)和 5.0(API Level 21)之間的 Android 版本具有藍色去件、紫色、紅色和橙色區(qū)段。低于 4.0 的 Android 版本只有藍色尤溜、紅色和橙色區(qū)段倔叼。
如果我們把上面的步驟轉化為線程模型,可以得到下面的流水線模型靴跛。CPU 將數據同步(sync)給 GPU 之后缀雳,一般不會阻塞等待 GPU 渲染完畢渡嚣,而是通知結束后就返回梢睛。而 RenderThread 承擔了比較多的繪制工作,分擔了主線程很多壓力识椰,提高了 UI 線程的響應速度绝葡。
通過上面的一些分析和測量工具,我們可以初步判斷應用 UI 渲染的性能是否達標腹鹉,例如經常出現的掉幀藏畅、掉幀主要發(fā)生在哪一個階段、是否存在 Overdraw 等功咒。
數據測量
雖然這些圖形化界面工具非常好用愉阎,但是它們難以提供準確的測量數據,那有哪些測量方法可以更精確的實現渲染測量呢力奋?
1. Systrace
在 Android 4.1榜旦,系統(tǒng)還新增了 Systrace 性能分析工具。Systrace 默認只能監(jiān)控系統(tǒng)特定調用的耗時情況景殷,例如跟蹤系統(tǒng)的 I/O 操作溅呢、CPU 負載、Surface 渲染猿挚、GC 等事件咐旧。而且性能開銷非常低。
Systrace 利用了 Linux 的 ftrace 調試工具绩蜻,相當于在系統(tǒng)各個關鍵位置都添加了一些性能探針铣墨,也就是在代碼里加了一些性能監(jiān)控的埋點。Android 在 ftrace 的基礎上封裝了 atrace办绝,并增加了更多特有的探針伊约,例如 Graphics、Activity Manager八秃、Dalvik VM碱妆、System Server 等。
Systrace 會生成一份包含多個部分的 HTML 文件昔驱,包括每個進程渲染界面沿時間軸所指明的渲染幀疹尾。具體該如何解讀這份報告你可以參考 Systrace 報告。
拿起筆,劃重點了
由于系統(tǒng)預留了 Trace.beginSection 接口來監(jiān)聽應用程序的調用耗時纳本,我們可以在 Systrace 上增加應用程序的耗時分析:通過編譯時給每個函數插樁的方式來實現窍蓝,也就是在重要函數的入口和出口分別增加 Trace.beginSection 和 Trace.endSection。當然出于性能考慮繁成,我們需要過濾大部分指令數比較少的函數吓笙,這樣就實現了在 Systrace 基礎上增加應用程序耗時的監(jiān)控。具體你可以參考為 Systrace 定義自定義事件巾腕。
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Trace.beginSection("MyAdapter.onCreateViewHolder");
MyViewHolder myViewHolder;
try {
myViewHolder = MyViewHolder.newInstance(parent);
} finally {
Trace.endSection();
}
return myViewHolder;
}
}
2. Tracer for OpenGL ES
Tracer for OpenGL ES 也是 Android 4.1 新增加的工具面睛,它可以逐幀、逐函數的記錄 App 用 OpenGL ES 的繪制過程尊搬。它提供了每個 OpenGL 函數調用的消耗時間叁鉴,所以很多時候用來做性能分析。但因為其強大的記錄功能佛寿,在分析渲染問題時幌墓,當 TraceView、Systrace 都顯得棘手冀泻,還找不到問題所在時常侣,此時這個工具就會派上用場了。
Graphics API Debugger
不過弹渔,正如前面文章中介紹 Android 渲染框架的演進非掣焓快,在 Android Studio 3.1 之后捞附,Android 推薦使用 Graphics API Debugger (GAPID)來替代 Tracer for OpenGL ES 工具巾乳。GAPID 可以說是它的升級版本,不僅支持跨平臺鸟召,而且功能更加強大胆绊,支持 Vulkan 和回放。
通過上面的幾個工具欧募,我們可以進一步判斷压状、分析應用 UI 渲染性能數據,并且能夠找出潛在的渲染瓶頸點跟继。
自動化測試場景
雖然上面這些工具已經非常強大种冬,不過線下的測試無論如何也難以復現線上大數據用戶場景,此時我們需要一套能夠在產品上線后的自動化測量工具舔糖,那有哪些測量方法可以滿足我們的需求呢娱两?
1. gfxinfo
gfx info 可以輸出包含各階段的動畫以及幀相關的性能分析,具體命令如下:
adb shell dumpsys gfxinfo packageName
除了渲染的性能之外金吗,gfxinfo 還可以拿到渲染相關的內存和 View Hierarchy 信息十兢。
View Hierarchy:
com.xxx.android.xxx/com.weex.app.DebugActivity/android.view.ViewRootImpl@f80e215
14 views, 11.05kB of display lists
com.xxx.android.xxx/com.weex.app.WeexActivity/android.view.ViewRootImpl@d92a
84 views, 96.78kB of display lists
Total ViewRootImol:2
Total Views: 98
Total DisplayList: 107.82 kB
在 Android 6.0 之后趣竣,gfxinfo 命令新增了 framestats 參數,可以拿到最近 120 幀每個繪制階段的耗時信息旱物。
adb shell dumpsys gfxinfo packageName framestats
通過這個命令我們可以實現自動化統(tǒng)計應用的幀率遥缕,更進一步還可以實現自定義的 “Profile GPU Rendering” 工具,在出現掉幀的時候宵呛,自動統(tǒng)計分析是哪個階段的耗時增長最快单匣,同時給出相應建議。
2. SurfaceFlinger
除了耗時宝穗,我們還比較關心渲染使用的內存户秤。在前面文章我們有講過,Android 4.1 以后每個 Surface 都會有三個 Graphic Buffer讽营,那如何查看 Graphic Buffer 占用的內存虎忌,系統(tǒng)是怎么樣管理這部分的內存的呢泡徙?
你可以通過下面的命令拿到 SurfaceFlinger 相關的信息:
adb shell dumpsys SurfaceFlinger
以我的測試項目為例橱鹏,應用使用了三個 Graphic Buffer 緩沖區(qū),當前用在顯示的第 0 個 Graphic Buffer堪藐,大小是 1080 * 1920莉兰。這樣我們可以更好地理解三緩沖機制,而且可以看到這三個 Graphic Buffer 的確在交替使用礁竞。
Layer 0x7b5dfb7000 (com.xxx.android.bbt/com.xxx.android.xxx.MainActivity)
// 序號 // 狀態(tài) // 對象 // 大小
[00:0x7be3a490e0] state=ACQUIRED 0x7be3a2eaa0 frame=471 [1080x1920:1088, 1]
[02:0x7be3a499a0] state=FREE 0x7be3a50780 frame=469 [1080x1920:1088, 1]
[01:0x7be3a492a0] state=FREE 0x7b5e883e60 frame=470 [1080x1920:1088, 1]
看下三個 Buffer 分別占用的內存:
Allocated buffers:
0x7be1bf6360: 8160.00 KiB | 1080 (1088) x 1920 | 1 | 1 | 0x20001a00
0x7be1bf63c0: 8160.00 KiB | 1080 (1088) x 1920 | 1 | 1 | 0x20001a00
0x7be1bf6420: 8160.00 KiB | 1080 (1088) x 1920 | 1 | 1 | 0x20001a00
這部分的內存其實真的不小糖荒,特別現在手機分辨率越來越大,而且應用可能還會存在其他 Surface 的情況模捂,例如 SurfaceView 或者 TextureView捶朵。
3. Choreographer
幀率,業(yè)界一般都使用 Choreographer 來監(jiān)控應用的幀率狂男。但是需要排除頁面在沒有操作的情況综看,也就是說只在界面存在繪制的時候才做統(tǒng)計。
那么如何監(jiān)聽界面是否存在繪制行為呢岖食?具體你可以參考《Android 之 ViewTreeObserver 全面解析》红碑。
getWindow().getDecorView().getViewTreeObserver().addOnDrawListener
我們經常用平均幀率來衡量界面流暢度,但事實上電影的幀率才 24 幀泡垃,用戶對于應用的平均幀率是 40 幀還是 50 幀并不一定可以感受出來析珊。對于用戶來說,感覺最明顯的是連續(xù)丟幀情況蔑穴,Android Vitals 將連續(xù)丟幀超過 700 ms 定義為凍幀忠寻,即連續(xù)丟幀超過 42 幀。
因此存和,我們可以統(tǒng)計更有價值的凍幀率奕剃。凍幀率就是計算發(fā)生凍幀時間在所有時間的占比赶舆。出現丟幀的時候,我們可以獲取當前的頁面信息祭饭、View 信息和操作路徑進行上報芜茵,降低二次排查的難度。
另外還可以進一步細化問題倡蝙,按照 Activity九串、Fragment 或者某個操作定義場景,通過細化不同場景的平均幀率和凍幀率寺鸥,進一步細化問題排查的范圍猪钮。
最后
Android 渲染框架在快速的演進,可能隨著版本的升級胆建,有些工具也不再適用烤低;工具只是幫助我們排查問題的一種手段,比工具本省更重要的是了解和學習它們背后的工作原理笆载,這對我們的成長會有很大的幫助扑馁。
日常開發(fā)中我們不能只滿足于完成需求就可以了,在實現的同時還要多去思考例如內存凉驻、卡頓腻要、渲染等這些影響性能的點,日積月累我們的進步自然也會更快一些涝登。
相信你也肯定有很多好的渲染分析或其他性能優(yōu)化的思路和方法雄家,歡迎大家留言分享你的性能優(yōu)化“必殺技”。文中如有不妥或有更好的分析結果胀滚,歡迎您的指正趟济。
文章如果對你有幫助,就請留個贊吧咽笼!
擴展閱讀
關于 UI 渲染顷编,你需要了解什么?
Android 之你真的了解 View.post() 原理嗎褐荷?
深入 Activity 三部曲(3)之 View 繪制流程
...
其他系列專題
Android 存儲優(yōu)化系列專題
Android 之不要濫用 SharedPreferences
Android 存儲選項之 SQLite 優(yōu)化那些事兒
Android 對象序列化之追求完美的 Serial
...