本文是 Systrace 系列文章的第五篇,主要是是介紹 Android App 的 MainThread 和 RenderThread,也就是你們熟悉的主線程和渲染線程。文章會從 Systrace 的角度來看 MainThread 和 RenderThread 的工做流程,以及涉及到的相關知識:卡頓、軟件渲染、掉幀計算等html
本系列的目的是經過 Systrace 這個工具,從另一個角度來看待 Android 系統總體的運行,同時也從另一個角度來對 Framework 進行學習。也許你看了不少講 Framework 的文章,可是老是記不住代碼,或者不清楚其運行的流程,也許從 Systrace 這個圖形化的角度,你能夠理解的更深刻一些java
這裏以滑動列表爲例 ,咱們截取主線程和渲染線程一幀的工做流程(每一幀都會遵循這個流程,不過有的幀須要處理的事情多,有的幀須要處理的事情少) ,重點看 「UI Thread 」 和 RenderThread 這兩行android
這張圖對應的工做流程以下git
上面這個流程在 Android 基於 Choreographer 的渲染機制詳解 這篇文章裏面已經介紹的很詳細了,包括每一幀的 doFrame 都在作什麼、卡頓計算的原理、APM 相關. 沒有看過這篇文章的同窗,建議先去掃一眼github
那麼這篇文章咱們主要從 Android 基於 Choreographer 的渲染機制詳解 這篇文章沒有講到的幾個點來入手,幫你更好地理解主線程和渲染線程數據庫
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
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 流程以下:
另外咱們常常說的,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 類,這樣間接的進行繪製操做的優勢以下
RenderThread 的具體流程你們能夠看這篇文章 : www.cocoachina.com/articles/35…
遊戲大多使用單獨的渲染線程,有單獨的 Surface ,直接跟 SurfaceFlinger 進行交互,其主線程的存在感比較低,絕大部分的邏輯都是本身在本身的渲染線程裏面實現的。
你們能夠看一下王者榮耀對應的 Systrace ,重點看應用進程和 SurfaceFlinger 進程(30fps)
能夠看到王者榮耀主線程的主要工做,就是把 Input 事件傳給 Unity 的渲染線程,渲染線程收到 Input 事件以後,進行邏輯處理,畫面更新等。
這裏提一下 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 通過處理後在顯示器上面顯示。整個流程以下圖:
若是主線程須要處理全部任務,則執行耗時較長的操做(例如,網絡訪問或數據庫查詢)將會阻塞整個界面線程。一旦被阻塞,線程將沒法分派任何事件,包括繪圖事件。主線程執行超時一般會帶來兩個問題
對於用戶來講,這兩個狀況都是用戶不肯意看到的,因此對於 App 開發者來講,兩個問題是發版本以前必需要解決的,ANR 這個因爲有詳細的調用棧,因此比較好定位;可是間歇性卡頓這個,可能就須要使用工具來進行分析了:Systrace + TraceView,因此理解主線程和渲染線程的關係和他們的工做原理是很是重要的,這也是本系列的一個初衷
另外關於卡頓,能夠參考下面三篇文章,你的 App 卡頓不必定是你 App 的問題,也有多是系統的問題,不過無論怎麼說,首先要會分析卡頓問題。
本文涉及到的附件也上傳了,各位下載後解壓,使用 Chrome 瀏覽器打開便可
小廠系統研發工程師 , 更多信息能夠點擊 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .
一我的能夠走的更快 , 一羣人能夠走的更遠