Android 性能個案研究
Falcon Pro
我最近在我的Nexus 4上安裝了Falcon Pro,一個新的Twitter客戶端。我真的很喜歡使用這個應(yīng)用程序,但我在使用它時注意到了某些地方存在一些問題,看起來滾動主時間軸并沒有得到非常穩(wěn)定的幀率。我深鉆了一下我每天工作所用工具和技術(shù)中的一些,我能很快地找到一些Falcon Pro 并不如它本可以表現(xiàn)得那么好的原因。
我在這篇文章里的目的是告訴你如何追蹤和修復(fù)應(yīng)用程序中的性能問題,即使你沒有它的源代碼。所有你需要的只是一份最新的Android 4.2 SDK –新的ADT bundle使設(shè)置變得簡易。我極力推薦你下載這個應(yīng)用程序來親自應(yīng)用這里描述的技術(shù)。對你來說不幸的是Falcon Pro是款付費應(yīng)用,因此我將給您提供你可以下載的各種文件的鏈接來跟隨我的分析。
關(guān)于性能
Android 4.1把重點放在Butter項目的性能上,它帶來了新的性能分析工具,如systrace。Android 4.2不提供象systrace那么顯著的工具,只是提供了對你的工具箱的一些有益補充。在本文的后面你會發(fā)現(xiàn)這些新工具中的一個。
性能分析往往是一個復(fù)雜的任務(wù),需要大量的經(jīng)驗和對某些工具、硬件、API的深入的知識等等。經(jīng)驗使我能只用幾分鐘就進行在這里展示的分析——你可以在我十二月一日的Twitter stream上看到它“實時”發(fā)生。你可能要多試幾次才會感覺這種工作很容易。
證實我的懷疑
關(guān)于性能操作,牢記最重要的一件事就是始終用測試去驗證你的行為。即使Falcon Pro 在Nexus4上運行幀率下降看起來很明顯,我還是需要確認一下。因此,我將應(yīng)用安裝到一部提供不同于Nexus性能概況并且比Nexus4更強大的Nexus7上。Nexus 7提供了一個有趣的更有優(yōu)勢的性能分析工具,我們以后再談。
在Nexus 7安裝應(yīng)用程序并沒有什么區(qū)別,我仍能看到幀率下降。應(yīng)用甚至顯得稍差。為了描述這個問題,我決定用4.1以后引入的“GPU呈現(xiàn)模式分析”工具。你可以在應(yīng)用設(shè)置下的“開發(fā)者選項”中找到它。
如果“開發(fā)者選項”在你的android4.2設(shè)備上不能使用,找到“關(guān)于手機”或者“關(guān)于平板”勾選底部的7的倍數(shù)構(gòu)建選項。
當選項打開后, 系統(tǒng)會保持跟蹤每個窗口繪制最后128幀所耗費的時間。使用這個工具你必須首先結(jié)束掉應(yīng)用– android將來的版本將擺脫這個限制。
方法說明:除非另有規(guī)定,本分析每個測量是通過每次緩慢滾動主時間軸上下幾點,顯示最多有一個額外的列表項。
在運行應(yīng)用,主時間軸開始滾動時候,我在終端執(zhí)行了如下命令:
$ adb shell dumpsys gfxinfo com.jv.falcon.pro
在產(chǎn)生的日志中,你會發(fā)現(xiàn)一個標題為: Profile data in ms. 這一節(jié)包含為每個窗口所屬應(yīng)用產(chǎn)生的3列表格。 為了使用這些數(shù)據(jù), 簡單的復(fù)制表格到你喜歡的電子表格軟件中就會生成一個堆疊柱狀圖表。下面的圖是我的測量結(jié)果 (原始表格數(shù)據(jù) 可以在線查看.)
- Draw是消耗在構(gòu)建java顯示列表的時間。 它顯示出運行方法用的時間諸如View.onDraw(Canvas).
- Process是消耗在Android的2D渲染器執(zhí)行顯示列表的時間。你的視圖層次越多,要執(zhí)行的繪圖命令就越多。
- Execute是消耗在排列每個發(fā)送過來的幀的順序的時間.這部分的圖通常是很小的。
注意:使順利在60幀,每幀必須小于16毫秒完成。
關(guān)于Execute:如果執(zhí)行耗費了過長的時間,這意味著你是跑在前面的圖形管線。 android在運行時可以有3個緩沖區(qū).如果你需要另一個應(yīng)用程序?qū)⒆枞钡狡渲械囊粋€緩沖區(qū)釋放出來。兩個原因會發(fā)生這種情況。第一,你的應(yīng)用在Dalvik中快速繪制但在GPU顯示列表時候消耗了大量時間。第二,你的應(yīng)用程序花了很長的時間來執(zhí)行第幾幀;一旦管線滿了他將無法趕上,除非動畫完成。我們希望Android在未來版本中改進。
該圖顯然證實了我的懷疑:通常應(yīng)用運行良好,不過有時候會運行幀率下降。
進一步觀察
盡管我們收集的數(shù)據(jù)顯示,應(yīng)用有時候花費太久去渲染,但是這并不是全部的事實。幀刷新率也能夠被沒有調(diào)度或者錯誤調(diào)度的幀所影響。例如,一個應(yīng)用總是以少于16ms的時間來畫圖,但是有時候在幀之間展現(xiàn)更長的任務(wù),有時他將失去一個幀。
Systrace 是用于檢查,一個Falcon Pro 是否在遭受這個問題最簡單的工具。這個工具是一個具有非常低開銷的系統(tǒng)分析工具。它的時域分析相當精確,并且給你展示了整個系統(tǒng)在做什么,包括你的應(yīng)用。
為了讓systrace起作用,進入Developer options并且選擇Enable traces。一個對話框出現(xiàn),并且讓你選擇你想要分析什么類型的事件。我們只關(guān)心Graphics 和 View。
注意:不要忘記關(guān)掉分析GPU rendering。
#p#
為了使用systrace,打開一個終端,并從Android SDK的tools/systrace下運行systrace.py:
默認該工具將捕獲5秒鐘的事件。我只是上下滾動主時間軸。跟蹤結(jié)果是一個獨立的HTML文件。
建議:為了在systrack中能夠?qū)Ш剑梢允褂肳ASD鍵平移和縮放。W將會放大鼠標光標處的內(nèi)容。
一個systrack文件展示了很多有趣的信息。比如,它表明你是否有一個進程計劃在CUP。如果你放大到名為10440: m.jv.falcon.pro的最后一行,你能看到應(yīng)用做了些什么。如果你查看performTraversals中的一塊,你能看到應(yīng)用繪制一幀消耗了多長時間。
雖然大多數(shù)的performtraversals低于16毫秒的臨界值,但是有些需要更多的時間,這也證實了先前獲得的測量結(jié)果(放大在935 MS標記看到這樣一塊。)
更有意思的是,你可以看到應(yīng)用有時候會丟失一幀,因為它沒有安排一個繪制操作。放大到標記為270毫秒的地方,找到deliverInputEvent 塊,消耗25毫秒。這些塊表示應(yīng)用消耗25毫秒來響應(yīng)用戶觸摸事件。由于應(yīng)用程序是使用ListView,這可能是由于在適配器的一個問題,我們隨后會回過頭來討論。
Systrace十分有用,不僅是它能檢查應(yīng)用消耗過多的是在繪制上,而且也能幫助我們找到其他的性能瓶頸。盡管它很有用,但是也存在自身的局限性。它只提供了高層次的數(shù)據(jù),我們需要利用其他的工具才能明白它真正發(fā)生了什么。
可視化的透支
繪制性能問題可能有很多根本原因,但是其中一個常見的是透支。透支發(fā)生在應(yīng)用每次向系統(tǒng)請求在其他物體上繪制內(nèi)容。想像一個最簡單的應(yīng)用問題:一個白色背景的窗口,在它的上面一個按鈕。當系統(tǒng)繪制按鈕時,要繪制已存在的白色背景上。這就是透支。
透支是不可避免的,但是過多的透支就會產(chǎn)生問題。設(shè)備具有有限的內(nèi)存帶寬,如果透支導(dǎo)致你的應(yīng)用請求資源超過了可用帶寬,就是造成性能下降。不同設(shè)備可以明確負擔透支的數(shù)量是變化的。
一個好的經(jīng)驗法則是針對最大透支的2倍;這意味著你可以繪制屏幕一次,再畫上畫兩次,每個像素總量的3倍。
透支的存在也通常表示其他問題:太多的視圖,層次結(jié)構(gòu)復(fù)雜,通脹時間較長,等
Android提供了三個工具來識別和修復(fù)過度繪制: Hierarchy Viewer, Tracer for OpenGL 和 Show GPU overdraw。前兩個可以在ADT或者獨立的監(jiān)控工具中找到。最后一個是開發(fā)這選項的一部分。
Show GPU overdraw 使用不用的顏色來繪制屏幕,來指示過度繪制在哪里發(fā)生以及程度如何。打開此選項然后別忘了關(guān)閉你自己的應(yīng)用 – 未來的Android版本中將不再需要這樣做。
在看Falcon Pro之前,讓我們看一下設(shè)置Show GPU overdraw選項的頁面長什么樣。
如果你記得每種顏色代表的含義,這些結(jié)果就很容易解釋:
- 沒有顏色意味著沒有透支。像素只畫了一次。在這個例子中,你可以看到背景顏色沒有變化。
- 藍色 意味著透支1倍。像素繪制了兩次。大片的藍色還是可以接受的。 (如果整個窗口是藍色的,你可以擺脫一層。)
- 綠色 意味著透支2倍。像素繪制了三次。中等大小的綠色區(qū)域是可以接受的但你也應(yīng)該嘗試優(yōu)化,減少他們。
- 淺紅 意味著透支3倍。像素繪制了四次。小范圍內(nèi)可以接受。
- 暗紅 意味著透支4倍。像素繪制了五次或者更多。這是錯誤的,要修復(fù)他們。
基于此信息你可以看到設(shè)置是一個好的應(yīng)用程序,不需要任何額外的工作。有一點紅色在轉(zhuǎn)換部分,但是并不需要過于糾結(jié)。
透明像素:仔細看看以前的截圖。每個圖標是藍色的。你可以看到位圖的透明像素加劇了透支。透明像素必須被GPU處理并且代價是昂貴的。Android使用層和9-pathches作為最優(yōu)方案去避免繪制透明像素,所有你只用考慮位圖就行。
透支和GPU: 在移動設(shè)備上有兩種GPU架構(gòu)。第一個使用了延遲渲染, 例如:ImaginationTech的SGX系列。 這種架構(gòu)允許GPU檢測和修復(fù)透支的具體情況 (如果你是混合透明或半透明的像素,它將不起作用)第二結(jié)構(gòu)采用直接繪制,可以在NVIDIA的Tegra GPU的找到。這種體系結(jié)構(gòu)不能優(yōu)化你的透支,這就是為什么我喜歡在Nexus 7試驗。兩種架構(gòu)都是各種所長,各有所短,但是具體的內(nèi)容已經(jīng)超出本文范圍。只要知道兩種方式都很好的運行。
截圖中有大片的紅色! 然而有趣的是列表的背景是綠色的。這表明該應(yīng)用甚至在繪制正文之前,就有了一個2倍的過度繪制。這里我們看到的問題很可能跟多重全屏背景有關(guān)。要修復(fù)它通常比較容易。
刪除附加層
為了減少透支,我們必須了解它是怎么來的。這是比層次視圖、跟蹤OpenGL更有用。層次視圖是ADT(或者監(jiān)聽器)的一部分,并可以生成層次視圖快照。特別是調(diào)試布局問題,很有幫助,對性能工作更是得心應(yīng)手。
重要提示:層次視圖只能工作在非安全設(shè)備上,比如工程機、平板或者模擬器。在某些設(shè)備上使用層次視圖需要在你的應(yīng)用上安裝ViewServer(一個開源庫)。
在ADT(或監(jiān)視器)中打開 層次查看視圖 ,然后選擇 Windows 選項卡。斜體高亮的窗口是設(shè)備的前臺窗口,也就是通常情況下你要查看的那個。點擊它然后 點擊工具條中的 Load 按鈕 (它看起來像一個由藍方框組成的的樹形結(jié)構(gòu))。加載這棵樹可能需要一會兒所以要有耐心。加載好后,可以看到類似下圖的樣子。
現(xiàn)在層次的視圖已經(jīng)展現(xiàn)在工具中,我們可以查看它就像瀏覽一個Photoshop文檔那樣。要進行查看可以點擊工具條中的第二個按鈕 – 該按鈕的提示是“捕獲窗口層次[…]”。Adobe Photoshop不是必須的,因為這里生成的文檔也可以由諸如Pixelmator和GIMP之類的工具來打開。我生成的PSD 文件已經(jīng)可以從網(wǎng)上下載。
該Photoshop文檔以每一個視圖一個層次的方式展示了該應(yīng)用。每一層都被標記為可見或不可見,可以通過View.getVisibility()的返回值來查看。每一層都以它所屬的視圖來命名,或者是android:id 或者是類名。我曾經(jīng)嘗試添加分組支持來重建視圖樹…我真應(yīng)當好好完成該功能。
通過檢查層級列表,我們可以快速的確認至少一個過度繪制的原因:多重全屏背景。第一個背景是第一層視圖,稱為DecorView。該視圖是Android系統(tǒng)創(chuàng)建的,包含了主題中定義的背景。系統(tǒng)中漸變的默認設(shè)置是不可見的,所以可以安全的把它去除掉。
從DecorView向上滾動,你可以看到一個包含另一全屏梯度背景的LinearLayout。這是與DecorView完全相同的背景,因此是不必要的。唯一可見的背景,必須保持屬于名為id/tweet_list_container的視圖。
刪除窗口背景:當你的應(yīng)用程序啟動時,在你的主題中定義的背景是被系統(tǒng)使用創(chuàng)建預(yù)覽窗口的。絕不要把它設(shè)置為null,除非您的應(yīng)用程序是透明的。相反,要把它設(shè)置為你想要的顏色或圖片,或者通過調(diào)用getWindow().setBackgroundDrawable(null)從onCreate() 中擺脫它。
#p#
進一步減少過度繪制
Photoshop文檔有助于理解應(yīng)用是如何構(gòu)建的,但是用它來消除小區(qū)域的過度繪制時就有些困難了?,F(xiàn)在我們必須轉(zhuǎn)向Tracer for OpenGL。打開ADT (或監(jiān)視器)中的同名視圖,然后點擊工具條中的箭頭圖標。輸入你app的包名和主Activity的名稱,然后選擇一個目標文件并點擊Trace。
建議:獲取OpenGL跟蹤是大任務(wù)并且很耗時。要使該任務(wù)更輕便更快,請勿勾選所有的Data Collection Ooptions 復(fù)選框。
Activity 名稱:當你運行一個應(yīng)用時,logcat會展示包和Activity的名稱。這就是我怎么知道在Tracer for OpenGL中該敲入些什么的原因。
當應(yīng)用啟動起來,打開前兩個設(shè)置。
- Collect Framebuffer contents on eglSwapBuffers()
- Collect Framebuffer contents on glDraw*()
第一個有用的選項能幫助你快速的找到感興趣的幀。第二個選擇讓我們看到每個幀由繪圖命令繪制命令。第二個選項是解決透支問題的關(guān)鍵。
這兩個選項使我開始滾動主時間軸。它將消耗很長時間去捕捉每一幀(不出意外是30秒)所有我建議你直接下載我的捕獲跟蹤.你可以通過點擊工具欄上的第一個按鈕在Tracer for OpenGL打開文件。
一旦加載完成。跟蹤顯示你每GL命令發(fā)送到GPU的每個捕獲的幀。 如果你下載了我的跟蹤文件,跳轉(zhuǎn)到21幀。當某一幀被選中你可以看到類似Frame Summary 選項卡情形. 另外,你可以點擊藍色高亮繪制的命令,在Details 選項卡中查看當前幀的狀態(tài)。
組織:GL命令通過view分組。他們重新創(chuàng)建相同的樹,在你的Hierarchy Viewer或XML布局文件可以查看。這使得了解視圖生成特定的操作很容易。
通過點擊先后在前三個繪圖命令,你可以看到在PS已經(jīng)確認的問題;一個全屏背景繪制3次。
通過向下追溯查找,我們可以進一步地找到更多有待優(yōu)化的。當一個tweet(列表項目)被繪制的時候,一個ImageView控件用來繪制圖像??丶紫壤L制圖像本身的背景:
如果你湊近一點看,你會發(fā)現(xiàn)背景只是作為圖片的邊框。這意味著在圖像背景中間黑色的部分過度繪制了。9-patch部分都被圖像覆蓋了。
這個問題的簡單解決方法是把9-patch中間部分設(shè)置為透明。 Android的 2D渲染器總是把9-patch優(yōu)化為透明。這個簡單的改變將會去掉很多的過度繪制。
有趣的是, 相同確切的問題發(fā)生與內(nèi)聯(lián)元素。頭像小不是個大問題,但內(nèi)聯(lián)元素卻能夠占據(jù)屏幕的絕大部分區(qū)域。解決方法是完全一樣的。
更多的選擇: 我想Android的2D渲染管道,能夠自動檢測和糾正你過度繪制。我有一些想法但我不能作出任何承諾,就像使用內(nèi)置GPU的優(yōu)化,這只會與不完全透明圖元。
扁平化(譯注:用“縮減”更直觀)視圖層級
現(xiàn)在(其實是大部分時候)透支是要注意的事情,讓我們回到層級查看器。通過檢查層級樹,我們可以嘗試識別不必要的視圖。刪除視圖,尤其是視圖組,不僅可以幫助提高幀速率,而且可以減少內(nèi)存消耗和啟動時間等等。
快速瀏覽一下Falcon Pro的視圖層級,足以識別幾個只有一個單獨子視圖的視圖組。這些視圖組通常是不必要的,很容易去除。至少下面截圖中顯示的節(jié)點中的兩個應(yīng)該被刪除。
有許多其他的視圖可以從這棵樹中移除。例如,每個包含名為 id/listElementBottom的 RelativeLayout的tweet(譯者注:在Twitter上發(fā)布的消息)。此布局包含了作者的名字,他的Twitter地址,這條tweet發(fā)布過后的時間,和一個圖標。作者名稱和Twitter地址是兩個單獨的 TextView而不是僅用一個,是為了使用不同風格。時間和圖標使用一個 TextView 和一個 ImageView,可以使用 TextView的復(fù)合畫板功能合并成一個 TextView。
左邊的滑入式菜單使用了幾組 LinearLayout+TextView+ImageView 來顯示帶圖標的標簽。每一組都可以用一個單獨的TextView來替換。
如何擺平你的UI:我在2009年的谷歌I/O上談?wù)摿祟}為激發(fā)你的UI的文章,在其中更詳細地解釋了這些技術(shù)。
輸入事件處理
還記得當我們在看systrace的時候發(fā)現(xiàn)觸摸事件響應(yīng)處理有一些延遲嗎?現(xiàn)在就是解決這個問題的時候啦。traceview就是處置這種問題和了解應(yīng)用正在做什么的最好的工具。
Traceview是一個測量應(yīng)用的方法調(diào)用所耗時間的Dalvik分析器。怎樣使用它呢?在ADT或者監(jiān)視器中打開DDMS視圖,在Devices選項卡中選擇您的應(yīng)用的進程,然后點擊“start method profiling”按鈕(就是一個紅色圈圈和三個箭頭的那個按鈕)。
開啟追蹤之后,在主時間軸上選取開始和結(jié)束時間,再次點擊按鈕來完成跟蹤。您也可以下載 我的程序調(diào)試信息。結(jié)果如下。
點擊第21行,ViewRootImpl.draw(),該方法調(diào)用時間將會高亮。表的最后一行將會給出這個方法及其子方法的平均調(diào)用時間。如果你仔細看時間軸上的高亮處,你會發(fā)現(xiàn)連續(xù)幀之間的差別。
一個弄清楚這些差別之間是怎么回事的簡易方法就是在差別開始出現(xiàn)的地方逐漸放大,然后點擊其中最大的彩色塊。跟蹤父鏈直到找到你的問題所在。在這個案例中,我跟蹤了Pattern.compileImpl方法的調(diào)用(平均調(diào)用時間為0.5ms),找到了DBListAdapter.bindView方法.
顯然,應(yīng)用程序?qū)⑾嗤谋磉_式被一遍又一遍的重復(fù)編譯,每次調(diào)用時候勢必在主時間軸產(chǎn)生一次調(diào)用時間花費。Traceview表明bindview方法的平均調(diào)用時間為38ms,56%的時間都花在了HTML文本解析上。這個花費應(yīng)該在后臺默默的運行而不應(yīng)該阻塞UI主線程。當然正則表達式也不應(yīng)該被一次一次的重復(fù)編譯。
試試吧,少年!
最后一個調(diào)試給大家留作練習。這是一個包含兩個菜單可以向左或向右滑動切換的應(yīng)用。但是我在使用openGL調(diào)試工具調(diào)試時發(fā)現(xiàn)在菜單滑動切換的時候產(chǎn)生了大量的畫圖消耗。下載我的調(diào)試,看看是什么原因造成了這種現(xiàn)象吧(去到第34幀。)
提示:
1、應(yīng)用程序應(yīng)該通過調(diào)用 View.setLayerType()使用硬件層來簡化繪圖。
2、多余的背景也可以通過9-patches來巧妙的優(yōu)化。
3、裁剪同樣也是很有用的優(yōu)化方式。
4、通過設(shè)置ColorFilter傳遞給setLayerType()的畫筆的方式也許可以移除最后的繪圖指令哦。
我們一起學習了多種優(yōu)化應(yīng)用的工具。雖然我可以花大量的時間來講解選用何種技術(shù)來解決解決特定問題,但是那樣的話這篇文章就會顯得太臃腫了。android開發(fā)者官網(wǎng)上的提供文檔和Google I/O大會有關(guān)于android的討論(網(wǎng)上有免費的幻燈片和視頻)也許能夠幫助你。