前言
一個優秀的應用不僅僅是要有吸引人的功能和互動,同時在效能上也有很高的要求。執行Android系統的手機,雖然配置在不斷的提升,但仍舊無法和PC相比,無法做到PC那樣擁有超大的記憶體以及高效能的CPU,因此在開發Android應用程式時也不可能無限制的使用CPU和記憶體,如果對CPU和記憶體使用不當也會造成應用的卡頓和記憶體溢位等問題。因此,應用的效能優化對於開發人員有著更高的要求。Android效能優化分為很多種,比較常用的有繪製優化、記憶體優化、耗電優化和穩定性優化等,這個系列我們就來學習效能優化中的繪製優化。
1.繪製原理
Android繪製View有三個主要的步驟,分別是measure、layout和draw。關於它們的原理請檢視我的文章Android View體系(七)從原始碼解析View的measure流程和Android View體系(八)從原始碼解析View的layout和draw流程,這裡就不在贅述。measure、layout和draw方法主要是執行在系統的應用框架層,而真正將資料渲染到螢幕上的則是系統Nativie層的SurfaceFlinger服務來完成的。
繪製過程主要是由CPU 來進行Measure、Layout、Record、Execute的資料計算工作,GPU負責柵格化、渲染。CPU和GPU是通過圖形驅動層來進行連線的。圖形驅動層維護了一個佇列,CPU將display list新增到該佇列中,這樣GPU就可以從這個佇列中取出資料進行繪製。
渲染時間線
FPS(Frames Per Second)這個名詞我想很多同學都知道,它是指畫面每秒傳輸幀數,通俗來講就是指動畫或視訊的畫面數,最簡單的舉例就是我們玩遊戲時,如果畫面在60fps則不會感覺到卡頓,如果低於60fps,比如50fps則會感覺到卡頓,你就可以考慮要換顯示卡或者採取其他一些措施了。
要想畫面保持在60fps,則需要每個繪製時長在16ms以內,如下圖所示。
Android系統每隔16ms發出VSYNC訊號,觸發對UI進行渲染, 如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,那什麼是VSYNC呢?VSYNC是Vertical Synchronization(垂直同步)的縮寫,是一種定時中斷,一旦收到VSYNC訊號,CPU就開始處理各幀資料。
如果某個操作要花費24ms,這樣系統在得到VSYNC訊號時無法進行正常的渲染,會發生丟幀。使用者會在32ms中看到同一幀的畫面,如下圖所示。
產生卡頓原因有很多,主要有以下幾點:
- 佈局Layout過於複雜,無法在16ms內完成渲染。
- 同一時間動畫執行的次數過多,導致CPU或GPU負載過重。
- View過度繪製,導致某些畫素在同一幀時間內被繪製多次。
- UI執行緒中做了稍微耗時的操作。
為了解決上述的問題,除了我們要在寫程式碼時要注意外,也可以藉助一些工具來分析和解決卡頓問題。
2.Profile GPU Rendering
Profile GPU Rendering是Android 4.1系統提供的開發輔助功能,我們可以在開發者選項中開啟這一功能,如下圖所示。
我們點選Profile GPU Rendering選項並選擇On screen as bars即開啟Profile GPU Rendering功能。接著螢幕會顯示出彩色的柱狀圖,如下所示。
上面的彩色的圖的橫軸代表時間,縱軸表示某一幀的耗時。綠色的橫線為警戒線,超過這條線則意味著時長超過了16m,儘量要保證垂直的彩色柱狀圖保持在綠線下面。這些垂直的彩色柱狀圖代表著一幀,不同顏色的彩色柱狀圖代表不同的含義:
- 橙色代表處理的時間,是CPU告訴GPU渲染一幀的地方,這是一個阻塞呼叫,因為CPU會一直等待GPU發出接到命令的回覆,如果橙色柱狀圖很高,則表明GPU很繁忙。
- 紅色代表執行的時間,這部分是Android進行2D渲染 Display List的時間。如果紅色柱狀圖很高,可能是由重新提交了檢視而導致的。還有複雜的自定義View也會導致紅的柱狀圖變高。
- 藍色代表測量繪製的時間,也就是需要多長時間去建立和更新DisplayList。如果藍色柱狀圖很高,可能是需要重新繪製,或者View的onDraw方法處理事情太多。
在Android 6.0中,有更多的顏色被加了進來,如下圖所示:
下面來分別介紹它們的含義:
- Swap Buffers:表示處理的時間,和上面講到的橙色一樣。
- Command Issue:表示執行的時間,和上面講到的紅色一樣。
- Sync & Upload:表示的是準備當前介面上有待繪製的圖片所耗費的時間,為了減少該段區域的執行時間,我們可以減少螢幕上的圖片數量或者是縮小圖片的大小。
- Draw:表示測量和繪製檢視列表所需要的時間,和上面講到的藍色一樣。
- Measure/Layout:表示佈局的onMeasure與onLayout所花費的時間,一旦時間過長,就需要仔細檢查自己的佈局是不是存在嚴重的效能問題。
- Animation:表示計算執行動畫所需要花費的時間,包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等。一旦這裡的執行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執行的過程中是不是觸發了讀寫操作等等。
- Input Handling:表示系統處理輸入事件所耗費的時間,粗略等於對事件處理方法所執行的時間。一旦執行時間過長,意味著在處理使用者的輸入事件的地方執行了複雜的操作。
- Misc Time/Vsync Delay:表示在主執行緒執行了太多的任務,導致UI渲染跟不上VSYNC的訊號而出現掉幀的情況。
Profile GPU Rendering可以找到渲染有問題的介面,但是想要修復的話,只依賴Profile GPU Rendering是不夠的,可以用另一個工具Hierarchy Viewer來檢視佈局層次和每個View所花的時間,這個工具會在下一篇文章進行介紹。
3.Systrace
Systrace是Android4.1中新增的效能資料取樣和分析工具。它可幫助開發者收集Android關鍵子系統(SurfaceFlinger、WindowManagerService等Framework部分關鍵模組、服務,View體系系統等)的執行資訊。Systrace的功能包括跟蹤系統的I/O操作、核心工作佇列、CPU負載以及Android各個子系統的執行狀況等。對於UI顯示效能,比如動畫播放不流暢、渲染卡頓等問題提供了分析資料。
使用Systrace
Systrace跟蹤的裝置要在Android4.1版本以上,對於Android4.3版本之前和4.3版本之後使用上有點區別,現在也很少有人用Android4.3之前的版本,因此這裡只講Android4.3版本的使用方法。Systrace可以在DDMS上使用,可以使用命令列來使用,也可以在程式碼中進行跟蹤。接下來分別來介紹這三種方式。
在DDMS中使用Systrace
1.首先我們要開啟Android Studio的Tool中的Android Device Monitor,並連線手機。
2.點選Systrace按鈕進入抓取設定介面,如下圖所示。
抓取設定介面可以設定跟蹤的時間,以及trace檔案輸出的地址等內容。如下圖所示。
3.設定完成後,我們就來操作的跟蹤的過程。跟蹤時間結束後,生成trace.html檔案。
4.用Chrome開啟trace.html檔案進行分析。分析的方法,後文會講到。
用命令列使用Systrace
Android 提供一個python指令碼檔案 systrace.py,它位於Android SDK 目錄 /tools/systrace 中,我們可以執行以下命令來使用Systrace:
$ cd android-sdk/platform-tools/systrace
$ python systrace.py --time=10 -o newtrace.html sched gfx view wm複製程式碼
在程式碼中使用Systrace
Systrace並不會追蹤應用的所有工作,在Android4.3及以上版本的程式碼中,可以使用Trace類對應用中的具體活動進行追蹤。
Android原始碼中也引用了Trace類,比如RecyclerView:
...
private final Runnable mUpdateChildViewsRunnable = new Runnable() {
public void run() {
if (!mFirstLayoutComplete) {
return;
}
if (mDataSetHasChangedAfterLayout) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
} else if (mAdapterHelper.hasPendingUpdates()) {
TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
eatRequestLayout();
mAdapterHelper.preProcess();
if (!mLayoutRequestEaten) {
rebindUpdatedViewHolders();
}
resumeRequestLayout(true);
TraceCompat.endSection();
}
}
};
...複製程式碼
TraceCompat類對Trace類進行了封裝,只會在Android4.3及以上版本才會使用Trace類,其中beginSection方法和endSection方法之間的程式碼會被追蹤,endSection方法會只會結束最近的beginSection方法,因此要保證beginSection方法和endSection方法的呼叫次數要相同。
用Chrome分析Systrace
通過前面的方法生成的trace.html需要用Chrome開啟,開啟後效果如下圖所示。
我們可以使用W鍵和S鍵進行放大和縮小,A鍵和D鍵進行左右移動。
Alert區域
首先來看Alert區域,這一區域會標記處效能有問題的點,單擊歎號圖示就可以檢視某一個Alert的問題描述,如下所示。
這個Alert指出了View在Measure/Layout時耗費了大量的時間,導致出現jank(同一幀畫了多次)。給出的建議是避免在動畫播放期間控制佈局。
CPU區域
接下來我們來檢視CPU區域,每一行代表一個CPU核心和它執行任務的時間片,放大後會看到每個色塊代表一個執行的程式,色塊的長度代表其執行時間,如下圖所示。
圖中CPU 0主要執行adbb執行緒和InputReader執行緒,CPU 2主要執行了surfaceflinger執行緒和ordinatorlayout程式中的RenderThread執行緒,我們點選RenderThread色塊,會給出RenderThread的相關資訊,如下圖所示。
圖中給出了當前色塊所執行的執行緒和程式、開啟時間和持續時間等資訊。
應用區域
應用區域會顯示應用的幀數,如下圖所示。
Systrace會給出應用中的Frames分析,每一幀就是一個F圓圈,F圓圈有三種顏色,其中綠色表示Frame渲染流暢,黃色和紅色則代表渲染時間超過了16.6ms,其中紅的更嚴重些。我們點選紅色F圓圈,會給出該Frame的資訊,如下圖所示。
從圖中可以看出,Frame給出了問題提示:Scheduling delay(排程延遲),當一幀繪製時間超過19ms會觸發該提示,更何況這一幀已經有將近40ms了。導致這一問題產生的原因主要是執行緒在繪製時,在很長一段時間都沒有分配到CPU時間片,因此無法繼續進行繪製。按m鍵來高亮該時間段,我們來檢視CPU的情況,如下圖所示。
可以看出這個時間段中兩個CPU都在滿負荷執行。至於具體是什麼讓CPU繁忙,則需要使用Traceview來進行分析。
Alerts總體分析
點開最右邊的Alerts按鈕會給出Alert的總體分析,如下圖所示。
Alerts會給出Alert型別,以及出現的次數。有了這些總體的分析,方便開發者對該時間段的繪製效能有一個整體的大概瞭解,便於進行下一步分析。
由於Systrace 是以系統的角度返回一些資訊,只能為我們提供一個概覽,它的深度是有限的,我們可以用它來進行粗略的檢查,以便了解大概的情況,但是如果要分析更詳細的,比如要找到是什麼讓CPU繁忙,某些方法的呼叫次數等,則還要藉助另一個工具:Traceview。
4.Traceview
TraceView是Android SDK中自帶的資料採集和分析工具。一般來說,通過TraceView我們可以得到以下兩種資料:
- 單次執行耗時的方法。
- 執行次數多的方法。
使用Traceview
要分析Traceview,則首先要得到一個trace檔案,trace檔案的獲取有兩種方式,分別是在DDMS中使用和在程式碼中加入除錯語句,下面分別對這兩種方式進行介紹。
DDMS中使用
1.首先我們要開啟Android Studio的Tool中的Android Device Monitor,並連線手機。
2.選擇相應的程式,並單擊Start Method Profiling按鈕。
3.對應用中需要監控的點進行操作。
4.單擊Stop Method Profiling按鈕,會自動跳到TraceView檢視。
程式碼中加入除錯語句
如果開發中出現不好復現的問題,則需要在程式碼中新增TraceView監控語句,程式碼如下所示。
Debug.startMethodTracing();
...
Debug.stopMethodTracing();複製程式碼
在開始監控的地方呼叫startMethodTracing方法,在需要結束監控的地方呼叫stopMethodTracing方法。系統會在SD卡中生成trace檔案,將trace檔案匯出並用SDK中的Traceview開啟即可。當然不要忘了在manifest中加入 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
許可權。
分析Traceview
為了分析Traceview,我們來舉一個簡單的例子來生成trace檔案,這裡採用第二種方式:程式碼中加入除錯語句。程式碼如下所示。
public class CoordinatorLayoutActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TabLayout mTabLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout);
Debug.startMethodTracing("test");//1
initView();
...
}
private void initView() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void onStop() {
super.onStop();
Debug.stopMethodTracing();
}
}複製程式碼
在註釋1處呼叫了startMethodTracing方法開始監控,其中test是生成的trace檔案的名稱。在initView中我們特意呼叫sleep方法來做耗時操作。在onStop方法中我們呼叫了stopMethodTracing方法結束監控。這時會在SD卡根目錄生成test.trace檔案,我們將該檔案匯出到桌面,用Traceview來分析test.trace檔案,我們在cmd中執行如下語句。
我們進入traceview所在的目錄(直接將traceview.bat拖入到cmd中),並執行上圖的traceview語句後會彈出Traceview檢視,它分為兩部分,分別是時間片皮膚和分析皮膚,我們先來看時間片皮膚,如下圖所示。
其中x軸代表時間的消耗,單位為ms,y軸代表各個執行緒。一般會檢視色塊的長度,明顯比較長的方法重點去關注,具體的分析還得看分析皮膚,如下圖所示。
每一列資料的代表的含義如下表所示。
列名 | 含義 |
---|---|
Name | 該執行緒執行過程中呼叫的函式名 |
Incl Cpu Time% | 某個方法包括其內部呼叫的方法所佔用CPU時間百分比 |
Excl Cpu Time% | 某個方法不包括其內部呼叫的方法所佔用CPU時間百分比 |
Incl Real Time% | 某個方法包括其內部呼叫的方法所佔用真實時間百分比 |
Excl Real Time% | 某個方法不包括其內部呼叫的方法所佔用真實時間百分比 |
Calls + Recur Calls / Total | 某個方法次數+遞迴呼叫次數 |
Cpu Time / Call | 該方法平均佔用CPU時間 |
Cpu Time / Call | 該方法平均佔用真實時間 |
Incl Cpu Time | 某個方法包括其內部呼叫的方法所佔用CPU時間 |
Excl Cpu Time | 某個方法不包括其內部呼叫的方法所佔用CPU時間 |
Incl Real Time | 某個方法包括其內部呼叫的方法所佔用真實時間 |
Excl Real Time | 某個方法不包括其內部呼叫的方法所佔用真實時間 |
因為我們用sleep方法來進行耗時操作,所以這裡我們可以單擊Incl Real Time來進行降序排列。其中有很多系統呼叫的方法,我們來進行一一過濾。最終我們發現了CoordinatorLayoutActivity的initView方法Incl Real Time的時間為1000.493ms,這顯然有問題,如下圖所示。
從圖中我們可以看出是呼叫sleep方法導致的耗時。關於Traceview還有很多種分析情況,就需要大家在平時進行積累了。
好了關於繪製效能分析,就講到這,如果覺得不過癮,本系列的後續文章還有大波的內容會持續向你砸來。
參考資料
《Android群英傳 神兵利器》
《Android應用效能優化最佳實踐》
blog.csdn.net/itachi85/ar…
www.cnblogs.com/sunzn/p/319…
blog.csdn.net/androiddeve…
www.tuicool.com/articles/jM…
www.mobile-open.com/2015/85005.…
歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。