概要
應用運行時的卡頓問題非常影響用戶體驗,嚴重降低產品表現力,本文將介紹應用卡頓原因以及分析方法等等。
卡頓問題可分為兩類,應用卡頓和系統卡頓,本文針對系統正常時應用卡頓場景。
黃油工程
Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,為了能夠實現60fps,這意味著程序的大多數操作都必須在16ms內完成。
如上圖,第1幀在顯示時,GPU并沒有準備好第2幀數據,導致第1幀數據連續顯示兩次,導致不流暢、卡頓。應用需要減輕UI線程負擔,將耗時工作放入工作線程中,同時精簡繪制工作,保證16ms內可以完成一幀繪制。
圖層疊加
Android通過圖層疊加完成繪制。
左側的對象是生成圖形緩沖區的渲染器,如主屏幕、狀態欄和系統界面。SurfaceFlinger 是合成器,屏幕負責顯示合成界面。
應用界面層級應該盡量精簡,同時減少過度繪制問題,減輕系統壓力。
卡頓原因
應用卡頓常見的原因,UI線程負荷過重,繪制代碼不當等。系統卡頓或資源緊張也有可能導致應用卡頓,gc過多也能導致卡頓。此外,過度繪制,界面層級過多也會導致繪制效率下降。
在某些GPU性能較差的機器上,alpha動畫效率低下,也會導致卡頓。
分析方法
遇到卡頓問題,從現象入手,排除系統卡頓原因,接下來可通過log分析、工具定位解決卡頓問題。常用工具為traceview,systrace等。
Traceview,針對單一應用,統計具體一段時間內方法執行時間、次數等信息。
Systrace,記錄整機運行情況,包括cpu使用情況,vsync信號,gc信息等等,它也能記錄部分方法的執行情況,時間等。
Log分析,重點跟蹤gc情況,具體業務邏輯流程等。
Traceview
Traceview有兩種使用方式,既可使用DDMS采集數據,也可使用Debug類采集數據。使用DDMS采集數據的步驟如下:
- 在應用的AndroidManifest.xml添加 android:debuggable="true",打開debug屬性
- 打開DDMS面板,選中調試的應用
- 點擊 Start Method Profiling 按鈕
- 操作機器,執行對應需要性能分析的過程
- 點擊 Stop Method Profiling 按鈕
抓取數據完成后會自動跳轉到traceview界面。
除了使用DDMS采集數據,也可以使用Debug類采集數據,使用Debug類采集數據步驟如下:
- 在AndroidManifest.xml中添加寫sd卡權限
- 在問題懷疑點處調用Debug.startMethodTracing("demo");
- 在問題結束點處調用Debug.stopMethodTracing();,例如,可以在Activity的onCreate和onPause處分別調用上述方法。
- 從sd卡中導致trace文件到pc端,利用ddms打開trace文件。
DDMS方式簡單,但很粗略。而使用Debug類采集數據,相對復雜,但更精確。
Traceview分析面板上,有很多的維度,一般來說,我們需要分析出那些耗時或者調用次數過多的函數,耗時的函數可通過維度Incl CPU Time分析。調用次數過多的函數可通過Cpu Time/Call維度分析。耗時的函數需要分析,很容易理解,但調用次數過多的函數很容易被忽略,如果view的onMeasure函數在不需要刷新時被調用多次,這也是值得關注的問題,所以需要分析函數的調用次數。
Traceview應用
模擬如下場景,ListView滑動卡頓,在getView方法中sleep一段時間,導致一幀數據無法在16ms內準備完成,因此滑動卡頓。
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
。。。。。。
try {
Thread.sleep(50);
} catch (Exception e) {
}
return convertView;
}
使用DDMS采集數據分析,按Incl CPU Time降序排列,traceview面板如下:
因為Incl CPU Time是包含內部調用的其它函數時間的,所以從上到下,花時間最多的就是虛擬機相關方法以及ActivityThread的main方法(應用線程啟動的入口方法)等。點擊每一個方法,均可以看到此方法自身花費時間、調用其它方法花費時間以及它的前一調用方法。一般來說通過跟蹤調用鏈,可找到對應問題所在。
從ActivityThread的main方法入手,parents屬性不用管,查看children屬性,self即是自身花費時間,此時為0,可見耗時是花在loop方法,一跟跟蹤調用鏈最終發現如下:
Adapter類的getView方法中調用了sleep方法以及setImageResource方法,而sleep方法耗時占用整體耗時的97%,如此,問題原兇已找到。
有一個小竅門,在分析某個方法的耗時構成時,如果此方法顯示有多個子方法耗時時,則選取耗時比較最大的子方法繼續分析,如下下面的情況:
我們預先已知listview滑動卡頓,那么耗時最多的應該是與listview相關的函數,而事實也是如此,繼續跟蹤線框內容,即可找到最終問題。
Systrace
Systrace與Traceview所不同的是,Systrace它能提供豐富得多的信息,它是對整機這段時間內的全面信息反饋,可以查看cpu分配情況,查看vsync信號,查看應用具體一幀圖像各階段耗時情況,甚至還可以查看gc情況,所以Systrace可以從系統高度,整體高度來查看卡頓問題。如果是分析單一應用卡頓問題,一定要通過Systrace來排除系統問題,確認這段時間內cpu資源足夠,也要查看gc情況。如果某段時間內,應用一幀卡頓,而cpu資源不夠或此時正在gc,則并不能說明是應用的問題。
由于google對android studio的大力推薦,現在使用eclipse抓取的Systrace,已經無法被chrome識別了,所以需要as抓取Systrace,抓取并打開Systrace的步驟如下:
- 打開as,打開tools菜單,打開android device monitor
- 點擊抓取systrace按鈕,勾選所需的信息,再確定
- 在機器上進行相應操作,生成對應trace文件
- 打開chrome瀏覽器,在地址欄上輸入chrome://tracing/,再使用chrome打開之前保存的trace文件
Systrace使用W放大,S縮小,A左移,D右移,右側有選擇條,可選擇鼠標點擊,也可選擇時間段標記。操作較為簡單,不再詳述。
Systrace有幾個需要注意的點,首先就是cpu的狀態問題。例如上圖紅框所示,obtainView方法上是無色的,而右邊小部分確是藍色或綠色,無色則代表cpu此時在睡眠狀態,藍色或綠色表示cpu處于運行狀態。如上圖所示,則可知此時cpu狀態異常,是否是調用了sleep方法導致這一幀卡頓了?可使用左鍵滑動選中一段距離,查看這段距離的cpu情況:
Systrace也是可以直接點擊紅色幀(紅色幀即是卡頓幀),查看紅色幀的一些基本情況,也能看到對于紅色幀的處理意見。
Systrace還能夠顯示當前的gc情況,例如:
Systrace還有一個強大的功能,選中查看某段時間內cpu的情況,如下圖所示,cpu這段時間內有gc操作,binder調用,還有打印log,如果gc操作過于頻繁,binder調用都是會影響系統性能的,另外log過多也是會影響系統性能的。
章節中的示例,抓取Systrace,分析,案例簡單,在Systrace上也一目了解,obtainView時間太長了,原因是cpu那段時間都處于sleep狀態。
性能優化的其它方法
前文介紹的是解決卡頓問題的重型武器,也有一些方法可以緩解卡頓或者讓性能更好。通過繪制原理的介紹,可以清楚地知道,如果UI的層級越少越好。怎么查看UI的層級呢?可以通過hierarchyviewer工具。可以通過自定義view形式實現UI層級的減少。UI層級減少,也會緩解過度繪制問題。
另外,減少界面刷新的次數也很重要,避免不必要的UI界面刷新。降低UI層級以及減少不必要刷新次數較為簡單,不再詳述。