在學習了 JVM、DVM、ART後,我們對Android關于運作時的一些機制(記憶體配置設定、垃圾回收、AOT、JIT)有所了解。
接下來就可以正式的性能調優了。
第一個學習的就是繪制優化,因為繪制和界面流暢度有關,這個優化的效果是最直覺的。
1.繪制性能分析
要學會性能的分析,首先要知道繪制的原理,接着就是介紹性能繪制的工具:
Profile GPU Rendering
、
Systrace
和
Traceview
。
1.1 繪制原理
View的繪制流程有 measure、layout和draw,他們主要運作在應用架構層。而真正将資料渲染到螢幕上的則是系統Native層的
SurfaceFlinger
服務來完成的。
繪制過程主要由CPU來進行
Measure
、
Layout
、
Record
、
Execute
的資料計算工作,GPU複制栅格化、渲染。
CPU和GPU是通過圖形驅動層來進行連接配接,圖形驅動層維護了一個隊列,CPU将display list添加到該隊列中,這樣GPU就可以從這個隊列中取出資料進行繪制。
畫面的流暢度的表現就是幀數,正常情況下,人眼在觀察60fps的畫面是流暢的、能夠被大腦很舒服的接受的。
如果螢幕的重新整理率要保持在60fps,就要螢幕在1秒鐘重新整理60次,是以每
16.6667ms
就要重新整理一次。
Android系統每隔16ms發出
VSYNC信号
,觸發對UI渲染,如果每次都能渲染成功,那麼就能達成流暢的60fps。
VSYNC信号
是垂直同步(Vertical Synchronization)的縮寫。每次接收到 VSYNC信号,CPU就計算各種幀資料,如果某個操作要花費超過16ms的時間,那麼使用者就要在 (totalTime / 16 + 1) * 16 的時間内看到同一幀畫面。
比如一幀CPU處理了17ms,超了1ms,這就導緻在 32ms的時間内看到的是同一幀畫面。
産生卡頓的原因有很多,主要有以下幾點:
- 布局Layout過于複雜,無法在16ms内完成渲染
- 統一時間動畫執行次數過多,導緻CPU或GPU負載過重
- View過度繪制,導緻某些像素在同一幀被繪制很多次
- 在UI線程中做了稍微耗時的操作
- GC回收時暫停時間過長或者頻繁的GC産生大量的暫停時間
為了解決上述的問題,除了我們要在寫代碼時注意外,也可以借助一些工具來分析和解決卡頓問題。
1.2 Profile GPU Rendering
Profile GPU Rendering是Android4.1系統提供的開發輔助功能,我們可以在開發者選項中打開這一個功能。
單擊 Profile GPU Rendering -> On screen as bars(中文版是 GPU渲染模式分析 -> 在螢幕上顯示為條形圖):

其中橫軸代表時間,縱軸代表某一幀的耗時。綠色的橫線為警戒線,超過這條線則意味着市場超過了了16ms,盡量要保證垂直的彩色柱狀圖保持在綠線下面,這些垂直的彩色柱狀圖代表着一幀,不同顔色的彩色柱狀圖代表不同的含義。
,具體如下:
他們的含義為:
- 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所花的時間,這個工具會在後面介紹。
1.3 Systrace
Systrace是Android4.1新增的性能資料采樣和分析工具,它可以幫助開發者手機Android關鍵子系統(SufaceFlinger、WMS等Framework部分關鍵子產品、服務,View體系系統等)的運作資訊。
Systrace的功能包括跟蹤系統的I/O操作、核心工作隊列、CPU負載以及Android各個子系統的運作狀況等。
是以可以用其來分析 UI渲染。
關于Systrace的使用:
Systrace跟蹤的裝置要在Android4.1上使用,其中4.3版本前後有差別,因為現在很少有4.3之前的手機了,是以就講4.3之後的使用方法。
Systrace可以再DDMS上使用,可以使用指令行來使用,也可以在代碼中進行跟蹤。接下來介紹3種方式
1.在DDMS種使用Systrace
因為Google認為DDMS已經不好用了,是以在Android Studio3.0就删除它了。
我們要去 Sdk\tools 下找到 monitor.bat,運作它就能看到Android Device Monitor了。
選中目前追蹤的手機,點中圖中框出的地方:
最上面的路徑就是Systrace生成html的路徑。
第二行就是跟蹤的時間,可以自己設定。
點選ok後,就會去追蹤目前應用。
2. 用指令行使用Systrace
Android提供一個Python腳本檔案systrace.py,它位于Android SDK目錄 /tools/systrace中,我們可以使用以下指令來使用Systrace:
C:\>cd C:\Users\msn\AppData\Local\Android\Sdk\platform-tools\systrace
C:\Users\msn\AppData\Local\Android\Sdk\platform-tools\systrace>python systrace.py --time=10 -o newtrace.html sched gfx view wm
然後我的就提示:Systrace does not support Python 3.7. Please use Python 2.7.
…
也就是說Android Studio通過指令行使用Python檢視Systrace,要用2.7版本的…
3. 在代碼中使用Systrace
可以使用Trace類對應用中的具體活動進行追蹤。Android源碼中也引入了Trace類,為了展示得更加直覺,這裡RecyclerView得源碼:
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
this.startInterceptRequestLayout();
this.onEnterLayoutOrScroll();
//這裡開始使用了Trace進行追蹤
TraceCompat.beginSection("RV Scroll");
this.fillRemainingScrollValues(this.mState);
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = this.mLayout.scrollHorizontallyBy(dx, this.mRecycler, this.mState);
}
if (dy != 0) {
consumedY = this.mLayout.scrollVerticallyBy(dy, this.mRecycler, this.mState);
}
//這裡開始暫停了Trace追蹤
TraceCompat.endSection();
this.repositionShadowingViews();
this.onExitLayoutOrScroll();
this.stopInterceptRequestLayout(false);
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
TraceCompat
類對
Trace
類進行了封裝,其中
beginSection()
和
endSection()
方法之間的代碼會被追蹤,
endSection()
隻會結束最近的
beginSection()
,是以要保證這個兩個方法調用的次數要相同。
用Chrome分析Systrace
通過前面的方法生成了trace.html檔案後,我們用Chrome打開它,如下圖所示:
我們可以使用W、S鍵進行放大和縮小,A、D鍵進行左右移動。接下來介紹上圖中的各個區域:1.Alert區域
這個區域會标記性能有問題的點,單擊歎号圖示就是檢視某一個Alert的問題描述,如下圖所示:
這裡指出:Scheduling delay說明這個處理特定時間片的線程很長時間沒有被CPU排程,是以這個線程花了很久才完成。為什麼會出現這種狀況呢?可能是因為開啟了太多的線程和UI線程競争CPU資源,導緻UI線程遲遲不能執行。2.CPU區域
CPU區域,每一行代表一個CPU核心和它執行任務的時間片,放大後會看到每個色塊代表一個執行過程,色塊的長度表示執行時間。
CPU0主要執行adbb線程和InputReader線程,CPU 1主要執行surfaceflinger線程和ordinartorlayout程序中的RnderThread線程,我們單擊每個色塊,都能看到它做了什麼事情,用了多久。3. 應用區域
Systrace會給出應用中的Frames分析,每一幀就是一個F圈圈,F圈圈有三種顔色,綠色表示渲染流暢,黃色和紅色則代表渲染的時間超過了16ms,紅色更嚴重一點。圖中的紅色F點選後會告訴我們原因。4. Alerts總體分析
單擊最右邊的Alerts按鈕會給出Alert的總體分析:
Alerts會給出Alert類型,以及出現的次數。有了這些總體的分析,友善開發者對該間斷的繪制性能有一個大概的了解,便于進行下一步分析。
由于Systrace是以系統的角度傳回一些資訊的,隻能為我們提供一個概覽,它的深度是有限的,我們可以用它來進行粗略的檢查,以便了解大概的情況,但是如果還要分析的更加詳細,比如要找到是什麼讓CPU繁忙,某些方法的調用次數等,則還要借助另一個工具 Traceview。
1.4 Traceview
TraceView是 AndroidSdk中自帶的資料采集和分析工具,一般來說通過TraceView,我們可以得到兩種資料:
- 單次執行耗時的方法
- 執行次數多的方法
如何使用Traceview
要分析traceview,首先要得到一個 trace檔案。擷取trace檔案有兩種方式:
-
DDMS中使用
在Android Device Monitor中選擇相應的程序,并單擊
Start Method Profiling
按鈕
然後對應用中需要監控的點進行操作
最後單擊
按鈕,會自動跳到TraceView師徒Stop Method Profiling
- 在代碼中加入調試語句
Debug.startMethodTracing();
...
Debug.stopMethodTracing();
系統會在SD卡中生成trace檔案,将trace檔案導出并用SDK中的Traceview打開即可。當然這需要我們為應用程式加上寫權限。
然後我們就能得到trace檔案。
接着我們來分析一下trace檔案:
2.布局優化
一個界面的測量和繪制是通過遞歸來完成的,減少布局的層數就會減少測量和繪制的時間,進而性能就會得到提升。
當然這隻是布局優化的一方面。
在講布局優化之前,我們先來學習兩種布局優化的工具,分别是 Hierarchy Viewer和Android Lint
2.1 Hierarchy Viewer
Hierarchy Viewer是Android SDK自帶的可視化的調試工具,用來檢查布局嵌套和繪制的時間。
我們通過 Android Device Monitor,在其中選擇Hierarchy Viewer如下圖:
打開後會給出一個Hierarchy Viewer視窗,在Hierarchy Viewer視窗中有4個子視窗,它們的作用如下:
-
Windows
目前裝置所有界面清單
-
Tree View
将目前Activity 的所有View的層次按照高層到低層從左到右顯示出來
-
Tree Overview
全局概覽,以縮略的形式顯示
-
Layout View
整體布局圖,以手機螢幕上真是的位置呈現出來。
使用注意:
在Android的官方文檔中提到:
To preserve security, Hierarchy Viewer can only connect to devices running a developer version of the Android system.
即:出于安全考慮,Hierarchy Viewer隻能連接配接Android開發版手機或是模拟器(準确地說,隻有ro.secure參數等于0且ro.debuggable等于1的android系統)。Hierarchy Viewer在連接配接手機時,手機上必須啟動一個叫View Server的用戶端與其進行socket通信。而在商業手機上,是無法開啟View Server的,故Hierarchy Viewer是無法連接配接到普通的商業手機。
是以我們在使用
Android Device Monitor
去連接配接真機/沒有配置的模拟器 會提示:
Unable to get view server version from device xxxx....
網上有很多教程,主要是通過Android逆向去替換系統檔案,讓系統通過可以調試的驗證。
這邊我的做法是使用 ViewServer的開源架構,在導入後,在Activity中的使用方法是:
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set content view, etc.
ViewServer.get(this).addWindow(this);
}
public void onDestroy() {
super.onDestroy();
ViewServer.get(this).removeWindow(this);
}
public void onResume() {
super.onResume();
ViewServer.get(this).setFocusedWindow(this);
}
}
然後再去重新打開Hierarchy Viewer,在手機中打開使用這個代碼的Activity就能看到層級結構了:
在我們開發中加入這些代碼,當界面完成時,我們可以将這些調試的代碼注釋掉。
通過單擊每個view,我們可以知道view的 layout、mesure、draw的耗時。
一個合格的布局,它應該是層級合理的。即縱向多,橫向少。
2.2 Android Lint
Android Lint是在ADT 16中提供的新工具,它是一個代碼掃描工具,通過代碼靜态檢查來發現代碼出現的潛在問題,并給出優化建議。檢查的範圍主要有以下幾點:
- 正确性:Correctness
- 安全性:Security
- 性能:Performance
- 可用性:Usablity
- 可達性:Accessiblility
- 國際化:Internationalization
其功能十分強大,這裡我們隻關注 XML布局檢查,可以通過AS的 Analyze->Inspect Code 來配置檢查的範圍。
點選OK後,就會出現所有問題的采集,以及每個問題種類的個數:
通過點選每一項,我們就能知道哪裡出現了問題,或者說哪裡的代碼/資源檔案沒有被引用到。
我們可以通過在 Setting -> Editor-> Inspections 中配置Lint檢查的範圍。
2.3 布局優化方法
布局優化的方法有很多,主要包括 合理運用布局、Inculde、Merge和ViewStub。
1.合理運用布局
這個比較籠統。舉個例子:
在布局很複雜時,合理運用 RelativeLayout可以來替代 LinearLayout,能減少很多層布局。
但是如果嵌套很多,使用 LinearLayout,性能要比 RelativeLayout要好。
這個還是要實際開發中,我們借助 Hierarchy Viewer等工具去判斷。
2.用Include來進行布局的複用
這個比較常見。我們在項目中經常會好多個頁面用到同一個View,比如說頂部欄、底部欄。我們就可以使用
<include>
标簽來進行複用。
3.用Merge标簽去除多餘層級
Merge意味着合并,在合适的場景使用Merge标簽可以減少多餘的層級。
<Merge>
一般和
<include>
複用。 不如這麼說,使用include并沒有怎麼照顧到性能而更多的是代碼的複用性。
真正讓
<include>
在性能上有意義的就是 Merge标簽。具體用法之前講過,這裡不講了。
merge标簽最好是替代 FrameLayout或者布局方向一緻的LinearLayout
4.使用ViewStub來提高加載速度
在XML中我們有時候會用到 GONE或者INVISIBLE 來隐藏一個View,但是這樣其實效率不高,因為系統仍然會解析他們。
可以使用
ViewStub
來解決這一個問題。ViewStub是一個輕量級的View,不可見且不占據布局位置。
當ViewStub調用
inflate()
或設定可見時,系統會加載 ViewStub指定的布局,然後将這個布局添加到ViewStub中,在對ViewStub調用 inflate方法或者設定可見之前,它是不占布局空間和系統資源的。它主要的目的是為目标視圖占用一個位置。
是以使用ViewStub可以調高界面初始化的性能,進而提高界面的加載速度。
我們在xml中代碼加入:
...
<ViewStub
android:id="@+id/viewstub"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout="@layout/titlebar"/>
...
然後再代碼中使用:
= findViewById(R.id.viewstub);
viewstub.infate();
viewstub.setVisibility(View.VISIBLE);
使用的時候要注意三點:
- ViewStub隻能被加載一次,加載後ViewStub對象會被置空,這樣在ViewStub引用的布局被加載後,就不能用ViewStub來控制引用的布局了。是以如果一個控件需要不斷地顯示和隐藏,還是要使用 View的Visiblity屬性
- ViewStub不能嵌套Merge标簽。
- ViewStub操作的是布局檔案,如果隻是想操作具體的View,還是要使用View的Visiblity屬性。
2.4 避免GPU過度繪制
過度繪制的概念:
如果一個像素在同一幀内被繪制了多次,進而浪費了GPU和CPU的資源,這就是過度繪制。
過度繪制的原因:
- 在XML布局中,空間有重疊,而且都有設定背景
- View的onDraw() 在同一區域繪制多次。
在實際項目中,要避免過度繪制其實是很難的,但是過多的過度繪制會浪費很多資源,并且導緻性能的問題,是以避免過度繪制是十分有必要的。我們可以使用Android系統中自帶的工具來檢測過度繪制,首先系統要4.1以上,然後去開發者選項中找 “調試GPU過度繪制選項”,就可以進入到GPU繪制模式:
這時,螢幕會出現多種顔色:
-
白色
沒有過度繪制,每個像素在螢幕上隻繪制了一次
-
藍色
一次過度繪制,…兩次
-
綠色
二次過度繪制,…三次
-
粉色
三次過度繪制,…四次
-
紅色
每個像素被繪制了五次或五次以上。
- 移除不需要的background
- 在自定義的View的 onDraw(),用
來指定繪制的區域,防止重疊的元件發生過度繪制。canvas.clipRect