Android 之如何優(yōu)化 UI 渲染(上)

UI 優(yōu)化系列專題,來聊一聊 Android 渲染相關知識,主要涉及 UI 渲染背景知識如何優(yōu)化 UI 渲染兩部分內(nèi)容。


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 渲染(下)


從產(chǎn)品和設計師的角度,他們自然希望應用可以使用豐富的圖形元素、更炫酷的動畫來實現(xiàn)流暢的用戶體驗。但是 Android 系統(tǒng)很有可能無法及時完成這些復雜界面的渲染操作,這個時候就會出現(xiàn)掉幀。也正因如此,我們才需要做 UI 優(yōu)化。UI 優(yōu)化要解決的核心問題是,由渲染性能本身造成用戶感知的卡頓,可以把它理解為卡頓優(yōu)化的一個子集。

工欲善其事,必先利其器,在做 UI 優(yōu)化之前,我們必須先要找到由渲染性能引起卡頓的問題點,有哪些工具可以幫助我們分析和測量呢?今天我們先來聊一聊 UI 渲染的測量方法。

布局分析

在 Android 中,絕大多數(shù)視圖元素都是通過 xml 布局完成的,接下來我們就從布局優(yōu)化開始逐步的排查和分析。

1. Hierarchy Viewer

說到布局優(yōu)化,不得不提 Hierarchy Viewer 這款利器,它是 Android Device Monitor 中內(nèi)置的一種布局性能分析工具,它通過柵格化獲取當前布局界面的顯示基元,并將其轉(zhuǎn)換為屏幕像素的過程。

  • Tree View:顯示視圖層次結構的樹狀視圖。通過鼠標或底層的縮放控件來拖動和縮放 View 樹。每個節(jié)點都指示它的 View 類名和 ID 名稱。

  • Tree Overview:應用界面完整視圖層次結構。移動灰色矩形可更改 Tree View 中可見視圖的窗口。

  • Layout View:顯示布局線框視圖。當前所選視圖的輪廓為紅色,其父視圖為淺紅色。

Hierarchy Viewer 可以測量每個視圖節(jié)點相對于其他同級視圖的性能,因此分析結果中總是會有紅色節(jié)點,紅色節(jié)點并不一定意味著該節(jié)點的布局性能表現(xiàn)不佳。不過它卻是當前視圖層級中表現(xiàn)最差的。

所選節(jié)點的每個子級都有三個圓點,可以是綠色、黃色或紅色

  • 左側圓點表示渲染管道的繪制過程;

  • 中間圓點表示布局階段;

  • 右邊圓點表示執(zhí)行階段。

這些圓點大致對應繪制流程的測量、布局和繪制階段。圓點的顏色表示在當前層級內(nèi)相對于其他節(jié)點的性能對比。

  • 綠色:表示視圖的渲染速度至少比其他一半的視圖要快;

  • 黃色:表示視圖渲染速度在當前層級處于中間階段;

  • 紅色:表示視圖渲染速度在當前層級處于最慢之一。

需要注意,如果應用的運行速度出乎意料的慢,則紅色節(jié)點可能是有問題的;在同一個層級內(nèi),總有一個最慢的視圖節(jié)點,但是我們只需要確保它是按照我們“預期”的結果即可。那該如何正確理解是符合預期的紅色圓點呢?

  • 查找子節(jié)點中的紅色圓點或僅包含少數(shù)子節(jié)點的視圖容器。

  • 如果一個視圖容器包含許多子視圖和一個紅色圓點,此時需要進一步了解子視圖的渲染情況。

  • 如果一個視圖容器具有紅色測量階段、紅色布局階段和黃色繪制階段,這就屬于比較典型的情況,因為它是所有子視圖的父容器,并且要在所有子視圖完成之后其布局才會完成。

  • 如果具有 20 個以上的視圖容器中,某個子視圖包含紅色繪制階段,則表示存在問題。需要進一步檢查它的繪制操作。

雖然 Hierarchy Viewer 并不能真實反映設備上的實際渲染性能,但是我們可以通過多次分析以了解平均測量結果。總的來看, 它還是能夠幫助我們檢查布局結構中每個視圖模塊的渲染性能,以及查找出冗余或?qū)е滦阅芷款i的視圖節(jié)點。

Layout Inspector

不過,在 Android Studio 3.1 及以后, Hierarchy Viewer 已經(jīng)被棄用,此時 Android 推薦使用 Layout Inspector 來檢查應用的視圖層次結構。

使用 Layout Inspector 可以將應用布局與設計模型進行比較、顯示應用的放大視圖,并在運行時檢查其布局細節(jié)。

  • \color{rgb(255,0,0)}{ ① }View Tree:布局中視圖的層次結構;

  • \color{rgb(255,0,0)}{②}Layout Inspector 工具欄:Layout Inspector 包含的工具;

  • \color{rgb(255,0,0)}{③}屏幕截圖:設備上顯示的應用布局的屏幕截圖,其中顯示了每個視圖的布局邊界;

  • \color{rgb(255,0,0)}{④}Properties Table:選定視圖的布局屬性。

整體來看,Layout Inspector 并沒有太多亮點功能,相反它還取消了 Hierarchy Viewer 中有關布局性能的分析信息。Hierarchy Viewer 接入真機調(diào)試也非常容易,具體你可以參考這里

2. Use Lint

在布局文件中運行 Lint 工具,以搜索視圖結構中可能潛在的問題,將始終是一個好的習慣!Lint 取代了早期 Layoutopt 工具,并且已經(jīng)默認被集成到 Android Studio。

無論何時編譯項目 Lint 都會自動運行,檢查 Android 源文件是否存在潛在的錯誤,并提供修改建議以及可以直接跳轉(zhuǎn)到問題代碼進行審查。那 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。不過現(xiàn)在 Android 更加推薦使用 ConstraintLayout。

下圖展示了 Lint 工具如何檢查應用源文件,源文件包括:Java、Kotlin 和 XML 文件、圖標以及 ProGuard 配置文件等。有關 Lint 的更多配置你可以參考《使用 Lint 檢查改進您的代碼


渲染測量

1. Show GPU Overdraw

在 Android 4.2,系統(tǒng)增加了檢測過渡繪制(Overdraw)的工具,通過對應用界面進行顏色編碼來幫助我們識別過渡繪制。具體可以參考《檢查 GPU 渲染速度和繪制過渡》。

當應用界面在同一幀內(nèi)多次繪制同一個像素時,便會發(fā)生過渡繪制。這種可視化會工具可以顯示出應用界面不必要的渲染工作;因為,這樣的繪制任務往往對用戶是不可見的。因此,我們應該盡可能避免過渡繪制的場景。

過渡繪制顏色等級劃分為 5 個階段:

  1. 真彩色:沒有發(fā)生過渡繪制;每個像素點僅繪制 1 次;

  2. 藍色:過渡繪制 1 次,這部分像素在屏幕上繪制了 2 次;

  3. 綠色:過渡繪制 2 次,這部分像素在屏幕上繪制了3 次;

  4. 粉色:過渡繪制 3 次,這部分像素在屏幕上繪制了 4 次;

  5. 紅色:過渡繪制 4 次或更多次,這部分像素在屏幕上繪制了 5 次以上。

注意,有些過渡繪制可能是不可避免的。在實際開發(fā)過程中,我們應盡可能保證更多的原色或藍色區(qū)域,極少部分的綠色或粉色,最好不要出現(xiàn)紅色。

  • 減少布局層級是根絕 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ū)段。

如果我們把上面的步驟轉(zhuǎn)化為線程模型,可以得到下面的流水線模型。CPU 將數(shù)據(jù)同步(sync)給 GPU 之后,一般不會阻塞等待 GPU 渲染完畢,而是通知結束后就返回。而 RenderThread 承擔了比較多的繪制工作,分擔了主線程很多壓力,提高了 UI 線程的響應速度。

通過上面的一些分析和測量工具,我們可以初步判斷應用 UI 渲染的性能是否達標,例如經(jīng)常出現(xiàn)的掉幀、掉幀主要發(fā)生在哪一個階段、是否存在 Overdraw 等。


數(shù)據(jù)測量

雖然這些圖形化界面工具非常好用,但是它們難以提供準確的測量數(shù)據(jù),那有哪些測量方法可以更精確的實現(xiàn)渲染測量呢?

1. Systrace

在 Android 4.1,系統(tǒng)還新增了 Systrace 性能分析工具。Systrace 默認只能監(jiān)控系統(tǒng)特定調(diào)用的耗時情況,例如跟蹤系統(tǒng)的 I/O 操作、CPU 負載、Surface 渲染、GC 等事件。而且性能開銷非常低。

Systrace 利用了 Linux 的 ftrace 調(diào)試工具,相當于在系統(tǒng)各個關鍵位置都添加了一些性能探針,也就是在代碼里加了一些性能監(jiān)控的埋點。Android 在 ftrace 的基礎上封裝了 atrace,并增加了更多特有的探針,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。

Systrace 會生成一份包含多個部分的 HTML 文件,包括每個進程渲染界面沿時間軸所指明的渲染幀。具體該如何解讀這份報告你可以參考 Systrace 報告


拿起筆,劃重點了

由于系統(tǒng)預留了 Trace.beginSection 接口來監(jiān)聽應用程序的調(diào)用耗時,我們可以在 Systrace 上增加應用程序的耗時分析:通過編譯時給每個函數(shù)插樁的方式來實現(xiàn),也就是在重要函數(shù)的入口和出口分別增加 Trace.beginSection 和 Trace.endSection。當然出于性能考慮,我們需要過濾大部分指令數(shù)比較少的函數(shù),這樣就實現(xiàn)了在 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 新增加的工具,它可以逐幀、逐函數(shù)的記錄 App 用 OpenGL ES 的繪制過程。它提供了每個 OpenGL 函數(shù)調(diào)用的消耗時間,所以很多時候用來做性能分析。但因為其強大的記錄功能,在分析渲染問題時,當 TraceView、Systrace 都顯得棘手,還找不到問題所在時,此時這個工具就會派上用場了。

Graphics API Debugger

不過,正如前面文章中介紹 Android 渲染框架的演進非常快,在 Android Studio 3.1 之后,Android 推薦使用 Graphics API Debugger (GAPID)來替代 Tracer for OpenGL ES 工具。GAPID 可以說是它的升級版本,不僅支持跨平臺,而且功能更加強大,支持 Vulkan 和回放。

通過上面的幾個工具,我們可以進一步判斷、分析應用 UI 渲染性能數(shù)據(jù),并且能夠找出潛在的渲染瓶頸點。


自動化測試場景

雖然上面這些工具已經(jīng)非常強大,不過線下的測試無論如何也難以復現(xiàn)線上大數(shù)據(jù)用戶場景,此時我們需要一套能夠在產(chǎn)品上線后的自動化測量工具,那有哪些測量方法可以滿足我們的需求呢?

1. gfxinfo

gfx info 可以輸出包含各階段的動畫以及幀相關的性能分析,具體命令如下:

adb shell dumpsys gfxinfo packageName

除了渲染的性能之外,gfxinfo 還可以拿到渲染相關的內(nèi)存和 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 參數(shù),可以拿到最近 120 幀每個繪制階段的耗時信息。

adb shell dumpsys gfxinfo packageName framestats

通過這個命令我們可以實現(xiàn)自動化統(tǒng)計應用的幀率,更進一步還可以實現(xiàn)自定義的 “Profile GPU Rendering” 工具,在出現(xiàn)掉幀的時候,自動統(tǒng)計分析是哪個階段的耗時增長最快,同時給出相應建議

2. SurfaceFlinger

除了耗時,我們還比較關心渲染使用的內(nèi)存。在前面文章我們有講過,Android 4.1 以后每個 Surface 都會有三個 Graphic Buffer,那如何查看 Graphic Buffer 占用的內(nèi)存,系統(tǒng)是怎么樣管理這部分的內(nèi)存的呢?

你可以通過下面的命令拿到 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 分別占用的內(nèi)存:

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

這部分的內(nèi)存其實真的不小,特別現(xiàn)在手機分辨率越來越大,而且應用可能還會存在其他 Surface 的情況,例如 SurfaceView 或者 TextureView

3. Choreographer

幀率,業(yè)界一般都使用 Choreographer 來監(jiān)控應用的幀率。但是需要排除頁面在沒有操作的情況,也就是說只在界面存在繪制的時候才做統(tǒng)計。

那么如何監(jiān)聽界面是否存在繪制行為呢?具體你可以參考《Android 之 ViewTreeObserver 全面解析》。

getWindow().getDecorView().getViewTreeObserver().addOnDrawListener

我們經(jīng)常用平均幀率來衡量界面流暢度,但事實上電影的幀率才 24 幀,用戶對于應用的平均幀率是 40 幀還是 50 幀并不一定可以感受出來。對于用戶來說,感覺最明顯的是連續(xù)丟幀情況,Android Vitals 將連續(xù)丟幀超過 700 ms 定義為凍幀,即連續(xù)丟幀超過 42 幀。

因此,我們可以統(tǒng)計更有價值的凍幀率。凍幀率就是計算發(fā)生凍幀時間在所有時間的占比。出現(xiàn)丟幀的時候,我們可以獲取當前的頁面信息、View 信息和操作路徑進行上報,降低二次排查的難度。

另外還可以進一步細化問題,按照 Activity、Fragment 或者某個操作定義場景,通過細化不同場景的平均幀率和凍幀率,進一步細化問題排查的范圍。


最后

Android 渲染框架在快速的演進,可能隨著版本的升級,有些工具也不再適用;工具只是幫助我們排查問題的一種手段,比工具本省更重要的是了解和學習它們背后的工作原理,這對我們的成長會有很大的幫助。

日常開發(fā)中我們不能只滿足于完成需求就可以了,在實現(xiàn)的同時還要多去思考例如內(nèi)存、卡頓、渲染等這些影響性能的點,日積月累我們的進步自然也會更快一些。


相信你也肯定有很多好的渲染分析或其他性能優(yōu)化的思路和方法,歡迎大家留言分享你的性能優(yōu)化“必殺技”。文中如有不妥或有更好的分析結果,歡迎您的指正。

文章如果對你有幫助,就請留個贊吧!


擴展閱讀

關于 UI 渲染,你需要了解什么?
Android 之你真的了解 View.post() 原理嗎?
深入 Activity 三部曲(3)之 View 繪制流程
...

其他系列專題

Android 存儲優(yōu)化系列專題
Android 之不要濫用 SharedPreferences
Android 存儲選項之 SQLite 優(yōu)化那些事兒
Android 對象序列化之追求完美的 Serial
...

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373