當咱們說 流暢度 的時候,咱們說的是什麼?不一樣的人對流暢性(卡頓掉幀)有不一樣的理解,對卡頓閾值也有不一樣的感知,因此有必要在開始這個系列文章以前,先把涉及到的內容說清楚,防止出現不一樣的理解,也方便你們帶着問題去看這幾篇問題,下面是一些基本的說明java
Systrace 流暢性實戰目前包括下面三篇android
Systrace (Perfetto) 工具的基本使用若是還不是很熟悉,那麼須要優先去補一下 Systrace 基礎知識系列git
如文章開頭所述,本文主要是分析流暢度相關的問題。流暢度是一個定義,咱們評價一個場景的流暢度的時候,每每會使用 fps 來表示。好比 60 fps,意思是每秒畫面更新 60 次;120 fps,意思是每秒畫面更新 120 次。若是 120 fps 的狀況下,每秒畫面只更新了 110 次(連續動畫的過程),這種狀況咱們就稱之爲掉幀,其表現就是卡頓,fps 對應的也從 120 下降到了 110 ,這些均可以被精確地監控到github
同時掉幀幀的緣由很是多,有 APP 自己的問題,有系統緣由致使卡頓的,也有硬件層的、整機卡的,這個能夠參考下面四篇文章性能優化
用戶在使用手機的過程當中,卡頓是最容易被感覺到的markdown
因此不論是應用仍是系統,都應該儘可能避免出現卡頓,發現的卡頓問題最好優先進行解決數據結構
爲了知道卡頓是如何發生的,咱們須要知道應用主線程的一幀是如何工做的app
從 Choreographer 收到 Vsync 開始,到 SurfaceFlinger/HWC 合成一幀結束(後面還包含屏幕顯示部分,不過是硬件相關,這裏就不列出來了)ide
上面的流程圖從 Systrace (Perfetto)的角度來看會更加直觀 函數
具體的流程參考上面兩個圖以及代碼就會很清楚了,上述總體流程中,任何一個步驟超時都有可能致使卡頓,因此分析卡頓問題,須要從多個層面來進行分析,好比應用主線程、渲染線程、SystemServer 進程、SurfaceFlinger 進程、Linux 區域等
我對卡頓的定義是:穩定幀率輸出的畫面出現幾幀沒有繪製 對應的應用單詞是 Smooth VS Jank
好比下圖中,App 主線程有在正常繪製的時候(一般是作動畫或者列表滑動),有一幀沒有繪製,那麼咱們認爲這一幀有可能會致使卡頓(這裏說的是有可能,因爲 Triple Buffer 的存在,這裏也有可能不掉幀)
下面從三個方面定義卡頓
這裏沒有提到應用主線程,是由於主線程耗時長通常會間接致使渲染線程出現延遲,加大渲染線程執行超時的風險,從而引發卡頓;並且應用致使的卡頓緣由裏面,大部分都是主線程耗時過長致使的
卡頓還要區分是否是邏輯卡頓,邏輯卡頓指的是一幀的渲染流程都是沒有問題的,也有對應的 Buffer 給到 SurfaceFlinger 去合成,可是這個 App Buffer 的內容和上一幀 App Buffer 相同(或者基本相同,肉眼沒法分辨),那麼用戶看來就是連續兩幀顯示了相同的內容。這裏通常來講咱們也認爲是發生了卡頓(不過還要區分具體的狀況);邏輯卡頓主要是應用自身的代碼邏輯形成的
因爲卡頓的緣由比較多,若是要分析卡頓問題,首先得對 Android 系統運行的機制有必定的瞭解,下面簡單介紹一下分析卡頓問題須要瞭解的系統運行機制:
App 進程在建立的時候,Fork 完成後會調用 ActivityThread 的 main 方法,進行主線程的初始化工做
frameworks/base/core/java/android/app/ActivityThread.java public static void main(String[] args) {
......
// 建立 Looper、Handler、MessageQueue
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
// 開始準備接收消息
Looper.loop();
}
// 準備主線程的 Looper
frameworks/base/core/java/android/os/Looper.java public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
// prepare 方法中會建立一個 Looper 對象
frameworks/base/core/java/android/os/Looper.java private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 對象建立的時候,同時建立一個 MessageQueue
frameworks/base/core/java/android/os/Looper.java private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread()
}
複製代碼
主線程初始化完成後,主線程就有了完整的 Looper、MessageQueue、Handler,此時 ActivityThread 的 Handler 就能夠開始處理 Message,包括 Application、Activity、ContentProvider、Service、Broadcast 等組件的生命週期函數,都會以 Message 的形式,在主線程按照順序處理,這就是 App 主線程的初始化和運行原理,部分處理的 Message 以下
frameworks/base/core/java/android/app/ActivityThread.java
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
複製代碼
上一節應用的主線程初始化完成後,主線程就進入阻塞狀態,等待 Message,一旦有 Message 發過來,主線程就會被喚醒,處理 Message,處理完成以後,若是沒有其餘的 Message 須要處理,那麼主線程就會進入休眠阻塞狀態繼續等待
從下圖能夠看到 ,Android Message 機制的核心就是四個:Handler、Looper、MessageQueue、Message
網上有不少關於 Message 機制代碼細節的分析,因此這裏只是簡單介紹 Message 機制的四個核心組件的做用
從第一節 App 主線程運行原理可知,ActivityThread 的就是利用 Message 機制,處理 App 各個生命週期和組件各個生命週期的函數
首先咱們須要知道什麼是屏幕刷新率,簡單來講,屏幕刷新率是一個硬件的概念,是說屏幕這個硬件刷新畫面的頻率:舉例來講,60Hz 刷新率意思是:這個屏幕在 1 秒內,會刷新顯示內容 60 次;那麼對應的,90Hz 是說在 1 秒內刷新顯示內容 90 次
與屏幕刷新率對應的,FPS 是一個軟件的概念,與屏幕刷新率這個硬件概念要區分開,FPS 是由軟件系統決定的 :FPS 是 Frame Per Second 的縮寫,意思是每秒產生畫面的個數。舉例來講,60FPS 指的是每秒產生 60 個畫面;90FPS 指的是每秒產生 90 個畫面
VSync 是垂直同期( Vertical Synchronization )的簡稱。基本的思路是將你的 FPS 和顯示器的刷新率同期起來。其目的是避免一種稱之爲」撕裂」的現象.
通常來講,屏幕刷新率是由屏幕控制的,FPS 則是由 Vsync 來控制的,在實際的使用場景裏面,屏幕刷新率和 FPS 通常都是一一對應的,具體能夠參考下面兩篇文章:
上一節講到 Vsync 控制 FPS,其實 Vsync 是經過 Choreographer 來控制應用刷新的頻率的
Choreographer 的引入,主要是配合 Vsync,給上層 App 的渲染提供一個穩定的 Message 處理的時機,也就是 Vsync 到來的時候 ,系統經過對 Vsync 信號週期的調整,來控制每一幀繪製操做的時機. 至於爲何 Vsync 週期選擇是 16.6ms (60 fps) ,是由於目前大部分手機的屏幕都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系統爲了配合屏幕的刷新頻率,將 Vsync 的週期也設置爲 16.6 ms,每隔 16.6 ms,Vsync 信號到來喚醒 Choreographer 來作 App 的繪製操做 ,若是每一個 Vsync 週期應用都能渲染完成,那麼應用的 fps 就是 60,給用戶的感受就是很是流暢,這就是引入 Choreographer 的主要做用
Choreographer 扮演 Android 渲染鏈路中承上啓下的角色
下圖就是 Vsync 信號到來的時候,Choreographer 藉助 Message 機制開始一幀的繪製工做流程圖
這部分詳細的流程能夠看 Android 基於 Choreographer 的渲染機制詳解 這篇文章
BufferQueue 是一個生產者(Producer)-消費者(Consumer)模型中的數據結構,通常來講,消費者(Consumer) 建立 BufferQueue,而生產者(Producer) 通常不和 BufferQueue 在同一個進程裏面
在 Android App 的渲染流程裏面,App 就是個生產者(Producer) ,而 SurfaceFlinger 是一個消費者(Consumer),因此上面的流程就能夠翻譯爲
知道了 Buffer 流轉的過程,下面須要說明的是,在目前的大部分系統上,每一個應用都有三個 Buffer 輪轉使用,來減小因爲 Buffer 在某個流程耗時過長致使應用無 Buffer 可用而出現卡頓狀況
下圖是雙 Buffer 和 三 Buffer 的一個對比圖
三個 Buffer 的好處以下
壞處就是 Buffer 多了會佔用內存
這部分詳細的流程能夠看 Android Systrace 基礎知識 - Triple Buffer 解讀 這篇文章
Android 系統是由事件驅動的,而 input 是最多見的事件之一,用戶的點擊、滑動、長按等操做,都屬於 input 事件驅動,其中的核心就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 SystemServer 裏面的兩個 Native 線程,負責讀取和分發 Input 事件,咱們分析 Systrace 的 Input 事件流,首先是找到這裏。
上面流程對應的 Systrace 以下
這部分詳細的流程能夠看 Android Systrace 基礎知識 - Input 解讀 這篇文章
附件已經上傳到了 Github 上,能夠自行下載:github.com/Gracker/Sys…
一我的能夠走的更快 , 一羣人能夠走的更遠