1. 程式人生 > 實用技巧 >Android效能優化案例研究(下)

Android效能優化案例研究(下)

>>> hot3.png

譯者前言:在Android效能優化案例研究(上)中,作者Romain Guy將Falcon Pro這款應用作為例子,通過Android現有的工具追蹤和分析了其隱藏的效能問題(重繪)。下篇作者將會帶來如何解決此類問題的方法和思路。

去掉冗餘的圖層

為 了去掉重繪我們必須首先理解它從哪裡產生的。這就輪到Hierarchy Viewer和Tracer for OpenGL大顯身手的時候了。Hierarchy Viewer是ADT工具(或者monitor)的一部分,可以被用作對檢視層級進行快速解讀。在處理佈局問題時特別有用,對於效能問題也很適用。

重要:

Hierarchy Viewer預設只能在非加密裝置使用,例如工程機,工程平板或者模擬器。為了能夠在任何手機上使用Hierarchy Viewer,你得在你的應用中新增ViewServer,這是一個開源庫。

在 ADT(或者monitor)中開啟Hierarchy Viewer的全景圖,選擇window標籤。這個介面就會粗體高亮的顯示當前裝置執行的視窗,通常就是你想要研究的那個應用。選中它再點選工具欄的 Load按鈕(它更像藍色方塊組成的樹)。載入這棵樹需要一段時間,所以請耐心等待。當這棵樹載入完成你就可以看到如下圖所示的畫面。

現 在檢視的層級已經載入到工具裡,我們也可以將其轉換為PhotoShop文件。只要點選工具欄的第二個按鈕,工具提示說:“Capture the window layers [..]”。Adobe Photoshop本身不是必須的,因為生成的文件可以被其他工具相容,例如Pixelmator, The GIMP等等。你們可以下載我所生成的

PSD檔案

PhotoShop文件可以顯示每個檢視的每個圖層。一個圖層可以標記為可見或者不可見,這是取決於View.getVisibility()返回的結果。每一個圖層命名在一個檢視的後面,如果檢視的android:id存在則使用android:id,或者使用它的類名。我曾經開始新增對於組(group)的支援用於檢視樹的重建…我其實應該早點把這個功能做完。

通過檢查每個圖層的列表,我們可以快速的辨別至少一種重繪的源頭:多個全屏的背景。第一個就是第一個圖層,叫做DecorView。這個view是由Android框架生成的,包含了面板主題指定的背景。這個預設的背景在應用中是不可見的,因此它可以被安全的去掉。

從DecorView向上滾動,你可以看到一個LinearLayout,它包含另一個全屏的背景。它和DecorView的背景是一回事,所以它也是不需要的。唯一可見且肯定存在的背景屬於一個名叫id/tweet_list_container的view

去掉桌面背景:

定 義在你的主題面板裡的背景通常是當系統啟動你的應用時用來建立預覽視窗的。千萬不要設定它為空(null),除非你的應用是透明的。相反,設定它為你想要 的顏色或者圖片,或者在onCreate()裡呼叫getWindow().setBackgroundDrawable(null)來去掉它

進一步去掉重繪

用 Photoshop的文件圖來理解應用是怎麼建立的是很有用的。但是用來去掉小範圍的重繪有點難度。現在我們就必須轉向Tracer for OpenGL。同樣在ADT(或者monitor)中開啟它的檢視,點選工具欄的箭頭圖示,輸入你應用的包名和你主要的Activity的名字,然後選擇 一個目的檔案,點選Trace。

一句建議:

OpenGL traces抓取的資料量很大。為了讓資料量較小,同時也利於更快速抓取。請去掉“all the Data Collection Options”選項。

Activity名字:

在應用啟動時可以通過logcat獲得包名和Activity名字。這就是為什麼我可以知道在Tracer for OpenGL輸入這些名字。

當啟動並執行這個應用時,開啟前兩個選項:

  • Collect Framebuffer contents on eglSwapBuffers()
  • Collect Framebuffer contents on glDraw*()

第一個選項可以方便的快速找到你感興趣的幀,第二個選項可以讓我們看到每一幀是如何通過一步步繪圖命令建立起來的。第二個選項就是解決重繪的關鍵。

隨著這兩個選項的開啟,我開始滾動螢幕。抓取每一幀需要很長時間(也許要30秒),所以我推薦你可以先簡單的下載我抓取的trace檔案。你可以通過Tracer for OpenGL工具欄的第一個按鈕開啟這個檔案。

trace 檔案一旦載入完成,你就可以看到每一幀發生給GPU的每一個GL命令。如果你下載了我的檔案,你跳到第21幀。當一幀被選中後,你就可以看到Frame Summary選項卡中呈現的模樣。此外,你還可以點選高亮為藍色的drawing命令,這樣你就可以在Details選項卡中獲得當前幀的狀態細節。

相繼的點選前三個繪圖命令,你就可以看到在PhotoShop裡面已經得到鑑定的問題:全屏的背景被畫了三次。

通過深入研究這個trace檔案,我們可以找到更多優化的地方。當去畫一個訊息內容條目時,ImageView被用來畫頭像。這個ImageView先畫了一個背景然後再畫頭像:

如果你看得再仔細點你就會注意到背景只是用來作為圖片的邊框。這就意味著在位於頭像的黑色方塊產生了重繪。那塊9格圖(9-patch)完全被頭像覆蓋了。

解決這個問題的有一個很簡單的方法就是讓這塊9格圖設為透明。Android的2D渲染引擎已經在9格圖上做了優化。這個簡單的方法就可以去掉重繪。

有趣的是,同樣的問題也發生在內嵌的媒體內容上。頭像很小所以它們的重繪不是個大問題。但內嵌的媒體內容卻可以佔據螢幕的大片區域,這個問題就嚴重了。可以用同樣的方法去解決它。

未來的優化:

我希望Android的2D渲染流水線能夠自動的檢測和修正重繪。我們已經有了一些想法但還不能做出承諾。

扁平化View的層級

現在重繪已經基本考慮過了。讓我們重新回到Hierarchy Viewer吧。通過研究這棵UI樹,我們可以儘量去鑑別哪些View不是必須的。去掉View,特別是去掉ViewGroups,不僅可以提供幀率,也可以節省記憶體,加快啟動時間等等。

看一眼Falcon Pro的View的層級樹就可以發現一些ViewGroups是在同一個子節點上。ViewGroups通常不是必須的,也很容易去掉。下面這個截圖顯示至少有兩個節點是可以去掉的。

也 有一些冗餘的View可以去掉。比如每一個訊息條目都包一個叫做id/listElementBottom的RelativeLayout。這個佈局包含 了作者的名字,推特訊息,已經發布了多長時間和一個圖示。名字和訊息用了兩個不同的TextView,其實只需要一個TextView用不同的風格來顯示 就行了。時間和圖示用了一個TextView和一個ImageView,其實兩者可以用一個TextView,然後用視覺化繫結到TextView上。

左邊滑動的介面用了若干不同的LinearLayout+TextView+ImageView來顯示標籤和圖示。他們都可以通過一個TextView來代替。

如何扁平化你的介面:

我在2009年的Google I/O大會上做了一篇題為優化你的UI的演講,裡面介紹了這其中的技術細節。

關於輸入事件?

還記得我們在看systrace時找到一段處理很慢的觸控事件?現在可以看看這個問題。理解這個問題最佳的工具就是traceview。

traceview 是Dalvik效能解析工具,它可以測量一個應用在每個方法呼叫上花費的事件。在ADT或者monitor裡開啟DDMS,在裝置選項卡里選擇你應用所在 的程序,然後點選“start method profiling”按鈕(三個箭頭和一個紅色的圈),你就可以使用traceview。

當啟動了traceview後,我滾動應用的介面,然後點選那個按鈕結束跟蹤。你也可以下載我的跟蹤檔案。結果如下圖所示。

點選條目21:ViewRootImpl.draw(),高亮它所花的時間。表的最後一列表明這個方法的和在它的子類裡平均的呼叫時間。如果你仔細看看高亮的時間軸,你可以注意到幀與幀之間的差距。

用 一個簡單的方法來檢視差距裡面到底發生了什麼,可以放大他們開始的階段,然後點選你找到的紅色的塊。你可以跟著呼叫鏈來找到你能認出的方法。在我的例子 裡,我跟蹤了一個大概佔用了0.5毫秒的Pattern.compileImpl方法,一直到跟DBListAdapter.bindView。

很 顯然這個應用將同一個正則表示式編譯了好幾次,每一次滾動螢幕都伴隨著一個條目的繫結。TraceView顯示bindView平均佔用了38毫秒,而其 中56%的時間花在瞭解析HTML文字上。似乎可以將這個步驟放在後臺執行而不去阻塞UI執行緒,而正則表示式不應該每次都需要重新編譯。

現在輪到你了!

我保留了最後一個跟蹤檔案作為測驗。這個應用有兩個滑動的選單,可以左右滑動時間軸。Show GPU overdraws高亮了滑動時大量的繪圖。我用Tracer for OpenGL抓取了滑動時的若干幀。下載我的trace檔案,然後看看你是否能找到重繪的原因(去看第34號幀)。

提示:

應 用應該呼叫View.setLayerType()來使用硬體圖層(hardware layer)來簡化繪圖。大量的背景可以使用9格圖來做優化。裁剪也很有效。最後,也許可以將一個顏色過濾器(colofilter)設定在畫筆 (paint)上,然後傳給setLayerType(),這樣可以幫助去掉最後一個繪圖命令。

我向你們展示了大量可以優化你們應用的工具。我其實還可以花費大量的時間來描述用這些工具處理這些問題的技術方法,但這樣文章就會變成長篇大論。你們可以去參考Android開發者的的官方文件和所有Google I/O上Android的演講(ppt和視訊都是免費可取得)。

轉載於:https://my.oschina.net/jerikc/blog/129714