扣丁書屋

Android Systrace 基礎知識(9)-MainThread 和 RenderThread 解讀

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

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

系列文章目錄

  1. [Systrace 簡介[1]]
  2. [Systrace 基礎知識 - Systrace 預備知識[2]]
  3. [Systrace 基礎知識 - Why 60 fps ?[3]]
  4. [Systrace 基礎知識 - SystemServer 解讀[4]]
  5. [Systrace 基礎知識 - SurfaceFlinger 解讀[5]]
  6. [Systrace 基礎知識 - Input 解讀[6]]
  7. [Systrace 基礎知識 - Vsync 解讀[7]]
  8. [Systrace 基礎知識 - Vsync-App :基于 Choreographer 的渲染機制詳解[8]]
  9. Systrace 基礎知識 - MainThread 和 RenderThread 解讀[9]
  10. Systrace 基礎知識 - Binder 和鎖競爭解讀[10]
  11. Systrace 基礎知識 - Triple Buffer 解讀[11]
  12. Systrace 基礎知識 - CPU Info 解讀[12]

正文

這里以滑動列表為例 ,我們截取主線程和渲染線程「一幀」的工作流程(每一幀都會遵循這個流程,不過有的幀需要處理的事情多,有的幀需要處理的事情少) ,重點看 “UI Thread ” 和 RenderThread 這兩行

App 一幀的工作流

「這張圖對應的工作流程如下」

  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 的渲染機制詳解[13] 這篇文章里面已經介紹的很詳細了,包括每一幀的 doFrame 都在做什么、卡頓計算的原理、APM 相關. 沒有看過這篇文章的同學,建議先去掃一眼

那么這篇文章我們主要從 Android 基于 Choreographer 的渲染機制詳解[14] 這篇文章沒有講到的幾個點來入手,幫你更好地理解主線程和渲染線程

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

主線程的創建

Android App 的進程是基于 Linux 的,其管理也是基于 Linux 的進程管理機制,所以其創建也是調用了 fork 函數

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

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 是主線程,其實他只是主線程的一個邏輯處理單元。

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 的具體流程大家可以看這篇文章 :http://www.cocoachina.com/articles/35302[15]

游戲的主線程與渲染線程

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

大家可以看一下王者榮耀對應的 Systrace ,重點看應用進程和 SurfaceFlinger 進程(30fps)

王者榮耀

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

Flutter'

Flutter 的主線程和渲染線程

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

Flutter

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

Flutter

可以看到 Flutter 的思路跟游戲開發的思路差不多,不依賴具體的平臺,自建渲染管道,更新快,跨平臺優勢明顯。

Flutter SDK 自帶 Skia 庫,不用等系統升級就可以用到最新的 Skia 庫,而且 Google 團隊在 Skia 上做了很多優化,所以官方號稱性能可以媲美原生應用

Flutter

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

Flutter

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

Flutter

性能

如果主線程需要處理所有任務,則執行耗時較長的操作(例如,網絡訪問或數據庫查詢)將會阻塞整個界面線程。一旦被阻塞,線程將無法分派任何事件,包括繪圖事件。主線程執行超時通常會帶來兩個問題

  1. 卡頓:如果主線程 + 渲染線程每一幀的執行都超過 16.6ms(60fps 的情況下),那么就可能會出現掉幀。
  2. 卡死:如果界面線程被阻塞超過幾秒鐘時間(根據組件不同 , 這里的閾值也不同),用戶會看到 “應用無響應[16]” (ANR) 對話框(部分廠商屏蔽了這個彈框,會直接 Crash 到桌面)

對于用戶來說,這兩個情況都是用戶不愿意看到的,所以對于 App 開發者來說,兩個問題是發版本之前必須要解決的,ANR 這個由于有詳細的調用棧,所以相對來說比較好定位;但是間歇性卡頓這個,可能就需要使用工具來進行分析了:Systrace + TraceView,所以理解主線程和渲染線程的關系和他們的工作原理是非常重要的,這也是本系列的一個初衷

另外關于卡頓,可以參考下面三篇文章,你的 App 卡頓不一定是你 App 的問題,也有可能是系統的問題,不過不管怎么說,首先要會分析卡頓問題。

  1. Android 中的卡頓丟幀原因概述 - 方法論[17]
  2. Android 中的卡頓丟幀原因概述 - 系統篇[18]
  3. Android 中的卡頓丟幀原因概述 - 應用篇[19]

參考

  1. https://juejin.im/post/5a9e01c3f265da239d48ce32[20]
  2. http://www.cocoachina.com/articles/35302[21]
  3. https://juejin.im/post/5b7767fef265da43803bdc65[22]
  4. http://gityuan.com/2019/06/15/flutter_ui_draw/[23]
  5. https://developer.android.google.cn/guide/components/processes-and-threads[24]

附件

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

點此鏈接下載文章所涉及到的 Systrace 附件[25]

Reference

[1]Systrace 簡介: https://www.androidperformance.com/2019/05/28/Android-Systrace-About/

[2]Systrace 基礎知識 - Systrace 預備知識: https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/

[3]Systrace 基礎知識 - Why 60 fps ?: https://www.androidperformance.com/2019/05/27/why-60-fps/

[4]Systrace 基礎知識 - SystemServer 解讀: https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/

[5]Systrace 基礎知識 - SurfaceFlinger 解讀: https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/

[6]Systrace 基礎知識 - Input 解讀: https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/

[7]Systrace 基礎知識 - Vsync 解讀: https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/

[8]Systrace 基礎知識 - Vsync-App :基于 Choreographer 的渲染機制詳解: https://androidperformance.com/2019/10/22/Android-Choreographer/

[9]Systrace 基礎知識 - MainThread 和 RenderThread 解讀: https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/

[10]Systrace 基礎知識 - Binder 和鎖競爭解讀: https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/

[11]Systrace 基礎知識 - Triple Buffer 解讀: https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer

[12]Systrace 基礎知識 - CPU Info 解讀: https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU

[13]Android 基于 Choreographer 的渲染機制詳解: https://www.androidperformance.com/2019/10/22/Android-Choreographer/

[14]Android 基于 Choreographer 的渲染機制詳解: https://www.androidperformance.com/2019/10/22/Android-Choreographer/

[15]http://www.cocoachina.com/articles/35302: http://www.cocoachina.com/articles/35302

[16]應用無響應: http://developer.android.google.cn/guide/practices/responsiveness.html

[17]Android 中的卡頓丟幀原因概述 - 方法論: https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/

[18]Android 中的卡頓丟幀原因概述 - 系統篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/

[19]Android 中的卡頓丟幀原因概述 - 應用篇: https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/

[20]https://juejin.im/post/5a9e01c3f265da239d48ce32: https://juejin.im/post/5a9e01c3f265da239d48ce32

[21]http://www.cocoachina.com/articles/35302: http://www.cocoachina.com/articles/35302

[22]https://juejin.im/post/5b7767fef265da43803bdc65: https://juejin.im/post/5b7767fef265da43803bdc65

[23]http://gityuan.com/2019/06/15/flutter_ui_draw/: http://gityuan.com/2019/06/15/flutter_ui_draw/

[24]https://developer.android.google.cn/guide/components/processes-and-threads: https://developer.android.google.cn/guide/components/processes-and-threads

[25]點此鏈接下載文章所涉及到的 Systrace 附件: https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace-MainThread-RenderThread

[26]關于我: https://www.androidperformance.com/about/

[27]博客內容導航: https://androidperformance.com/2019/12/01/BlogMap/

[28]優秀博客文章記錄 - Android 性能優化必知必會: https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/


https://mp.weixin.qq.com/s/yfCNVVWJsD6Q99QIx8OoBw

Android Systrace 基礎知識(11) - Triple Buffer 解讀

發布于:3月以前  |  514次閱讀  |  詳細內容 ?

Android Systrace 基礎知識(7) - Vsync 解讀

發布于:3月以前  |  528次閱讀  |  詳細內容 ?

Android Systrace 基礎知識(6) - Input 解讀

發布于:3月以前  |  511次閱讀  |  詳細內容 ?

Android Systrace 基礎知識(5) - SurfaceFlinger 解讀

發布于:3月以前  |  533次閱讀  |  詳細內容 ?

Android Systrace 基礎知識(4) - SystemServer 解讀

發布于:3月以前  |  484次閱讀  |  詳細內容 ?

Android Systrace 基礎知識(3) - Why 60 fps ?

發布于:3月以前  |  428次閱讀  |  詳細內容 ?

Android Systrace 基礎知識 -- Systrace 簡介

發布于:3月以前  |  493次閱讀  |  詳細內容 ?

所屬標簽

最多閱讀

簡化Android的UI開發 2年以前  |  515113次閱讀
Android 深色模式適配原理分析 1年以前  |  26416次閱讀
Android 樣式系統 | 主題背景覆蓋 1年以前  |  7953次閱讀
Android Studio 生成so文件 及調用 1年以前  |  5587次閱讀
30分鐘搭建一個android的私有Maven倉庫 3年以前  |  4751次閱讀
Android設計與開發工作流 2年以前  |  4413次閱讀
Google Enjarify:可代替dex2jar的dex反編譯 3年以前  |  4397次閱讀
Android多渠道打包工具:apptools 3年以前  |  4028次閱讀
移動端常見崩潰指標 2年以前  |  4014次閱讀
Google Java編程風格規范(中文版) 3年以前  |  3942次閱讀
Android-模塊化-面向接口編程 1年以前  |  3857次閱讀
Android內存異常機制(用戶空間)_NE 1年以前  |  3824次閱讀
Android UI基本技術點 3年以前  |  3790次閱讀
Android死鎖初探 2年以前  |  3734次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>