Android UI項目優化兩年經驗之談

Android系統的卡頓,不流暢是一個被詬病多年,讓不少用戶以爲體驗不如iOS的問題。可是通過Google多年對Android系統的迭代,實際上在系統層面上Android已經在UI渲染性能上作出了不少改進和優化,對於應用開發者而言,很大程度上,寫不出流暢的頁面和動畫已經再也不是系統的鍋,而是應該努力去學習和理解Android系統的特性,發如今渲染過程當中的瓶頸,作出合理的優化。

回顧Android的歷史版本,咱們能夠一覽Google Android團隊在各版本上面作出的具體改進。android

Android2.x時代,UI渲染採用CPU軟渲染,基本上滑動列表只能維持在30fps左右,那個時代硬件性能和系統架構確實都不夠好。緩存

  • 2011年,Android 3.0 Honeycomb發佈,Android開始支持GPU硬件加速渲染,採用OpenGL渲染UI,第一款Android 3.0的設備是MOTO ZOOM,一款分辨率爲1280800像素的平板電腦,第一次達到如此高的分辨率( 此前的Android手機最高分辨率爲480854),若是說沒有硬件加速,Android系統僅靠CPU很難流暢渲染這麼高的分辨率。安全

  • 2012年,Android4.1 JellyBean推出的「Project butter」(黃油計劃)帶來了vSync(垂直同步)和tripple buffer(三重緩衝)技術。vSync定時中斷讓Android系統渲染更加有節奏,tripple buffer讓系統在偶爾丟幀的狀況下經過多生成一個buffer解決了以前在丟幀時因buffer不足而致使無法按時渲染下一幀的問題。此外還優化了觸摸事件,經過CPU input boost和預測觸摸行爲減小觸摸延時,讓系統響應更加靈敏。Android4.1是我認爲Android UI渲染架構革新史上里程碑式的一個版本,Cortex雙核 A9 CPU+PowerVR SGX540 GPU的Galaxy Nexus渲染720p的屏幕,刷上JellyBean系統以後對比4.0 ICS是巨大的提高,基本能流暢運行在60fps。性能優化

  • 2013年,Android4.3 JellyBean發佈,在圖形渲染架構上更進一步優化,經過對GPU繪製指令的Reorder&Merge,減小了GPU在繪製時上下文的切換和繪製指令執行的數量,一樣的內容能以更小的消耗去渲染,提升了渲染效率從而提升了渲染性能。架構

  • 2014年,Android5.0 Lollipop發佈,帶來了全新的Material Design設計風格和分擔主線程負載的RenderThread,在主線程更新完DisplayList以後,將draw commands所有都同步到RenderThread,而後由RenderThread繼續去與OpenGL Render(GPU)打交道完成最終的繪製,而主線程能夠在同步完draw commands以後開始處理接下來的input event或進入Idle狀態,很明顯這減小了主線程的負擔,也能讓主線程更快抽身出來響應用戶的操做減小延遲。另外在較新版本的RecyclerView庫中,經過一個叫GapWorker的類在Android 5.0及以上利用主線程在同步完draw commands以後等待下一幀繪製的這一點空閒時間,對將要顯示的ViewHolder作了預加載操做,從而讓列表在接下來的滑動中更加流暢。app

以後這些年的Android6.x-8.x版本對於性能的改進主要集中在ART Runtime執行效率和速度的改進,以及更加嚴格的後臺運行限制。代碼執行更快,後臺應用活動受到限制從而減小了與前臺應用搶資源的可能,必定程度上也是提升了UI渲染性能。不過也有一些手機廠商的定製系統(ROM),修改系統框架,加了太多定製功能,帶來了一些「負優化」,相同甚至更好的硬件性能,運行流暢度比不上相同版本的原生系統。總而言之從5.0開始,Android系統的UI渲染架構確實已經足夠好,並且硬件性能還在以每一年20-30%的速度進步,因此在現在的軟硬件環境下,咱們徹底能夠經過合理優化寫出滑動流暢的界面和過渡優雅的動畫。框架

咱們知道UI渲染大體分爲 Measure->Layout->Draw三個階段,這也是咱們編寫自定義View必須本身實現的三個方法。

image

根據多年對Android系統的瞭解和開發經驗的總結,列出了以下表格工具

image

在Measure/Layout階段,咱們可能更多關注ViewGroup,即RelativeLayout,ConstraintLayout,LinearLayout,FrameLayout等這些佈局類。總體的一個原則就是,儘可能減小子view的數量和layout的嵌套。佈局

由於過深的View層級會致使測量階段耗時較多,複雜的依賴和約束關係可能致使屢次測量。咱們能夠經過不少系統提供的工具來查看頁面的View層級,仔細審視,看是否有可優化的地方。性能

Hierarchy Viewer是Android SDK裏面提供的一個能夠查看調試應用Activity頁面View層級的工具,而且能夠手動觸發頁面從新渲染,觀察每一個view節點的各個階段的耗時狀況。

具體的使用教程能夠參看官方文檔:developer.android.com/studio/prof…

不過這個工具目前已經被官方做廢了,Android Studio裏面推出了新的LayoutInspector來代替他,不過目前LayoutInspector只能查看View層級沒法查看節點渲染耗時。

image

LayoutInspector是集成在Android Studio裏面的工具,能夠詳細查看調試應用的View層級,以及view節點的各類屬性值。

image

另外還有開發者選項裏面的「顯示佈局邊界」,也可讓咱們比較方便的查看當前界面的View節點組成和分佈狀況。與Hierarchy Viewer和LayoutInspector不一樣的是,

顯示佈局邊界能夠查看任何應用界面的View節點大小和位置。不過他查看到的信息有限,只能根據描線看到View節點的邊界,位置等信息。 也許是出於安全考慮,沒法查看到更多信息,不過咱們能夠從黑盒角度審視其餘應用界面的佈局學習別人的作法和思路。

image

1.經過合併多個要顯示的數據到一個View來實現減小View個數

查看界面View層級的目的是爲在不影響功能實現的狀況下儘量減小View數量和優化View層級,咱們能夠經過合併多個要顯示的數據到一個View上來實現減小View的目標, 以下圖展現了幾種優化方案

image

2.採用ConstraintLayout減小布局嵌套

傳統方式,對於以下佈局咱們可能會採用RelativeLayout和LinearLayout嵌套的方式完成佈局,雖然RelativeLayout能夠定義view之間的相對關係和約束關係,

可是他沒法完成百分比佈局,按照權重weight佈局,寬高比等佈局的場景,因此咱們爲了完成複雜界面的佈局,每每須要大量的嵌套不一樣的layout來組合完成。

image

因此2017年Google在I/O大會上推出了ConstraintLayout來解決此問題,他不只擁有空前強大的佈局約束屬性,更重要的是性能比RelativeLayout更好,

他足夠豐富的佈局功能以至於不須要嵌套就能完成很是複雜的界面佈局,因此Google推出ConstraintLayout的目的就是爲了取代RelativeLayout

來優化UI佈局的性能。並且ConstraintLayout是一個不斷開發迭代的layout庫,不斷優化的性能和擴展的功能讓他愈來愈強大,學會使用ConstraintLayout 是每個位Android開發者都必需要學會的高級技能。

關於學習ConstraintLayout,這裏推薦一些文章供你們學習。相信你們學會ConstraintLayout以後, 就不會再想用RelativeLayout了,並且也儘可能不要再用RelativeLayout,由於即便實現相同功能其性能相對ConstraintLayout也不如。

ConstraintLayout徹底解析

ConstraintLayout官方API文檔

ConstraintLayout資源集合

以下界面是採用ConstraintLayout佈局的相同界面,能夠看到採用ConstraintLayout以後,View的數量和層級都大爲減小,這能夠顯著明顯減小View在 測量階段的耗時。

推薦選擇Layout順序:優先FrameLayout(性能最好)>LinearLayout(性能次之)>ConstraintLayout(功能最強大,複雜佈局用它)。

image

3.LinearLayout關閉baselineAligned

LinearLayout有一個屬性叫android:baselineAligned,默認值爲true,即默認開啓baseline基線對齊,是LinearLayout內有多個橫向排布的TextView須要優化文字對齊效果的時候一個開關。

以下圖所示咱們能夠大概瞭解一下baseline的含義。

image

若是咱們的LinearLayout內的多個TextView不須要基線對齊,或者說並不包含TextView而是一些其餘的View,那麼咱們能夠關閉此功能來減小測量的耗時,避免開啓後因對齊基線而多測量一次。

下圖是LinearLayout measure時的部分代碼,很明顯能夠看到由於對齊基線而多了一次測量。

image

4.ViewStub延遲初始化

ViewStub是一個不可見,零尺寸的View,可在使用時能夠按需初始化佈局。當ViewStub被設置爲visible或者調用inflate方法時,指定的layout會被加載填充, 被加載的layout將代替ViewStub添加到ViewStub的父佈局中。

image

咱們能夠經過閱讀ViewStub的源碼看到他在未inflate時幾乎無開銷,因此對於部分一開始不使用的layout,能夠採用此種方式加載,提升初次inflate和渲染主界面的速度。

image

5.include佈局時善用merge標籤,減小嵌套

當咱們有一些layout能夠複用時,咱們每每能夠把它單獨寫到一個layout文件裏面,而後須要的時候再include進主layout裏面,layout文件每每須要一個根佈局來包裝全部的子View(ViewGroup),

而這個根佈局被include以後每每會由於上層是一個一樣的ViewGroup變得多餘,這裏咱們能夠用Merge標籤做爲根佈局來包含全部子view而去除原有的ViewGroup,減小一層嵌套。

介紹完了mesure/layout階段的優化技巧,咱們再來看看在draw階段咱們能夠作哪些優化。 在進行優化以前首先給你們介紹一個很是重要的查看界面渲染流暢度的工具,GPU呈現模式分析,經過他咱們能夠很直觀的觀察界面渲染的幀率和流暢度

image

關於每條豎線的分段顏色的含義,以下是一個詳細介紹,他們表明了每一幀渲染過程的各個階段,分段長度則表明這一階段的耗時

image

5.減小Overdraw過分繪製

過分繪製指的是在同一塊屏幕空間內發生的重疊繪製次數,這是不可避免會存在的問題,咱們能作的就是儘可能減小這種現象。

很幸運開發者選項裏面的「調試GPU過分繪製」提供了相應的工具讓咱們能夠很直觀的觀察到這種現象

image

發生過分繪製咱們能優化的主要是避免繪製看不見的元素,這裏面最容易犯錯的地方就是繪製了看不見的View背景。

下圖顯示了咱們經過去掉多個ViewGroup的背景色,而後僅在窗口背景上設置須要的背景顏色,減小了Overdraw又不影響功能實現。

image

下圖是去掉多餘背景以後的效果,結果很是明顯,大幅減小了overdraw現象,這樣在draw的階段能夠有更好的性能表現。

image

6.Canvas.clipRect/quickReject

Canvas.clipRect()能夠定義繪製的邊界,邊界之外的部分不會進行繪製。

Canvas.quickReject()能夠用來測試指定區域是否在裁剪範圍以外,若是要繪製的元素位於裁剪範圍以外,就能夠直接跳過繪製步驟。

下圖演示了一個包含多個層疊View的自定義ViewGroup,當發生重疊時不可避免要產生很嚴重的過分繪製狀況,但實際上底層被遮蓋的View的某一部分是不可見的, 咱們能夠經過在ViewGroup drawChilds的時候,經過計算顯示和不顯示的區域來作合理的clip,避免繪製看不見的部分。

image

實際上若是你看過DrawerLayout的源碼,他裏面的實現也有相似這樣的優化,在Drawer打開的時候底下View被遮蓋的部分區域被clip不繪製。

image

經過下圖咱們能夠明顯看出來,Drawer並無比右邊沒遮蓋的區域繪製更多層,由於Drawer底下被遮蓋的區域被clip作了優化。

image

7.佔位背景圖優化

在顯示圖片的時候,咱們有些時候可能須要給圖片加邊框和陰影或者設置默認佔位圖片作修飾,若是當圖片加載完了以後咱們仍然須要顯示佔位圖來修飾原圖, 那麼可能在被目標圖遮蓋的那部分區域就多繪製了一層佔位圖的背景,致使即便不顯示也要被繪製。

image

因此這裏咱們提出一種優化方法,根據不一樣的狀態顯示不一樣的佔位修飾圖。當未加載完成是顯示帶背景的佔位圖,當加載完成後咱們顯示透明背景的佔位圖 這樣被目標圖遮蓋的區域就不會多繪製一層無用的背景了。

image

優化完了以後結果以下,很明顯,使用透明背景的邊框圖少了一次繪製。

image

在這裏總結一下:避免Overdraw的核心原則始終只有一個,就是避免繪製看不見的元素

8.Alpha blending透明度合成優化

image

若是直接對每一個View分別作alpha合層會致使丟掉他們以前的層疊效果,致使看見被覆蓋的底層的View,顯然這不是咱們想要的結果

image

咱們想要的結果是以下圖所示,Alpha合成以後仍然能保留堆疊信息。實際上系統已經爲Alpha合層作出了合理處理,在幀緩衝(FrameBuffer)繪製以前,

會建立離屏緩衝區(off-screen buffer/Canvas Layer)進行繪製,而後以一個總體進行透明度合層,結果再被複制到幀緩衝。也就是說在繪製帶透明度的View時, 咱們須要雙倍的開銷。

image

爲了不由於透明度合層致使繪製時雙倍的開銷,有些須要alpha合層的地方咱們能夠作一些優化,好比TextView須要設置alpha值,若是沒有背景,咱們能夠直接修改文字顏色值,

在原顏色值的基礎上加入透明度混合計算以後直接修改文字顏色值,這樣避免alpha合層時建立離屏緩衝從而減小開銷提升性能。

image

對於自定義View,咱們能夠經過重寫onSetAlpha方法返回true來向上層框架代表本身有能力處理alpha值的變化,經過將alpha值設置到paint上, 在draw的時候直接處理了alpha相關信息。

image

8.重寫hasOverlappingRendering方法

hasOverlappingRendering顧名思義就是是否存在重疊渲染的意思。hasOverlappingRendering方法是打算讓繼承自View的子類覆蓋重寫的方法,

當View的alpha<1時他是一個優化點,默認返回true會在渲染時啓用離屏緩衝致使雙倍開銷,而若是View沒有重疊繪製的話,那麼能夠返回false,

這樣View在alpha<1的狀況下繪製時就不會啓用離屏緩衝,必定程度上優化了渲染性能。

image

下面是ImageView的源碼,能夠看到當判斷沒有background的時候,重寫hasOverlappingRendering返回false,優化了alpha合成性能。

image

9.Use Hardware Layer

當給一個View.setLayerType(View.LAYER_TYPE_HARDWARE,null)後,就定義了此View採用Hardware Layer(背後採用一個硬件相關紋理)來加速渲染。

Hardware Layer對於ViewGroup以及其所包含的子Views一塊兒作Alpha Blending,ColorFilter很是有用。Hardware Layer能夠緩存整個複雜的View樹到一個紋理上, 減小繪製命令,當須要對整個View樹作動畫的時候,只須要渲染一次(層)。

image

下面這段代碼顯示了咱們執行一個旋轉動畫,開啓或不開啓HW Layer的對比效果

image

實際運行效果以下,能夠看到ViewGroup初始狀態是包含4個堆疊的View,本身還有背景色,過渡繪製很嚴重有不少層,

未開啓HWLayer作動畫的時候,依然須要渲染不少層,致使幀率不高常常頂到16ms的基線在跑,而開啓HW Layer以後,

優化效果立竿見影,ViewGroup及其子View都被合層爲一個HWLayer上渲染,只須要繪製一層,運行動畫的幀率明顯流暢了很多。

image

##最後總結一下Draw階段的優化原則:

  • 儘可能減小沒必要要的繪製。能夠經過減小overdraw,Avoid Alpha Blending,clip Canvas,Use Hardware Layer等手段來完成。
  • 避免在調用頻繁的路徑(eg:onDraw,onBindViewHolder)建立對象,格式化數據和作大量的計算,善用緩存和局部刷新機制。

UI性能優化是一個永無止境的工做,若是咱們能把全部可優化的點都優化好,一點點積累優點,量變就會產生質變,最後必定達會到滿意的效果。

相關文章
相關標籤/搜索