Systrace 流暢性實戰 1 :瞭解卡頓原理

當咱們說 流暢度 的時候,咱們說的是什麼?不一樣的人對流暢性(卡頓掉幀)有不一樣的理解,對卡頓閾值也有不一樣的感知,因此有必要在開始這個系列文章以前,先把涉及到的內容說清楚,防止出現不一樣的理解,也方便你們帶着問題去看這幾篇問題,下面是一些基本的說明java

  1. 對手機用戶來講,卡頓包含了不少場景,好比在 滑動列表的時候掉幀應用啓動白屏過長點擊電源鍵亮屏慢界面操做沒有反應而後閃退點擊圖標沒有響應窗口動畫不連貫、滑動不跟手、重啓手機進入桌面卡頓 等場景,這些場景跟咱們開發人員所理解的卡頓還有點不同,開發人員會更加細分去分析這些問題,這是開發人員和用戶之間的一個認知差別,這一點在處理用戶(或者測試人員)的問題反饋的時候尤爲須要注意
  2. 對開發人員來講,上面的場景包括了 流暢度(滑動列表的時候掉幀、窗口動畫不連貫、重啓手機進入桌面卡頓)、響應速度(應用啓動白屏過長、點擊電源鍵亮屏慢、滑動不跟手)、穩定性(界面操做沒有反應而後閃退、點擊圖標沒有響應)這三個大的分類。之因此這麼分類,是由於每一種分類都有不太同樣的分析方法和步驟,快速分辨問題是屬於哪一類很重要
  3. 在技術上來講,流暢度、響應速度、穩定性(ANR)這三類之因此用戶感知都是卡頓,是由於這三類問題產生的原理是一致的,都是因爲主線程的 Message 在執行任務的時候超時,根據不一樣的超時閾值來進行劃分而已,因此要理解這些問題,須要對系統的一些基本的運行機制有必定的瞭解,本文會介紹一些基本的運行機制
  4. 流暢性這個系列主要是分析流暢度相關的問題,響應速度和穩定性會有專門的文章介紹,在理解了流暢性相關的內容以後,再去分析響應速度和穩定性問題會事半功倍
  5. 流暢性這個系列主要是講如何使用 Systrace (Perfetto) 工具去分析,之因此 Systrace 爲切入點,是由於影響流暢度的因素不少,有 App 自身的緣由、也有系統的緣由。而 (Perfetto) 工具能夠從一個整機運行的角度來展現問題發生的過程,方便咱們去初步定位問題

Systrace 流暢性實戰目前包括下面三篇android

  1. Systrace 流暢性實戰 1 :瞭解卡頓原理
  2. Systrace 流暢性實戰 2 :案例分析: MIUI 桌面滑動卡頓分析
  3. Systrace 流暢性實戰 3 :卡頓分析過程當中的一些疑問

Systrace (Perfetto) 工具的基本使用若是還不是很熟悉,那麼須要優先去補一下 Systrace 基礎知識系列git

瞭解卡頓原理

卡頓現象及影響

如文章開頭所述,本文主要是分析流暢度相關的問題。流暢度是一個定義,咱們評價一個場景的流暢度的時候,每每會使用 fps 來表示。好比 60 fps,意思是每秒畫面更新 60 次;120 fps,意思是每秒畫面更新 120 次。若是 120 fps 的狀況下,每秒畫面只更新了 110 次(連續動畫的過程),這種狀況咱們就稱之爲掉幀,其表現就是卡頓,fps 對應的也從 120 下降到了 110 ,這些均可以被精確地監控到github

同時掉幀幀的緣由很是多,有 APP 自己的問題,有系統緣由致使卡頓的,也有硬件層的、整機卡的,這個能夠參考下面四篇文章性能優化

  1. Android 中的卡頓丟幀緣由概述 - 方法論
  2. Android 中的卡頓丟幀緣由概述 - 系統篇
  3. Android 中的卡頓丟幀緣由概述 - 應用篇
  4. Android 中的卡頓丟幀緣由概述 - 低內存篇

用戶在使用手機的過程當中,卡頓是最容易被感覺到的markdown

  1. 偶爾出現的小卡頓會下降用戶的使用體驗,好比刷微博的時候卡了一下,好比返回桌面動畫卡頓這種
  2. 整機出現卡頓的則會讓手機沒法使用
  3. 如今的高幀率時代,若是用戶習慣了 120 fps ,在用戶比較容易感知的場景下忽然切換到 60 fps,用戶也會有明顯的感知,並以爲出現了卡頓

因此不論是應用仍是系統,都應該儘可能避免出現卡頓,發現的卡頓問題最好優先進行解決數據結構

卡頓定義

應用一幀渲染的總體流程

爲了知道卡頓是如何發生的,咱們須要知道應用主線程的一幀是如何工做的app

從執行順序的角度來看

從 Choreographer 收到 Vsync 開始,到 SurfaceFlinger/HWC 合成一幀結束(後面還包含屏幕顯示部分,不過是硬件相關,這裏就不列出來了)ide

從 Systrace 的角度來看

上面的流程圖從 Systrace (Perfetto)的角度來看會更加直觀 函數

具體的流程參考上面兩個圖以及代碼就會很清楚了,上述總體流程中,任何一個步驟超時都有可能致使卡頓,因此分析卡頓問題,須要從多個層面來進行分析,好比應用主線程、渲染線程、SystemServer 進程、SurfaceFlinger 進程、Linux 區域等

卡頓定義

我對卡頓的定義是:穩定幀率輸出的畫面出現幾幀沒有繪製 對應的應用單詞是 Smooth VS Jank

好比下圖中,App 主線程有在正常繪製的時候(一般是作動畫或者列表滑動),有一幀沒有繪製,那麼咱們認爲這一幀有可能會致使卡頓(這裏說的是有可能,因爲 Triple Buffer 的存在,這裏也有可能不掉幀)

下面從三個方面定義卡頓

  1. 從現象上來講,在 App 連續的動畫播放或者手指滑動列表時(關鍵是連續),若是連續 2 幀或者 2 幀以上,應用的畫面都沒有變化,那麼咱們認爲這裏發生了卡頓
  2. 從 SurfaceFlinger 的角度來講,在 App 連續的動畫播放或者手指滑動列表時(關鍵是連續),若是有一個 Vsync 到來的時候 ,App 沒有能夠用來合成的 Buffer,那麼這個 Vsync 週期 SurfaceFlinger 就不會走合成的邏輯(或者是去合成其餘的 Layer),那麼這一幀就會顯示 App 的上一幀的畫面,咱們認爲這裏發生了卡頓
  3. 從 App 的角度來看,若是渲染線程在一個 Vsync 週期內沒有 queueBuffer 到 SurfaceFlinger 中 App 對應的 BufferQueue 中,那麼咱們認爲這裏發生了卡頓

這裏沒有提到應用主線程,是由於主線程耗時長通常會間接致使渲染線程出現延遲,加大渲染線程執行超時的風險,從而引發卡頓;並且應用致使的卡頓緣由裏面,大部分都是主線程耗時過長致使的

卡頓還要區分是否是邏輯卡頓邏輯卡頓指的是一幀的渲染流程都是沒有問題的,也有對應的 Buffer 給到 SurfaceFlinger 去合成,可是這個 App Buffer 的內容和上一幀 App Buffer 相同(或者基本相同,肉眼沒法分辨),那麼用戶看來就是連續兩幀顯示了相同的內容。這裏通常來講咱們也認爲是發生了卡頓(不過還要區分具體的狀況);邏輯卡頓主要是應用自身的代碼邏輯形成的

系統運行機制簡介

因爲卡頓的緣由比較多,若是要分析卡頓問題,首先得對 Android 系統運行的機制有必定的瞭解,下面簡單介紹一下分析卡頓問題須要瞭解的系統運行機制:

  1. App 主線程運行原理
  2. Message、Handler、MessageQueue、Looper 機制
  3. 屏幕刷新機制和 Vsync
  4. Choreogrepher 機制
  5. Buffer 流程和 TripleBuffer
  6. Input 流程

系統機制 - App 主線程運行原理

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,處理完成以後,若是沒有其餘的 Message 須要處理,那麼主線程就會進入休眠阻塞狀態繼續等待

從下圖能夠看到 ,Android Message 機制的核心就是四個:HandlerLooperMessageQueueMessage

網上有不少關於 Message 機制代碼細節的分析,因此這裏只是簡單介紹 Message 機制的四個核心組件的做用

  1. Handler : Handler 主要是用來處理 Message,應用能夠在任何線程建立 Handler,只要在建立的時候指定對應的 Looper 便可,若是不指定,默認是在當前 Thread 對應的 Looper
  2. Looper : Looper 能夠當作是一個循環器,其 loop 方法開啓後,不斷地從 MessageQueue 中獲取 Message,對 Message 進行 Delivery 和 Dispatch,最終發給對應的 Handler 去處理。因爲 Looper 中應用能夠在 Message 處理先後插入本身的 printer,因此不少 APM 工具都會使用這個做爲性能監控的一個切入點,具體能夠參考 Tencent-Matrix 和 BlockCanary
  3. MessageQueue:MessageQueue 入上圖所示,就是一個管理器,隊列中是 Message,在沒有 Message 的時候,MessageQueue 藉助 Linux 的 nativePoll 機制,阻塞等待,直到有 Message 進入隊列
  4. Message:Message 是傳遞消息的對象,其內部包含了要傳遞的內容,最經常使用的包括 what、arg、callback 等

從第一節 App 主線程運行原理可知,ActivityThread 的就是利用 Message 機制,處理 App 各個生命週期和組件各個生命週期的函數

系統機制 - 屏幕刷新機制 和 Vsync

首先咱們須要知道什麼是屏幕刷新率,簡單來講,屏幕刷新率是一個硬件的概念,是說屏幕這個硬件刷新畫面的頻率:舉例來講,60Hz 刷新率意思是:這個屏幕在 1 秒內,會刷新顯示內容 60 次;那麼對應的,90Hz 是說在 1 秒內刷新顯示內容 90 次

與屏幕刷新率對應的,FPS 是一個軟件的概念,與屏幕刷新率這個硬件概念要區分開,FPS 是由軟件系統決定的 :FPS 是 Frame Per Second 的縮寫,意思是每秒產生畫面的個數。舉例來講,60FPS 指的是每秒產生 60 個畫面;90FPS 指的是每秒產生 90 個畫面

VSync 是垂直同期( Vertical Synchronization )的簡稱。基本的思路是將你的 FPS 和顯示器的刷新率同期起來。其目的是避免一種稱之爲」撕裂」的現象.

  1. 60 fps 的系統 , 1s 內須要生成 60 個可供顯示的 Frame , 也就是說繪製一幀須要 16.67ms ( 1/60 ) , 纔會不掉幀 ( FrameMiss ).
  2. 90 fps 的系統 , 1s 內生成 90 個可供顯示的 Frame , 也就是說繪製一幀須要 11.11ms ( 1/90 ) , 纔不會掉幀 ( FrameMiss ).

通常來講,屏幕刷新率是由屏幕控制的,FPS 則是由 Vsync 來控制的,在實際的使用場景裏面,屏幕刷新率和 FPS 通常都是一一對應的,具體能夠參考下面兩篇文章:

  1. Android 新的流暢體驗,90Hz 漫談
  2. Android Systrace 基礎知識 - Vsync 解讀

系統機制 - Choreographer

上一節講到 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 渲染鏈路中承上啓下的角色

  1. 承上:負責接收和處理 App 的各類更新消息和回調,等到 Vsync 到來的時候統一處理。好比集中處理 Input(主要是 Input 事件的處理) 、Animation(動畫相關)、Traversal(包括 measure、layout、draw 等操做) ,判斷卡頓掉幀狀況,記錄 CallBack 耗時等
  2. 啓下:負責請求和接收 Vsync 信號。接收 Vsync 事件回調(經過 FrameDisplayEventReceiver.onVsync );請求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .

下圖就是 Vsync 信號到來的時候,Choreographer 藉助 Message 機制開始一幀的繪製工做流程圖

這部分詳細的流程能夠看 Android 基於 Choreographer 的渲染機制詳解 這篇文章

系統機制 - Buffer 流程和 TripleBuffer

BufferQueue 是一個生產者(Producer)-消費者(Consumer)模型中的數據結構,通常來講,消費者(Consumer) 建立 BufferQueue,而生產者(Producer) 通常不和 BufferQueue 在同一個進程裏面

在 Android App 的渲染流程裏面,App 就是個生產者(Producer) ,而 SurfaceFlinger 是一個消費者(Consumer),因此上面的流程就能夠翻譯爲

  1. 當 App 須要 Buffer 時,它經過調用 dequeueBuffer()並指定 Buffer 的寬度,高度,像素格式和使用標誌,從 BufferQueue 請求釋放 Buffer
  2. App 能夠用 cpu 進行渲染也能夠調用用 gpu 來進行渲染,渲染完成後,經過調用 queueBuffer()將緩衝區返回到 App 對應的 BufferQueue(若是是 gpu 渲染的話,這裏還有個 gpu 處理的過程,因此這個 Buffer 不會立刻可用,須要等 GPU 渲染完成)
  3. SurfaceFlinger 在收到 Vsync 信號以後,開始準備合成,使用 acquireBuffer()獲取 App 對應的 BufferQueue 中的 Buffer 並進行合成操做
  4. 合成結束後,SurfaceFlinger 將經過調用 releaseBuffer()將 Buffer 返回到 App 對應的 BufferQueue

知道了 Buffer 流轉的過程,下面須要說明的是,在目前的大部分系統上,每一個應用都有三個 Buffer 輪轉使用,來減小因爲 Buffer 在某個流程耗時過長致使應用無 Buffer 可用而出現卡頓狀況

下圖是雙 Buffer 和 三 Buffer 的一個對比圖

三個 Buffer 的好處以下

  1. 緩解掉幀 :從上圖 Double Buffer 和 Triple Buffer 的對比圖能夠看到,在這種狀況下(出現連續主線程超時),三個 Buffer 的輪轉有助於緩解掉幀出現的次數(從掉幀兩次 -> 只掉幀一次)。,App 主線程超時不必定會致使掉幀,因爲 Triple Buffer 的存在,部分 App 端的掉幀(主要是因爲 GPU 致使),到 SurfaceFlinger 這裏未必是掉幀,這是看 Systrace 的時候須要注意的一個點
  2. 減小主線程和渲染線程等待時間 :雙 Buffer 的輪轉,App 主線程有時候必需要等待 SurfaceFlinger(消費者)釋放 Buffer 後,才能獲取 Buffer 進行生產,這時候就有個問題,如今大部分手機 SurfaceFlinger 和 App 同時收到 Vsync 信號,若是出現 App 主線程等待 SurfaceFlinger(消費者)釋放 Buffer,那麼勢必會讓 App 主線程的執行時間延後
  3. 下降 GPU 和 SurfaceFlinger 瓶頸 :這個比較好理解,雙 Buffer 的時候,App 生產的 Buffer 必需要及時拿去讓 GPU 進行渲染,而後 SurfaceFlinger 才能進行合成,一旦 GPU 超時,就很容易出現 SurfaceFlinger 沒法及時合成而致使掉幀;在三個 Buffer 輪轉的時候,App 生產的 Buffer 能夠及早進入 BufferQueue,讓 GPU 去進行渲染(由於不須要等待,就算這裏積累了 2 個 Buffer,下下一幀纔去合成,這裏也會提前進行,而不是在真正使用以前去匆忙讓 GPU 去渲染),另外 SurfaceFlinger 自己的負載若是比較大,三個 Buffer 輪轉也會有效下降 dequeueBuffer 的等待時間

壞處就是 Buffer 多了會佔用內存

這部分詳細的流程能夠看 Android Systrace 基礎知識 - Triple Buffer 解讀 這篇文章

系統機制 - Input 流程

Android 系統是由事件驅動的,而 input 是最多見的事件之一,用戶的點擊、滑動、長按等操做,都屬於 input 事件驅動,其中的核心就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 SystemServer 裏面的兩個 Native 線程,負責讀取和分發 Input 事件,咱們分析 Systrace 的 Input 事件流,首先是找到這裏。

  1. InputReader 負責從 EventHub 裏面把 Input 事件讀取出來,而後交給 InputDispatcher 進行事件分發
  2. InputDispatcher 在拿到 InputReader 獲取的事件以後,對事件進行包裝和分發 (也就是發給對應的)
  3. OutboundQueue 裏面放的是即將要被派發給對應 AppConnection 的事件
  4. WaitQueue 裏面記錄的是已經派發給 AppConnection 可是 App 還在處理沒有返回處理成功的事件
  5. PendingInputEventQueue 裏面記錄的是 App 須要處理的 Input 事件,這裏能夠看到已經到了應用進程
  6. deliverInputEvent 標識 App UI Thread 被 Input 事件喚醒
  7. InputResponse 標識 Input 事件區域,這裏能夠看到一個 Input_Down 事件 + 若干個 Input_Move 事件 + 一個 Input_Up 事件的處理階段都被算到了這裏
  8. App 響應 Input 事件 : 這裏是滑動而後鬆手,也就是咱們熟悉的桌面滑動的操做,桌面隨着手指的滑動更新畫面,鬆手後觸發 Fling 繼續滑動,從 Systrace 就能夠看到整個事件的流程

上面流程對應的 Systrace 以下

這部分詳細的流程能夠看 Android Systrace 基礎知識 - Input 解讀 這篇文章

系列文章

  1. Systrace 流暢性實戰 1 :瞭解卡頓原理
  2. Systrace 流暢性實戰 2 :案例分析: MIUI 桌面滑動卡頓分析
  3. Systrace 流暢性實戰 3 :卡頓分析過程當中的一些疑問

附件

附件已經上傳到了 Github 上,能夠自行下載:github.com/Gracker/Sys…

  1. xiaomi_launcher.zip : 桌面滑動卡頓的 Systrace 文件,此次案例主要是分析這個 Systrace 文件
  2. xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑動的 Systrace 文件
  3. oppo_launcher_scroll.zip :對比文件

關於我 && 博客

  1. 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .
  2. 博客內容導航
  3. 優秀博客文章記錄 - Android 性能優化必知必會

一我的能夠走的更快 , 一羣人能夠走的更遠

相關文章
相關標籤/搜索