Systrace 基礎知識 - MainThread 和 RenderThread 解讀

本文是 Systrace 系列文章的第五篇,主要是是介紹 Android App 的 MainThread 和 RenderThread,也就是你們熟悉的主線程和渲染線程。文章會從 Systrace 的角度來看 MainThread 和 RenderThread 的工做流程,以及涉及到的相關知識:卡頓、軟件渲染、掉幀計算等html

本系列的目的是經過 Systrace 這個工具,從另一個角度來看待 Android 系統總體的運行,同時也從另一個角度來對 Framework 進行學習。也許你看了不少講 Framework 的文章,可是老是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你能夠理解的更深刻一些java

系列文章目錄

  1. Systrace 簡介
  2. Systrace 基礎知識 - Systrace 預備知識
  3. Systrace 基礎知識 - Why 60 fps ?
  4. Systrace 基礎知識 - SystemServer 解讀
  5. Systrace 基礎知識 - SurfaceFlinger 解讀
  6. Systrace 基礎知識 - Input 解讀
  7. Systrace 基礎知識 - Vsync 解讀
  8. Systrace 基礎知識 - MainThread 和 RenderThread 解讀
  9. Systrace 基礎知識 - Triple Buffer 解讀
  10. Systrace 基礎知識 - CPU Info 解讀
  11. Systrace 實戰 - 分析應用冷啓動時間問題
  12. Systrace 實戰 - 分析應用熱啓動時間問題
  13. Systrace 實戰 - 分析列表卡頓問題
  14. Systrace 實戰 - 分析窗口動畫卡頓問題
  15. Systrace 實戰 - 分析進程亂跑致使的性能問題
  16. Systrace 實戰 - 分析 IO 致使的性能問題
  17. Systrace 實戰 - 分析 Memory 致使的性能問題
  18. Systrace 實戰 - Systrace 與 MethodTrace 結合使用
  19. Systrace 實戰 - 分析硬件加速問題

正文

這裏以滑動列表爲例 ,咱們截取主線程和渲染線程一幀的工做流程(每一幀都會遵循這個流程,不過有的幀須要處理的事情多,有的幀須要處理的事情少) ,重點看 「UI Thread 」 和 RenderThread 這兩行android

圖片

這張圖對應的工做流程以下git

  1. 主線程處於 Sleep 狀態,等待 Vsync 信號
  2. Vsync 信號到來,主線程被喚醒,Choreographer 回調 FrameDisplayEventReceiver.onVsync 開始一幀的繪製
  3. 處理 App 這一幀的 Input 事件(若是有的話)
  4. 處理 App 這一幀的 Animation 事件(若是有的話)
  5. 處理 App 這一幀的 Traversal 事件(若是有的話)
  6. 主線程與渲染線程同步渲染數據,同步結束後,主線程結束一幀的繪製,能夠繼續處理下一個 Message(若是有的話,IdleHandler 若是不爲空,這時候也會觸發處理),或者進入 Sleep 狀態等待下一個 Vsync
  7. 渲染線程首先須要從 BufferQueue 裏面取一個 Buffer(dequeueBuffer) , 進行數據處理以後,調用 OpenGL 相關的函數,真正地進行渲染操做,而後將這個渲染好的 Buffer 還給 BufferQueue (queueBuffer) , SurfaceFlinger 在 Vsync-SF 到了以後,將全部準備好的 Buffer 取出進行合成(這個流程在講 SurfaceFlinger 的時候會提到)

上面這個流程在 Android 基於 Choreographer 的渲染機制詳解 這篇文章裏面已經介紹的很詳細了,包括每一幀的 doFrame 都在作什麼、卡頓計算的原理、APM 相關. 沒有看過這篇文章的同窗,建議先去掃一眼github

那麼這篇文章咱們主要從 Android 基於 Choreographer 的渲染機制詳解 這篇文章沒有講到的幾個點來入手,幫你更好地理解主線程和渲染線程數據庫

  1. 主線程的發展
  2. 主線程的建立
  3. 渲染線程的建立
  4. 主線程和渲染線程的分工
  5. 遊戲的主線程與渲染線程
  6. Flutter 的主線程和渲染線程

主線程的建立

Android App 的進程是基於 Linux 的,其管理也是基於 Linux 的進程管理機制,因此其建立也是調用了 fork 函數瀏覽器

frameworks/base/core/jni/com_android_internal_os_Zygote.cppbash

pid_t pid = fork();
複製代碼

Fork 出來的進程,咱們這裏能夠把他看作主線程,可是這個線程尚未和 Android 進行鏈接,因此沒法處理 Android App 的 Message ;因爲 Android App 線程運行基於消息機制 ,那麼這個 Fork 出來的主線程須要和 Android 的 Message 消息綁定,才能處理 Android App 的各類 Message網絡

這裏就引入了 ActivityThread ,確切的說,ActivityThread 應該起名叫 ProcessThread 更貼切一些。ActivityThread 鏈接了 Fork 出來的進程和 App 的 Message ,他們的通力配合組成了咱們熟知的 Android App 主線程。因此說 ActivityThread 其實並非一個 Thread,而是他初始化了 Message 機制所須要的 MessageQueue、Looper、Handler ,並且其 Handler 負責處理大部分 Message 消息,因此咱們習慣上以爲 ActivityThread 是主線程,其實他只是主線程的一個邏輯處理單元。app

ActivityThread 的建立

App 進程 fork 出來以後,回到 App 進程,查找 ActivityThread 的 Main函數

com/android/internal/os/ZygoteInit.java

static final Runnable childZygoteInit(
        int targetSdkVersion, String[] argv, ClassLoader classLoader) {
    RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
    return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader);
}
複製代碼

這裏的 startClass 就是 ActivityThread,找到以後調用,邏輯就到了 ActivityThread的main函數

android/app/ActivityThread.java

public static void main(String[] args) {
    //1. 初始化 Looper、MessageQueue
    Looper.prepareMainLooper();
    // 2. 初始化 ActivityThread
    ActivityThread thread = new ActivityThread();
    // 3. 主要是調用 AMS.attachApplicationLocked,同步進程信息,作一些初始化工做
    thread.attach(false, startSeq);
    // 4. 獲取主線程的 Handler,這裏是 H ,基本上 App 的 Message 都會在這個 Handler 裏面進行處理 
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    // 5. 初始化完成,Looper 開始工做
    Looper.loop();
}
複製代碼

註釋裏面都很清楚,這裏就不詳細說了,main 函數處理完成以後,主線程就算是正式上線開始工做,其 Systrace 流程以下:

圖片

ActivityThread 的功能

另外咱們常常說的,Android 四大組件都是運行在主線程上的,其實這裏也很好理解,看一下 ActivityThread 的 Handler 的 Message 就知道了

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 STOP_SERVICE            = 116;
    public static final int BIND_SERVICE            = 121;
    public static final int UNBIND_SERVICE          = 122;
    public static final int DUMP_SERVICE            = 123;
    public static final int REMOVE_PROVIDER         = 131;
    public static final int DISPATCH_PACKAGE_BROADCAST = 133;
    public static final int DUMP_PROVIDER           = 141;
    public static final int UNSTABLE_PROVIDER_DIED  = 142;
    public static final int INSTALL_PROVIDER        = 145;
    public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
}
複製代碼

能夠看到,進程建立、Activity 啓動、Service 的管理、Receiver 的管理、Provider 的管理這些都會在這裏處理,而後進到具體的 handleXXX

圖片

渲染線程的建立和發展

主線程講完了咱們來說渲染線程,渲染線程也就是 RenderThread ,最初的 Android 版本里面是沒有渲染線程的,渲染工做都是在主線程完成,使用的也都是 CPU ,調用的是 libSkia 這個庫,RenderThread 是在 Android Lollipop 中新加入的組件,負責承擔一部分以前主線程的渲染工做,減輕主線程的負擔

軟件繪製

咱們通常提到的硬件加速,指的就是 GPU 加速,這裏能夠理解爲用 RenderThread 調用 GPU 來進行渲染加速 。 硬件加速在目前的 Android 中是默認開啓的, 因此若是咱們什麼都不設置,那麼咱們的進程默認都會有主線程和渲染線程(有可見的內容)。咱們若是在 App 的 AndroidManifest 裏面,在 Application 標籤裏面加一個

android:hardwareAccelerated="false"
複製代碼

咱們就能夠關閉硬件加速,系統檢測到你這個 App 關閉了硬件加速,就不會初始化 RenderThread ,直接 cpu 調用 libSkia 來進行渲染。其 Systrace 的表現以下

圖片

與這篇文章開頭的開了硬件加速的那個圖對比,能夠看到主線程因爲要進行渲染工做,因此執行的時間邊長了,也更容易出現卡頓,同時幀與幀直接的空閒間隔也變短了,使得其餘 Message 的執行時間被壓縮

硬件加速繪製

正常狀況下,硬件加速是開啓的,主線程的 draw 函數並無真正的執行 drawCall ,而是把要 draw 的內容記錄到 DIsplayList 裏面,同步到 RenderThread 中,一旦同步完成,主線程就能夠被釋放出來作其餘的事情,RenderThread 則繼續進行渲染工做

圖片

渲染線程初始化

渲染線程初始化在真正須要 draw 內容的時候,通常咱們啓動一個 Activity ,在第一個 draw 執行的時候,會去檢測渲染線程是否初始化,若是沒有則去進行初始化

android/view/ViewRootImpl.java

mAttachInfo.mThreadedRenderer.initializeIfNeeded(
        mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
複製代碼

後續直接調用 draw

android/graphics/HardwareRenderer.java

mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    choreographer.mFrameInfo.markDrawStart();

    updateRootDisplayList(view, callbacks);

    if (attachInfo.mPendingAnimatingRenderNodes != null) {
        final int count = attachInfo.mPendingAnimatingRenderNodes.size();
        for (int i = 0; i < count; i++) {
            registerAnimatingRenderNode(
                    attachInfo.mPendingAnimatingRenderNodes.get(i));
        }
        attachInfo.mPendingAnimatingRenderNodes.clear();
        attachInfo.mPendingAnimatingRenderNodes = null;
    }

    int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
    if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
        setEnabled(false);
        attachInfo.mViewRootImpl.mSurface.release();
        attachInfo.mViewRootImpl.invalidate();
    }
    if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
        attachInfo.mViewRootImpl.invalidate();
    }
}
複製代碼

上面的 draw 只是更新 DIsplayList ,更新結束後,調用 syncAndDrawFrame ,通知渲染線程開始工做,主線程釋放。渲染線程的核心實如今 libhwui 庫裏面,其代碼位於 frameworks/base/libs/hwui

frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}
複製代碼

關於 RenderThread 的工做流程這裏就不細說了,後續會有專門的篇幅來說解這個,目前 hwui 這一塊的流程也有不少優秀的文章,你們能夠對照文章和源碼來看,其核心流程在 Systrace 上的表現以下:

圖片

主線程和渲染線程的分工

主線程負責處理進程 Message、處理 Input 事件、處理 Animation 邏輯、處理 Measure、Layout、Draw ,更新 DIsplayList ,可是不涉及 SurfaceFlinger 打交道;渲染線程負責渲染渲染相關的工做,一部分工做也是 CPU 來完成的,一部分操做是調用 OpenGL 函數來完成的

當啓動硬件加速後,在 Measure、Layout、Draw 的 Draw 這個環節,Android 使用 DisplayList 進行繪製而非直接使用 CPU 繪製每一幀。DisplayList 是一系列繪製操做的記錄,抽象爲 RenderNode 類,這樣間接的進行繪製操做的優勢以下

  1. DisplayList 能夠按需屢次繪製而無須同業務邏輯交互
  2. 特定的繪製操做(如 translation, scale 等)能夠做用於整個 DisplayList 而無須從新分發繪製操做
  3. 當知曉了全部繪製操做後,能夠針對其進行優化:例如,全部的文本能夠一塊兒進行繪製一次
  4. 能夠將對 DisplayList 的處理轉移至另外一個線程(也就是 RenderThread)
  5. 主線程在 sync 結束後能夠處理其餘的 Message,而不用等待 RenderThread 結束

RenderThread 的具體流程你們能夠看這篇文章 : www.cocoachina.com/articles/35…

遊戲的主線程與渲染線程

遊戲大多使用單獨的渲染線程,有單獨的 Surface ,直接跟 SurfaceFlinger 進行交互,其主線程的存在感比較低,絕大部分的邏輯都是本身在本身的渲染線程裏面實現的。

你們能夠看一下王者榮耀對應的 Systrace ,重點看應用進程和 SurfaceFlinger 進程(30fps)

圖片

能夠看到王者榮耀主線程的主要工做,就是把 Input 事件傳給 Unity 的渲染線程,渲染線程收到 Input 事件以後,進行邏輯處理,畫面更新等。

圖片

Flutter 的主線程和渲染線程

這裏提一下 Flutter App 在 Systrace 上的表現,因爲 Flutter 的渲染是基於 libSkia 的,因此它也沒有 RenderThread ,而是他自建的 RenderEngine , Flutter 比較重要的兩個線程是 ui 線程和 gpu 線程,對應到下面提到的  Framework 和 Engine 兩層

圖片

Flutter 中也會監聽 Vsync 信號 ,其 VsyncView 中會以 postFrameCallback 的形式,監聽 doFrame 回調,而後調用 nativeOnVsync ,將 Vsync 到來的信息傳給 Flutter UI 線程,開始一幀的繪製。

圖片

能夠看到 Flutter 的思路跟遊戲開發的思路差很少,不依賴具體的平臺,自建渲染管道,更新快,跨平臺優點明顯。

Flutter SDK 自帶 Skia 庫,不用等系統升級就能夠用到最新的 Skia 庫,並且 Google 團隊在 Skia 上作了不少優化,因此官方號稱性能能夠媲美原生應用

圖片

Flutter 的框架分爲 Framework 和 Engine 兩層,應用是基於 Framework 層開發的,Framework 負責渲染中的 Build,Layout,Paint,生成 Layer 等環節。Engine 層是 C++實現的渲染引擎,負責把 Framework 生成的 Layer 組合,生成紋理,而後經過 Open GL 接口向 GPU 提交渲染數據。

圖片

當須要更新 UI 的時候,Framework 通知 Engine,Engine 會等到下個 Vsync 信號到達的時候,會通知 Framework,而後 Framework 會進行 animations, build,layout,compositing,paint,最後生成 layer 提交給 Engine。Engine 會把 layer 進行組合,生成紋理,最後經過 Open Gl 接口提交數據給 GPU,GPU 通過處理後在顯示器上面顯示。整個流程以下圖:

圖片

性能

若是主線程須要處理全部任務,則執行耗時較長的操做(例如,網絡訪問或數據庫查詢)將會阻塞整個界面線程。一旦被阻塞,線程將沒法分派任何事件,包括繪圖事件。主線程執行超時一般會帶來兩個問題

  1. 卡頓:若是主線程+渲染線程每一幀的執行都超過 16.6ms,那麼就會出現掉幀。
  2. 卡死:若是界面線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘),用戶會看到 「應用無響應」 (ANR) 對話框

對於用戶來講,這兩個狀況都是用戶不肯意看到的,因此對於 App 開發者來講,兩個問題是發版本以前必需要解決的,ANR 這個因爲有詳細的調用棧,因此比較好定位;可是間歇性卡頓這個,可能就須要使用工具來進行分析了:Systrace + TraceView,因此理解主線程和渲染線程的關係和他們的工做原理是很是重要的,這也是本系列的一個初衷

另外關於卡頓,能夠參考下面三篇文章,你的 App 卡頓不必定是你 App 的問題,也有多是系統的問題,不過無論怎麼說,首先要會分析卡頓問題。

0. Android 中的卡頓丟幀緣由概述 - 方法論

1. Android 中的卡頓丟幀緣由概述 - 系統篇

2. Android 中的卡頓丟幀緣由概述 - 應用篇

參考

  1. juejin.im/post/5a9e01…
  2. www.cocoachina.com/articles/35…
  3. juejin.im/post/5b7767…
  4. gityuan.com/2019/06/15/…
  5. developer.android.google.cn/guide/compo…

附件

本文涉及到的附件也上傳了,各位下載後解壓,使用 Chrome 瀏覽器打開便可

點此連接下載文章所涉及到的 Systrace 附件

關於我

小廠系統研發工程師 , 更多信息能夠點擊 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .

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

相關文章
相關標籤/搜索