以前在項目中作過一些Android卡頓以及性能優化的工做,可是一直沒時間總結,趁着這段時間把這部分總結一下。html
在應用開發中若是留意到log的話有時候可能會發下下面的log信息:java
I/Choreographer(1200): Skipped 60 frames! The application may be doing too much work on its main thread.
複製代碼
在大部分Android平臺的設備上,Android系統是16ms刷新一次,也就是一秒鐘60幀。要達到這種刷新速度就要求在ui線程中處理的任務時間必需要小於16ms,若是ui線程中處理時間長,就會致使跳過幀的渲染,也就是致使界面看起來不流暢,卡頓。若是用戶點擊事件5s中沒反應就會致使ANR。python
即 Frame Rate,單位 fps,是指 gpu 生成幀的速率,60fps,Android中更幀率相關的類是SurfaceFlinger。android
SurfaceFlinger surfaceflinger做用是接受多個來源的圖形顯示數據,將他們合成,而後發送到顯示設備。好比打開應用,常見的有三層顯示,頂部的statusbar底部或者側面的導航欄以及應用的界面,每一個層是單獨更新和渲染,這些界面都是有surfaceflinger合成一個刷新到硬件顯示。 web
Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就普遍使用的技術,能夠簡單的把它認爲是一種定時中斷。而在Android 4.1(JB)中已經開始引入VSync機制,用來同步渲染,讓UI和SurfaceFlinger能夠按硬件產生的VSync節奏進行工做。shell
安卓系統中有 2 種 VSync 信號: 一、屏幕產生的硬件 VSync: 硬件 VSync 是一個脈衝信號,起到開關或觸發某種操做的做用。 二、由 SurfaceFlinger 將其轉成的軟件 Vsync 信號:經由 Binder 傳遞給 Choreographer。數據庫
除了Vsync的機制,Android還使用了多級緩衝的手段以優化UI流程度,例如雙緩衝(A+B),在顯示buffer A的數據時,CPU/GPU就開始在buffer B中準備下一幀數據:可是不能保證每一幀CPU、GPU都運行狀態良好,可能因爲資源搶佔等性能問題致使某一幀GPU掉鏈子,vsync信號到來時buffer B的數據還沒準備好,而此時Display又在顯示buffer A的數據,致使後面CPU/GPU沒有新的buffer着手準備數據,致使卡頓(jank)。 瀏覽器
從系統層面上看主要如下幾個方面的緣由會致使卡頓: 1. SurfaceFlinger 主線程耗時性能優化
SurfaceFlinger 負責 Surface 的合成 , 一旦 SurfaceFlinger 主線程調用超時 , 就會產生掉幀 . SurfaceFlinger 主線程耗時會也會致使 hwc service 和 crtc 不能及時完成, 也會阻塞應用的 binder 調用, 如 dequeueBuffer \ queueBuffer 等.網絡
2. 後臺活動進程太多致使系統繁忙
後臺進程活動太多,會致使系統很是繁忙, cpu \ io \ memory 等資源都會被佔用, 這時候很容易出現卡頓問題 , 這也是系統這邊常常會碰到的問題。 dumpsys cpuinfo 能夠查看一段時間內 cpu 的使用狀況:
當線程爲 Runnable 狀態的時候 , 調度器若是遲遲不能對齊進行調度 , 那麼就會產生長時間的 Runnable 線程狀態 , 致使錯過 Vsync 而產生流暢性問題。
四、System 鎖
system_server 的 AMS 鎖和 WMS 鎖 , 在系統異常的狀況下 , 會變得很是嚴重 , 以下圖所示 , 許多系統的關鍵任務都被阻塞 , 等待鎖的釋放 , 這時候若是有 App 發來的 Binder 請求帶鎖 , 那麼也會進入等待狀態 , 這時候 App 就會產生性能問題 ; 若是此時作 Window 動畫 , 那麼 system_server 的這些鎖也會致使窗口動畫卡頓
Android P 修改了 Layer 的計算方法 , 把這部分放到了 SurfaceFlinger 主線程去執行, 若是後臺 Layer 過多, 就會致使 SurfaceFlinger 在執行 rebuildLayerStacks 的時候耗時 , 致使 SurfaceFlinger 主線程執行時間過長。
一、主線程執行時間長 主線程執行 Input \ Animation \ Measure \ Layout \ Draw \ decodeBitmap 等操做超時都會致使卡頓 。
二、主線程 Binder 耗時
Activity resume 的時候, 與 AMS 通訊要持有 AMS 鎖, 這時候若是碰到後臺比較繁忙的時候, 等鎖操做就會比較耗時, 致使部分場景由於這個卡頓, 好比多任務手勢操做。
三、WebView 性能不足
應用裏面涉及到 WebView 的時候, 若是頁面比較複雜, WebView 的性能就會比較差, 從而形成卡頓
四、幀率與刷新率不匹配
若是屏幕幀率和系統的 fps 不相符 , 那麼有可能會致使畫面不是那麼順暢. 好比使用 90 Hz 的屏幕搭配 60 fps 的動畫。
卡頓檢測可使用如下多種方法同時進行: 一、使用dumpsys gfxinfo 二、使用Systrace獲取相關信息 三、使用LayoutInspect 檢測佈局層次 四、使用BlockCanary 五、利用Choreographer。 六、使用嚴格模式(StrictMode )。
在開發過程當中發現有卡頓發生時可使用下面的命令來獲取卡頓相關的信息:
adb shell dumpsys gfxinfo [PACKAGE_NAME]
複製代碼
輸入這個命令後可能會打印下面的信息:
Applications Graphics Acceleration Info:
Uptime: 102809662 Realtime: 196891968
** Graphics info for pid 31148 [com.android.settings] **
Stats since: 524615985046231ns
Total frames rendered: 8325
Janky frames: 729 (8.76%)
90th percentile: 13ms
95th percentile: 20ms
99th percentile: 73ms
Number Missed Vsync: 294
Number High input latency: 47
Number Slow UI thread: 502
Number Slow bitmap uploads: 44
Number Slow issue draw commands: 135
複製代碼
上面參數說明:
Graphics info for pid 31148 [com.android.settings]: 代表當前dump的爲設置界面的幀信息,pid爲31148 Total frames rendered: 8325 本次dump蒐集了8325幀的信息
Janky frames :729 (8.76%)出現卡頓的幀數有729幀,佔8.76%
Number Missed Vsync: 294 垂直同步失敗的幀
Number Slow UI thread: 502 因UI線程上的工做致使超時的幀數
Number Slow bitmap uploads: 44 因bitmap的加載耗時的幀數
Number Slow issue draw commands: 135 因繪製致使耗時的幀數
上面使用的dumpsys是能發現問題或者判斷問題的嚴重性,但沒法定位真正的緣由。若是要定位緣由,應當配合systrace工具使用。
systrace使用
Systrace能夠幫助分析應用是如何設備上運行起來的,它將系統和應用程序線程集中在一個共同的時間軸上,分析systrace的第一步須要在程序運行的時間段中抓取trace log,在抓取到的trace文件中,包含了這段時間中想要的關鍵信息,交互狀況。
Android studio中使用systrace
一、在android設備的 設置 -- 開發者選項 -- 監控 -- 開啓traces。 二、選擇要追中的類別,而且點擊肯定。
完成以上配置後,開始抓trace文件
$ python systrace.py --cpu-freq --cpu-load --time=10 -o mytracefile.html
複製代碼
分析trace文件 抓到trace.html文件後,經過web瀏覽器打開
檢查Frames 每一個應用程序都有一排表明渲染幀的圓圈,一般爲綠色,若是繪製的時間超過16.6毫秒則顯示黃色或紅色。經過「W」鍵查看幀。
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
...
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Trace.beginSection("MyAdapter.onCreateViewHolder");
MyViewHolder myViewHolder;
try {
myViewHolder = MyViewHolder.newInstance(parent);
} finally {
Trace.endSection();
}
return myViewHolder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Trace.beginSection("MyAdapter.onBindViewHolder");
try {
try {
Trace.beginSection("MyAdapter.queryDatabase");
RowItem rowItem = queryDatabase(position);
mDataset.add(rowItem);
} finally {
Trace.endSection();
}
holder.bind(mDataset.get(position));
} finally {
Trace.endSection();
}
}
…
}
複製代碼
BlockCanary是國內開發者MarkZhai開發的一套性能監控組件,它對主線程操做進行了徹底透明的監控,並能輸出有效的信息,幫助開發分析、定位到問題所在,迅速優化應用。 其特色有: 一、非侵入式,簡單的兩行就打開監控,不須要處處打點,破壞代碼優雅性。 二、精準,輸出的信息能夠幫助定位到問題所在(精確到行),不須要像Logcat同樣,慢慢去找。 三、目前包括了核心監控輸出文件,以及UI顯示卡頓信息功能
BlockCanary基本原理
android應用程序只有一個主線程ActivityThread,這個主線程會建立一個Looper(Looper.prepare),而Looper又會關聯一個MessageQueue,主線程Looper會在應用的生命週期內不斷輪詢(Looper.loop),從MessageQueue取出Message 更新UI。
public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
複製代碼
BlockCanary主要是檢測msg.target.dispatchMessage(msg);
以前的>>>>> Dispatching to
和以後的<<<<< Finished to
的間隔時間。 應用發生卡頓,必定是在dispatchMessage中執行了耗時操做。經過給主線程的Looper設置一個Printer,打點統計dispatchMessage方法執行的時間,若是超出閥值,表示發生卡頓,則dump出各類信息,提供開發者分析性能瓶頸。
Android 主線程運行的本質,其實就是 Message 的處理過程,咱們的各類操做,包括每一幀的渲染操做 ,都是經過 Message 的形式發給主線程的 MessageQueue ,MessageQueue 處理完消息繼續等下一個消息。
Choreographer 兩個主要做用
一、承上:負責接收和處理 App 的各類更新消息和回調,等到 Vsync 到來的時候統一處理。好比集中處理 Input(主要是 Input 事件的處理) 、Animation(動畫相關)、Traversal(包括 measure、layout、draw 等操做) ,判斷卡頓掉幀狀況,記錄 CallBack 耗時等。
二、啓下:負責請求和接收 Vsync 信號。接收 Vsync 事件回調(經過 FrameDisplayEventReceiver.onVsync );請求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .
使用Choreographer 計算幀率
Choreographer 處理繪製的邏輯核心在 Choreographer.doFrame 函數中,從下圖能夠看到,FrameDisplayEventReceiver.onVsync post 了本身,其 run 方法直接調用了 doFrame 開始一幀的邏輯處理:
public class MyFrameCallback implements Choreographer.FrameCallback {
private String TAG = "性能檢測";
private long lastTime = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (lastTime == 0) {
//代碼第一次初始化。不作檢測統計。
lastTime = frameTimeNanos;
} else {
long times = (frameTimeNanos - lastTime) / 1000000;
int frames = (int) (times / 16);
if (times > 16) {
Log.w(TAG, "UI線程超時(超過16ms):" + times + "ms" + " , 丟幀:" + frames);
}
lastTime = frameTimeNanos;
}
Choreographer.getInstance().postFrameCallback(mFrameCallback);
}
}
複製代碼
由上面的分析可知對象分配、垃圾回收(GC)、線程調度以及Binder調用 是Android系統中常見的卡頓緣由,所以卡頓優化主要如下幾種方法,更多的要結合具體的應用來進行:
一、佈局優化
二、減小主線程耗時操做
三、減小過分繪製 過分繪製是同一個像素點上被屢次繪製,減小過分繪製通常減小布局背景疊加等方式,以下圖所示右邊是過分繪製的圖片。
五、對象分配和回收優化
自從Android引入 ART 而且在Android 5.0上成爲默認的運行時以後,對象分配和垃圾回收(GC)形成的卡頓已經顯著下降了,可是因爲對象分配和GC有額外的開銷,它依然又可能使線程負載太重。 在一個調用不頻繁的地方(好比按鈕點擊)分配對象是沒有問題的,但若是在在一個被頻繁調用的緊密的循環裏,就須要避免對象分配來下降GC的壓力。
一、source.android.google.cn/devices/gra… 二、www.bradcypert.com/what-is-and… 三、ashishb.net/tech/demyst… 四、devblogs.microsoft.com/xamarin/tip… 五、developer.android.com/training/te… 六、www.androidperformance.com/2019/09/05/… 七、developer.android.com/tools/help/… 八、zhuanlan.zhihu.com/p/87954949