開發者必讀:13種方式幫助你提升App效能
譯者注:Matt Lacey 從事軟體開發行業已有12年之久,他專注於移動和Web開發。近期,他積累了大量基於Windows Phone 7的開發經驗。下面的這篇文章是他在開發中積累的一些提升App的效能的經驗。這會是一篇使開發人員深受其益的好文章,下面我們就進入正題。
很多方法可以幫助你提升App的效能,本篇文章不提供完整版本,筆者只提供幾種常用的方法。
筆者希望你從本篇文章中得到的最重要的資訊是:優異的效能並不是憑空而得來的,這需要你儘自己所能讓使用者取得最好的體驗。
值得注意的是,一些MVVM 純化論者可能會對以下的一些內容提出異議,但是這些規則或方法是由現實世界中的一些體驗所得來的,必然有其合理性。
1.避免使用UI執行緒–除非是必要情況,否則儘量不要使用UI執行緒,而是保持UI的響應。
2.儘量不要使用值轉換器–值轉換器帶來的效能影響遠遠大於使用直接屬性值帶來的影響。
3.在不必要的情況下,不要使用依賴屬性–如果常規屬效能夠滿足需要,那就儘量使用常規屬性,提升系統的執行速度。
4.減少外部依賴–儘量避免使用第三方元件,有利於提升系統的執行速度。
5.拆分程式集–在不必要的情況下,不要在你的App中載入所有的頁面。
6.把圖片(資料)放到快取中–下載外部資源消耗時間,會影響系統的效能。
7.使用“Boot Loader”–如果你的App很大並且啟動時需要進行大量處理,那麼請儘量將這些啟動步驟分開,這樣將大大提高啟動時的效能。
8.對資料進行預載–在情況允許的條件下,在使用者獲取資料之前載入所需的資料,這樣可以縮減使用者等待資料載入的時間。
9.載入顯示同步進行–如果資料量非常大,建議只加載你所需部分資料,不要在把時間浪費在一次性載入全部資料上,讓你的使用者空等。
10.對資料/模板進行重用(儘量避免載入XAML)–重用模板意味著增加App的效率,避免過載同樣的內容。
11.儘量不要使用XAML–若非必要情況,不要使用XAML。我經常看到一些執行速度很慢的App,它們有一個共同特點:在使用很少的控制元件就能夠解決問題的情況下,它們偏偏使用一些非常複雜而沒有使用必要的巢狀控制元件。
12.避免使用巢狀Margin
13.不要使用XAML—這的確很極端,但是使用程式碼構建UI會比使用XAML為系統提供更好的效能(可以節省下解析的時間)。
過去,以上這些規則使我深受其益。希望以後,這些規則也能夠給你帶來幫助。
Google近期在Udacity上釋出了Android效能優化的線上課程,目前有三個篇章,分別從渲染,運算與記憶體,電量三個方面介紹瞭如何去優化效能,這些課程是Google之前在Youtube上釋出的<a href="http://www.kuqin.com/shuoit/20150120/344489.html" "="" target="_blank" style="color: rgb(128, 0, 128); text-decoration: none; outline-style: none;">Android效能優化典範專題課程的細化與補充。
下面是渲染、運算、記憶體、電量四個篇章的學習筆記,部分內容和前面的效能優化典範有重合,歡迎大家一起學習交流!
渲染篇
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來優化佈局。