標籤:
下面是渲染篇章的學習筆記,部分內容和前面的效能最佳化典範有重合,歡迎大家一起學習交流!
1)Why Rendering Performance Matters
現在有不少App為了達到很華麗的視覺效果,會需要在介面上層疊很多的視圖組件,但是這會很容易引起效能問題。如何平衡Design與Performance就很需要智慧了。
2)Defining ‘Jank’
大多數手機的螢幕重新整理頻率是60hz,如果在1000/60=16.67ms內沒有辦法把這一幀的任務執行完畢,就會發生丟幀的現象。丟幀越多,使用者感受到的卡頓情況就越嚴重。
3)Rendering Pipeline: Common Problems
渲染操作通常依賴於兩個核心組件:CPU與GPU。CPU負責包括Measure,Layout,Record,Execute的計算操作,GPU 負責Rasterization(柵格化)操作。CPU通常存在的問題的原因是存在非必需的視圖組件,它不僅僅會帶來重複的計算操作,而且還會佔用額外的 GPU資源。
4)Android UI and the GPU
瞭解Android是如何利用GPU進行畫面渲染有助於我們更好的理解效能問題。一個很直接的問題是:activity的畫面是如何繪製到螢幕上的?那些複雜的XML布局檔案又是如何能夠被識別並繪製出來的?
Resterization柵格化是繪製那些Button,Shape,Path,String,Bitmap等組件最基礎的操作。它把那些組件拆分到不同的像素上進行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作。
CPU負責把UI組件計算成Polygons,Texture紋理,然後交給GPU進行柵格化渲染。
然而每次從CPU轉移到GPU是一件很麻煩的事情,所幸的是OpenGL ES可以把那些需要渲染的紋理Hold在GPU Memory裡面,在下次需要渲染的時候直接進行操作。所以如果你更新了GPU所hold住的紋理內容,那麼之前儲存的狀態就丟失了。
在Android裡面那些由主題所提供的資源,例如Bitmaps,Drawables都是一起打包到統一的Texture紋理當中,然後再傳遞到 GPU裡面,這意味著每次你需要使用這些資源的時候,都是直接從紋理裡面進行擷取渲染的。當然隨著UI組件的越來越豐富,有了更多演變的形態。例如顯示圖 片的時候,需要先經過CPU的計算載入到記憶體中,然後傳遞給GPU進行渲染。文字的顯示比較複雜,需要先經過CPU換算成紋理,然後交給GPU進行渲染, 返回到CPU繪製單個字元的時候,再重新引用經過GPU渲染的內容。動畫則存在一個更加複雜的操作流程。
為了能夠使得App流暢,我們需要在每幀16ms以內處理完所有的CPU與GPU的計算,繪製,渲染等等操作。
5)GPU Problem: Overdraw
Overdraw(過度繪製)描述的是螢幕上的某個像素在同一幀的時間內被繪製了多次。在多層次重疊的UI結構裡面,如果不可見的UI也在做繪製的操作,會導致某些像素地區被繪製了多次。這樣就會浪費大量的CPU以及GPU資源。
當設計上追求更華麗的視覺效果的時候,我們就容易陷入採用複雜的多層次重疊視圖來實現這種視覺效果的怪圈。這很容易導致大量的效能問題,為了獲得最佳的效能,我們必須盡量減少Overdraw的情況發生。
幸運的是,我們可以通過手機設定裡面的開發人員選項,開啟Show GPU Overdraw的選項,觀察UI上的Overdraw情況。
藍色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標就是盡量減少紅色Overdraw,看到更多的藍色地區。
6)Visualize and Fix Overdraw - Quiz & Solution
這裡舉了一個例子,通過XML檔案可以看到有好幾處非必需的background。通過把XML中非必需的background移除之後,可以顯著 減少布局的過度繪製。其中一個比較有意思的地方是:針對ListView中的Avatar ImageView的設定,在getView的代碼裡面,判斷是否擷取到對應的Bitmap,在擷取到Avatar的映像之後,把ImageView的 Background設定為Transparent,只有當映像沒有擷取到的時候才設定對應的Background佔位圖片,這樣可以避免因為給 Avatar設定背景圖而導致的過度渲染。
總結一下,最佳化步驟如下:
移除Window預設的Background
移除XML布局檔案中非必需的Background
按需顯示佔位背景圖片
7)ClipRect & QuickReject
前面有提到過,對不可見的UI組件進行繪製更新會導致Overdraw。例如Nav Drawer從前置可見的Activity滑出之後,如果還繼續繪製那些在Nav Drawer裡面不可見的UI組件,這就導致了Overdraw。為瞭解決這個問題,Android系統會通過避免繪製那些完全不可見的組件來盡量減少 Overdraw。那些Nav Drawer裡面不可見的View就不會被執行浪費資源。
但是不幸的是,對於那些過於複雜的自訂的View(通常重寫了onDraw方法),Android系統無法檢測在onDraw裡面具體會執行什麼操作,系統無法監控並自動最佳化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來 協助系統識別那些可見的地區。這個方法可以指定一塊矩形地區,只有在這個地區內才會被繪製,其他的地區會被忽視。這個API可以很好的協助那些有多組重疊 組件的自訂View來控制顯示的地區。同時clipRect方法還可以協助節約CPU與GPU資源,在clipRect地區之外的繪製指令都不會被執 行,那些部分內容在矩形地區內的組件,仍然會得到繪製。
除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形地區內的繪製操作。
8)Apply clipRect and quickReject - Quiz & Solution
上面的樣本圖中顯示了一個自訂的View,主要效果是呈現多張重疊的卡片。這個View的onDraw方法如所示:
開啟開發人員選項中的顯示過度渲染,可以看到我們這個自訂的View部分地區存在著過度繪製。那麼是什麼原因導致過度繪製的呢?
9)Fixing Overdraw with Canvas API
下面的代碼顯示了如何通過clipRect來解決自訂View的過度繪製,提高自訂View的繪製效能:
下面是最佳化過後的效果:
10)Layouts, Invalidations and Perf
Android需要把XML布局檔案轉換成GPU能夠識別並繪製的對象。這個操作是在DisplayList的協助下完成的。DisplayList持有所有將要交給GPU繪製到螢幕上的資料資訊。
在某個View第一次需要被渲染時,Display List會因此被建立,當這個View要顯示到螢幕上時,我們會執行GPU的繪製指令來進行渲染。
如果View的Property屬性發生了改變(例如移動位置),我們就僅僅需要Execute Display List就夠了。
然而如果你修改了View中的某些可見組件的內容,那麼之前的DisplayList就無法繼續使用了,我們需要重新建立一個DisplayList並重新執行渲染指令更新到螢幕上。
請注意:任何時候View中的繪製內容發生變化時,都會需要重新建立DisplayList,渲染DisplayList,更新到螢幕上等一系列操 作。這個流程的表現效能取決於你的View的複雜程度,View的狀態變化以及渲染管道的執行效能。舉個例子,假設某個Button的大小需要增大到目前 的兩倍,在增大Button大小之前,需要通過父View重新計算並擺放其他子View的位置。修改View的大小會觸發整個HierarcyView的 重新計算大小的操作。如果是修改View的位置則會觸發HierarchView重新計算其他View的位置。如果布局很複雜,這就會很容易導致嚴重的性 能問題。
11)Hierarchy Viewer: Walkthrough
Hierarchy Viewer可以很直接的呈現布局的層次關係,視圖組件的各種屬性。 我們可以通過紅,黃,綠三種不同的顏色來區分布局的Measure,Layout,Executive的相對效能表現如何。
12)Nested Hierarchies and Performance
提升布局效能的關鍵點是盡量保持布局層級的扁平化,避免出現重複的嵌套布局。例如下面的例子,有2行顯示相同內容的視圖,分別用兩種不同的寫法來實現,他們有著不同的層級。
顯示了使用2種不同的寫法,在Hierarchy Viewer上呈現出來的效能測試差異:
13)Optimizing Your Layout
舉例示範了如何最佳化ListItem的布局,通過RelativeLayout替代舊方案中的嵌套LinearLayout來最佳化布局。
來源: <http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0416/2735.html>
來自為知筆記(Wiz)
Android效能最佳化之渲染篇