標籤:
前面部落格分析了導致app卡頓慢的直接原因,這裡就從原因出發,分析一些最佳化方案(這裡主要是從直接影響渲染機制的布局相關進行分析)
1) Invalidations, Layouts, and Performance(動畫,布局的最佳化)
順滑精妙的動畫是app設計裡面最重要的元素之一,這些動畫能夠顯著提升使用者體驗。下面會講解Android系統是如何處理UI組件的更新操作的。
通常來說,Android需要把XML布局檔案轉換成GPU能夠識別並繪製的對象。這個操作是在DisplayList的協助下完成的。DisplayList持有所有將要交給GPU繪製到螢幕上的資料資訊。
在某個View第一次需要被渲染時,DisplayList會因此而被建立,當這個View要顯示到螢幕上時,我們會執行GPU的繪製指令來進行渲染。如果你在後續有執行類似移動這個View的位置等操作而需要再次渲染這個View時,我們就僅僅需要額外操作一次渲染指令就夠了。然而如果你修改了View中的某些可見組件,那麼之前的DisplayList就無法繼續使用了,我們需要回頭重新建立一個DisplayList並且重新執行渲染指令並更新到螢幕上。
需要注意的是:任何時候View中的繪製內容發生變化時,都會重新執行建立DisplayList,渲染DisplayList,更新到螢幕上等一系列操作。這個流程的表現效能取決於你的View的複雜程度,View的狀態變化以及渲染管道的執行效能。舉個例子,假設某個Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過父View重新計算並擺放其他子View的位置。修改View的大小會觸發整個HierarcyView的重新計算大小的操作(特別是繪製動畫時影響是很嚴重的)。如果是修改View的位置則會觸發HierarchView重新計算其他View的位置。如果布局很複雜,這就會很容易導致嚴重的效能問題。我們需要盡量減少Overdraw。
我們可以通過前面介紹的Monitor GPU Rendering來查看渲染的表現效能如何,另外也可以通過開發人員選項裡面的Show GPU view updates來查看視圖更新的操作,最後我們還可以通過HierarchyViewer這個工具來查看布局,使得布局盡量扁平化(層數盡量減少),移除非必需的UI組件,這些操作能夠減少Measure,Layout的計算時間,製作動畫時,運動元素與其父控制項的關係要好好考慮,盡量在運動元素運動過程中,不會導致其父控制項變化,否則就會導致父控制項重繪,整個重繪可能影響到整個布局檔案,這就是你做出的動畫卡頓慢的可能原因之一。
2) Overdraw, Cliprect, QuickReject(過度繪製)
引起效能問題的一個很重要的方面是因為過多複雜的繪製操作。我們可以通過工具來檢測並修複標準UI組件的Overdraw問題,但是針對高度自訂的UI組件則顯得有些力不從心。
有一個竅門是我們可以通過執行幾個APIs方法來顯著提升繪製操作的效能。前面有提到過,非可見的UI組件進行繪製更新會導致Overdraw。例如Nav Drawer從前置可見的Activity滑出之後,如果還繼續繪製那些在Nav Drawer裡面不可見的UI組件,這就導致了Overdraw。為瞭解決這個問題,Android系統會通過避免繪製那些完全不可見的組件來盡量減少Overdraw。那些Nav Drawer裡面不可見的View就不會被執行浪費資源(對於不需要使用者看見的元素,設定屬性為gone,這可以在一定程度上最佳化效能,這時與設定成invisable相比較的)。
但是不幸的是,對於那些過於複雜的自訂的View(重寫了onDraw方法),Android系統無法檢測具體在onDraw裡面會執行什麼操作,系統無法監控並自動最佳化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來協助系統識別那些可見的地區。這個方法可以指定一塊矩形地區,只有在這個地區內才會被繪製,其他的地區會被忽視。這個API可以很好的協助那些有多組重疊組件的自訂View來控制顯示的地區。同時clipRect方法還可以協助節約CPU與GPU資源,在clipRect地區之外的繪製指令都不會被執行,那些部分內容在矩形地區內的組件,仍然會得到繪製。
除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形地區內的繪製操作。做了那些最佳化之後,我們可以通過上面介紹的Show GPU Overdraw來查看效果。
3) Custom Views and Performance(自訂視圖的最佳化)
Android系統有提供超過70多種標準的View,例如TextView,ImageView,Button等等。在某些時候,這些標準的View無法滿足我們的需要,那麼就需要我們自己來實現一個View,這節會介紹如何最佳化自訂View的效能。
通常來說,針對自訂View,我們可能犯下面三個錯誤:
·Useless calls to onDraw():我們知道調用View.invalidate()會觸發View的重繪,有兩個原則需要遵守,第1個是僅僅在View的內容發生改變的時候才去觸發invalidate方法,第2個是盡量使用ClipRect等方法來提高繪製的效能。
·Useless pixels:減少繪製時不必要的繪製元素,對於那些不可見的元素,我們需要盡量避免重繪。
·Wasted CPU cycles:對於不在螢幕上的元素,可以使用Canvas.quickReject把他們給剔除,避免浪費CPU資源。另外盡量使用GPU來進行UI的渲染,這樣能夠極大的提高程式的整體表現效能。
最後請時刻牢記,盡量提高View的繪製效能,這樣才能保證介面的重新整理幀率盡量的高。
4) Hidden Cost of Transparency(透明效果的隱藏效能消耗)
這小節會介紹如何減少透明地區對效能的影響。通常來說,對於不透明的View,顯示它只需要渲染一次即可,可是如果這個View設定了alpha值,會至少需要渲染兩次。原因是包含alpha的view需要事Crowdsourced Security Testing道混合View的下一層元素是什麼,然後再結合上層的View進行Blend混色處理。
在某些情況下,一個包含alpha的View有可能會觸發改View在HierarchyView上的父View都被額外重繪一次。下面我們看一個例子,示範的ListView中的圖片與二級標題都有設定透明度。
大多數情況下,螢幕上的元素都是由後向前進行渲染的。在上面的圖示中,會先渲染背景圖(藍,綠,紅),然後渲染人物頭像圖。如果後渲染的元素有設定alpha值,那麼這個元素就會和螢幕上已經渲染好的元素做blend處理。很多時候,我們會給整個View設定alpha的來達到fading的動畫效果,如果我們圖示中的ListView做alpha逐漸減小的處理,我們可以看到ListView上的TextView等等組件會逐漸融合到背景色上。但是在這個過程中,我們無法觀察到它其實已經觸發了額外的繪製任務,我們的目標是讓整個View逐漸透明,可是期間ListView在不停的做Blending的操作(由於listview的適配器中getview會頻繁調用,除了listview會這樣,還包括grideview也會),這樣會導致不少效能問題。
如何渲染才能夠得到我們想要的效果呢?我們可以先按照通常的方式把View上的元素按照從後到前的方式繪製出來,但是不直接顯示到螢幕上,而是使用GPU預先處理之後,再又GPU渲染到螢幕上,GPU可以對介面上的未經處理資料直接做旋轉,設定透明度等等操作。使用GPU進行渲染,雖然第一次操作相比起直接繪製到螢幕上更加耗時,可是一旦原始紋理資料產生之後,接下去的操作就比較省時省力(其實是將cpu的事落到gpu上去做,並且由於gpu的紋理機制讓整體效能提升)。
如何才能夠讓GPU來渲染某個View呢?我們可以通過setLayerType的方法來指定View應該如何進行渲染,從SDK 16開始,我們還可以使用ViewPropertyAnimator.alpha().withLayer()來指定。如所示:
另外一個例子是包含陰影地區的View,這種類型的View並不會出現我們前面提到的問題,因為他們並不存在層疊的關係。
為了能夠讓渲染器知道這種情況,避免為這種View佔用額外的GPU記憶體空間,我們可以做下面的設定。
通過上面的設定以後,效能可以得到顯著的提升,如所示:
總結一下:直接和渲染相關的就是布局檔案,為了達到程式渲染最優,有幾個原則1.布局層數盡量少(扁平化)。2,盡量避免過度渲染。3.盡量簡化布局,4,經常變化的view的布局深度盡量低(每一次變化都會涉及上層及上上層控制項的變化)。這是直接和渲染機制相關的布局,渲染還提出了要求,每一個功能相對單一的模組的執行時間盡量接近16ms(這時幀率為60fps),最多為32ms,若再多就會影響使用者體驗了,其實16ms是一幀不落的繪製了,從16ms到32ms這段時間丟幀了,但是沒影響使用者體驗,但是大於32ms就很影響了,卡頓慢就出現了。至於這麼縮短每個模組的時間,這個就得具體模組的來定了。這裡的16ms和32ms是主線程的時間,所以一般耗時較大(例如網路請求,檔案操作,以及資料庫操作這些典型功能就算再最佳化,也很難將時間壓縮到16ms),這種情況就可以使用額外的線程來處理這些工作列了,有人可能就有疑問了,既然線程可以解決問題,那我就不用最佳化了,直接使用線程就好了,這種說法其實是有問題的,因為線程管理還是得佔用cpu時間的,如果線程數很多,cpu管理的時間就會變多,值得注意的是渲染機制的第一步解析xml布局檔案(測量繪製Display List)的工作還得cpu做呢,所以可能會出現一種情況就是把耗時任務都放在新線程裡了,但是依舊還是卡頓慢。所以一般是最佳化後距離16ms這個標準還是相差很遠的情況才使用新線程。
Android app效能最佳化解決卡慢頓之布局最佳化