Android界面繪制流程詳解

概述

我們知道,activity顯示出頁面是在onresum之后,那么他具體到底是怎么添加和繪制的呢

繪制的入口

從前面講的APP啟動流程分析中我們知道,在創(chuàng)建Activiy的流程這一步中,其步驟
1、創(chuàng)建Activity的Context;
2、通過反射創(chuàng)建Activity對象;
3、執(zhí)行Activity的attach動作,其中會創(chuàng)建應用窗口的PhoneWindow對象并設置WindowManage;
4、執(zhí)行應用Activity的onCreate生命周期函數(shù),并在setContentView中創(chuàng)建窗口的DecorView對象;
在Activiy Resume的流程這一步中,其步驟:
1、執(zhí)行應用Activity的onResume生命周期函數(shù);
2、執(zhí)行WindowManager的addView動作開啟視圖繪制邏輯;
3、創(chuàng)建Activity的ViewRootImpl對象;
4、執(zhí)行ViewRootImpl的setView函數(shù)開啟UI界面繪制動作;

所以其入口跟隨Activity的啟動而調用

應用UI布局與繪制

應用在執(zhí)行Activity的Resume流程的最后,會創(chuàng)建ViewRootImpl對象并調用其setView函數(shù),從此并開啟了應用界面UI布局與繪制的流程。在開始講解這個過程之前,我們先來整理一下前面代碼中講到的這些概念,如Activity、PhoneWindow、DecorView、ViewRootImpl、WindowManager它們之間的關系與職責,因為這些核心類基本構成了Android系統(tǒng)的GUI顯示系統(tǒng)在應用進程側的核心架構,其整體架構如下圖所示:


Activity-DecorView-ViewRoot.jpg

Window是一個抽象類,通過控制DecorView提供了一些標準的UI方案,比如背景、標題、虛擬按鍵等,而PhoneWindow是Window的唯一實現(xiàn)類,在Activity創(chuàng)建后的attach流程中創(chuàng)建,應用啟動顯示的內容裝載到其內部的mDecor(DecorView);

DecorView是整個界面布局View控件樹的根節(jié)點,通過它可以遍歷訪問到整個View控件樹上的任意節(jié)點;

WindowManager是一個接口,繼承自ViewManager接口,提供了View的基本操作方法;
WindowManagerImp實現(xiàn)了WindowManager接口,內部通過組合方式持有WindowManagerGlobal,用來操作View;
WindowManagerGlobal是一個全局單例,內部可以通過ViewRootImpl將View添加至窗口中;

ViewRootImpl是所有View的Parent,用來總體管理View的繪制以及與系統(tǒng)WMS窗口管理服務的IPC交互從而實現(xiàn)窗口的開辟;ViewRootImpl是應用進程運轉的發(fā)動機,可以看到ViewRootImpl內部包含mView(就是DecorView)、mSurface、Choregrapher,mView代表整個控件樹,mSurfacce代表畫布,應用的UI渲染會直接放到mSurface中,Choregorapher使得應用請求vsync信號,接收信號后開始渲染流程;

我們從ViewRootImpl的setView流程繼續(xù)結合代碼往下看:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
      synchronized (this) {
         if (mView == null) {
             mView = view;
         }
         ...
         // 開啟繪制硬件加速,初始化RenderThread渲染線程運行環(huán)境
         enableHardwareAcceleration(attrs);
         ...
         // 1.觸發(fā)繪制動作
         requestLayout();
         ...
         inputChannel = new InputChannel();
         ...
         // 2.Binder調用訪問系統(tǒng)窗口管理服務WMS接口,實現(xiàn)addWindow添加注冊應用窗口的操作,并傳入inputChannel用于接收觸控事件
         res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
         ...
         // 3.創(chuàng)建WindowInputEventReceiver對象,實現(xiàn)應用窗口接收觸控事件
         mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
         ...
         // 4.設置DecorView的mParent為ViewRootImpl
         view.assignParent(this);
         ...
      }
}

從以上代碼可以看出ViewRootImpl的setView內部關鍵流程如下:
1、requestLayout()通過一系列調用觸發(fā)界面繪制(measure、layout、draw)動作,下文會詳細展開分析;
2、通過Binder調用訪問系統(tǒng)窗口管理服務WMS的addWindow接口,實現(xiàn)添加、注冊應用窗口的操作,并傳入本地創(chuàng)建inputChannel對象用于后續(xù)接收系統(tǒng)的觸控事件,這一步執(zhí)行完我們的View就可以顯示到屏幕上了。關于WMS的內部實現(xiàn)流程也非常復雜,由于篇幅有限本文就不詳細展開分析了。
3、創(chuàng)建WindowInputEventReceiver對象,封裝實現(xiàn)應用窗口接收系統(tǒng)觸控事件的邏輯;
4、執(zhí)行view.assignParent(this),設置DecorView的mParent為ViewRootImpl。所以,雖然ViewRootImpl不是一個View,但它是所有View的頂層Parent。

接下來ViewRootImpl的requestLayout動作繼續(xù)往下看界面繪制的流程代碼:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
         // 檢查當前UI繪制操作是否發(fā)生在主線程,如果發(fā)生在子線程則會拋出異常
         checkThread();
         mLayoutRequested = true;
         // 觸發(fā)繪制操作
         scheduleTraversals();
    }
}

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
         //...
         // 注意此處會往主線程的MessageQueue消息隊列中添加同步欄刪,因為系統(tǒng)繪制消息屬于異步消息,需要更高優(yōu)先級的處理
         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
         // 通過Choreographer往主線程消息隊列添加CALLBACK_TRAVERSAL繪制類型的待執(zhí)行消息,用于觸發(fā)后續(xù)UI線程真正實現(xiàn)繪制動作
         mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         //...
     }
}

Choreographer 的引入,主要是配合系統(tǒng)Vsync垂直同步機制(Android“黃油計劃”中引入的機制之一,協(xié)調APP生成UI數(shù)據(jù)和SurfaceFlinger合成圖像,避免Tearing畫面撕裂的現(xiàn)象),給上層 App 的渲染提供一個穩(wěn)定的 Message 處理的時機,也就是 Vsync 到來的時候 ,系統(tǒng)通過對 Vsync 信號周期的調整,來控制每一幀繪制操作的時機。Choreographer 扮演 Android 渲染鏈路中承上啟下的角色:
1、負責接收和處理 App 的各種更新消息和回調,等到 Vsync 到來的時候統(tǒng)一處理。比如集中處理 Input(主要是 Input 事件的處理) 、Animation(動畫相關)、Traversal(包括 measure、layout、draw 等操作) ,判斷卡頓掉幀情況,記錄 CallBack 耗時等;
2、負責請求和接收 Vsync 信號。接收 Vsync 事件回調(通過 FrameDisplayEventReceiver.onVsync ),請求 Vsync(FrameDisplayEventReceiver.scheduleVsync) 。

Choreographer在收到CALLBACK_TRAVERSAL類型的繪制任務后,其內部的工作流程如下圖所示



ViewRootImpl調用Choreographer的postCallback接口放入待執(zhí)行的繪制消息后,Choreographer會先向系統(tǒng)申請APP 類型的vsync信號,然后等待系統(tǒng)vsync信號到來后,去回調到ViewRootImpl的doTraversal函數(shù)中執(zhí)行真正的繪制動作(measure、layout、draw)。

接著ViewRootImpl的doTraversal函數(shù)的簡化代碼流程往下看:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
void doTraversal() {
     if (mTraversalScheduled) {
         mTraversalScheduled = false;
         // 調用removeSyncBarrier及時移除主線程MessageQueue中的Barrier同步欄刪,以避免主線程發(fā)生“假死”
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
         ...
         // 執(zhí)行具體的繪制任務
         performTraversals();
         ...
    }
}

private void performTraversals() {
     ...
     // 1.從DecorView根節(jié)點出發(fā),遍歷整個View控件樹,完成整個View控件樹的measure測量操作
     windowSizeMayChange |= measureHierarchy(...);
     ...
     if (mFirst...) {
    // 2.第一次執(zhí)行traversals繪制任務時,Binder調用訪問系統(tǒng)窗口管理服務WMS的relayoutWindow接口,實現(xiàn)WMS計算應用窗口尺寸并向系統(tǒng)surfaceflinger正式申請Surface“畫布”操作
         relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
     }
     ...
     // 3.從DecorView根節(jié)點出發(fā),遍歷整個View控件樹,完成整個View控件樹的layout測量操作
     performLayout(lp, mWidth, mHeight);
     ...
     // 4.從DecorView根節(jié)點出發(fā),遍歷整個View控件樹,完成整個View控件樹的draw測量操作
     performDraw();
     ...
}

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
        ...
        // 通過Binder IPC訪問系統(tǒng)WMS服務的relayout接口,申請Surface“畫布”操作
        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
                mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize, mBlastSurfaceControl);
        if (mSurfaceControl.isValid()) {
            if (!useBLAST()) {
                // 本地Surface對象獲取指向遠端分配的Surface的引用
                mSurface.copyFrom(mSurfaceControl);
            } else {
               ...
            }
        }
        ...
}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ...
        // 原生標識View樹的measure測量過程的trace tag
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            // 從mView指向的View控件樹的根節(jié)點DecorView出發(fā),遍歷訪問整個View樹,并完成整個布局View樹的測量工作
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

private void performDraw() {
     ...
     boolean canUseAsync = draw(fullRedrawNeeded);
     ...
}

private boolean draw(boolean fullRedrawNeeded) {
    ...
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
        ...
        // 如果開啟并支持硬件繪制加速,則走硬件繪制的流程(從Android 4.+開始,默認情況下都是支持跟開啟了硬件加速的)
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        // 否則走drawSoftware軟件繪制的流程
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
         }
    }
}

ViewRootImpl中負責的整個應用界面繪制的主要流程
1、從界面View控件樹的根節(jié)點DecorView出發(fā),遞歸遍歷整個View控件樹,完成對整個View控件樹的measure測量操作,由于篇幅所限,本文就不展開分析這塊的詳細流程;

2、界面第一次執(zhí)行繪制任務時,會通過BinderIPC訪問系統(tǒng)窗口管理服務WMS的relayout接口,實現(xiàn)窗口尺寸的計算并向系統(tǒng)申請用于本地繪制渲染的Surface“畫布”的操作(具體由SurfaceFlinger負責創(chuàng)建應用界面對應的BufferQueueLayer對象,并通過內存共享的方式通過Binder將地址引用透過WMS回傳給應用進程這邊),由于篇幅所限,本文就不展開分析這塊的詳細流程;

3、從界面View控件樹的根節(jié)點DecorView出發(fā),遞歸遍歷整個View控件樹,完成對整個View控件樹的layout測量操作;

4、從界面View控件樹的根節(jié)點DecorView出發(fā),遞歸遍歷整個View控件樹,完成對整個View控件樹的draw測量操作,如果開啟并支持硬件繪制加速(從Android 4.X開始谷歌已經(jīng)默認開啟硬件加速),則走GPU硬件繪制的流程,否則走CPU軟件繪制的流程;
繪制流程如下:

Android繪制入口圖解.jpg

DecorView是所有視圖的根視圖,也就是最頂層布局,它是一個ViewGroup。每個Activity的View繪制流程都是先從DecorView的繪制開始,然后依次遞歸繪制它的子View和子ViewGroup,子ViewGroup再遞歸繪制它包含的子View,直到所有的View都繪制完成。

這里說說View繪制涉及到的類和方法
從上面源碼分析中可以看出,View繪制的入口是ViewRootImpl類的performTraversals()方法。在performTraversals()方法中有3個重要的步驟方法:
(1)performMeasure():該方法主要用于測量所有View和ViewGroup的大小(寬和高)

(2)performLayout():該方法用于確定所有View和ViewGroup在父布局的位置

(3)performDraw():該方法進行具體的繪制操作

performMeasure測量

1.測量涉及的方法

performMeasure()主要就是用來測量View和ViewGroup的寬和高,涉及到的方法有:

(1)View:Measure() —>onMeasure() —>setMeasureDimension() —>getDefaultSize()

View的測量流程:View的測量是從Measure()方法開始的,在Measure()里面沒有具體的操作,而是直接調用onMeasure()方法。在onMeasure()方法里調用了兩個getDefaultSize()方法來分別測量View的寬和高的值。最后調用setMeasureDimension()方法將View的寬和高測量值保存下來。

(2)ViewGroup:Measure() —>onMeasure() —>measureChildren() —>measureChild()/measureChildWithMargins() —>Measure() —>onMeasure()—>setMeasureDimension() —>getDefaultSize()。

ViewGroup除了需要測量自己的寬高大小,還需要測量它包含的子View或子ViewGroup的大小,所以它涉及到的方法除了測量View需要的方法之外,還有measureChildren()方法,在measureChildren()方法內通過循環(huán)調用measureChild()方法,在measureChild方法中又調用子View或子ViewGroup的Measure()方法。

總結:在performMeasure()方法中首先調用的是頂級視圖DecorView的Measure()方法,在Measure()方法中調用onMeasure()方法進行DecorView的寬高大小測量。由于DecorView是一個ViewGroup,所以在onMeasure()方法內又調用了measureChildren()方法來測量它的子View和子ViewGroup寬高大小,這樣一步步往下遞歸遍歷,最后測量出所有的View和ViewGroup的大小。

2.MeasureSpec

上面描述了測量View和ViewGroup大小的大致流程,但是在具體測量的時候還涉及到了一個MeasureSpec類,這個類是View類的內部類,主要內容是一個int型變量,int是32位的,其中高2位表示模式(Mode),低30位表示大?。⊿ize)。每一個View和ViewGroup都有自己的MeasureSpec,具體來說是有2個MeasureSpec:widthMeasureSpec和heightMeasureSpec。這兩個MeasureSpec從measure()方法開始作為參數(shù),一直傳遞到getDefaultSize()方法,最后在getDefaultSize()方法里面參考這2個MeasureSpec來確定具體大小。

MeasureSpec高2位表示的模式有3種:UNSPECIFIED(不指定的)、EXACTLY(確定的)、AT_MOST(至多的)。

子View的大小通常是受限于父布局的,舉個例子,假如某個子View大小設為match_parent,那么該子View的大小就依賴于父布局的大小,父布局如果大小為200200dp,子View就為200200dp。

子View的MeasureSpec是由自身的LayoutParams和父布局的MeasureSpec共同確定的。在上面例子中父布局大小為200*200dp,于是父布局的widthMeasureSpec和widthMeasureSpec模式Mode為EXACTLY(確定的),大小Size為200。又由于子View的LayoutParams指定為match_parent,于是子View的widthMeasureSpec和widthMeasureSpec模式Mode也為EXACTLY(確定的),大小Size也為200。

上面子View的MeasureSpec確定過程發(fā)生在measureChild()方法中,確定好子View的MeasureSpec后就將其作為參數(shù)傳入到measure()方法,最后在getDefaultSize()方法里根據(jù)子View的兩個MeasureSpec來確定子View的寬高。

父MeasureSpec和子MeasureSpec的關系圖如下,上面例子對應下圖的第二行第一列。

父/子 EXACTLY AT_MOST UNSPECIFIED
具體尺寸 EXACTLY 、childSize EXACTLY 、childSize EXACTLY 、childSize
match_parent EXACTLY 、parentSize AT_MOST、parentSize UNSPECIFIED、SDK<23?0:parentSize
wrap_content AT_MOST、parentSize AT_MOST、parentSize UNSPECIFIED、SDK<23?0:parentSize

上圖總結:

1.當子View的LayoutParams指定為具體的尺寸如200*200dp,那么無論父布局的MeasureSpec模式和大小是什么,子View的模式都為EXACTLY(確定的),大小就是LayoutParams指定的具體值200,而不依賴于父布局MeasureSpec指定的大小。

2.當子View的LayoutParams指定為match_parent時,子View的MeasureSpec就和父布局的MeasureSpec相同。

2.當子View的LayoutParams指定為wrap_content時,分兩種情況:當父布局MeasureSpec模式為UNSPECIFIED(不指定的),子View的MeasureSpec模式也是UNSPECIFIED(不指定的);當父布局MeasureSpec模式為EXACTLY(確定的)和AT_MOST(至多的),子View的MeasureSpec模式都是AT_MOST(至多的)。無論Mode是哪一種,子View的MeasureSpec的大?。⊿ize)都是父布局的MeasureSpec中指定的大小,表示子View大小不應該超過父布局大小。

只要知道了父布局的MeasureSpec和子View的LayoutParams,那么子View的MeasureSpec也就確定了,從而能夠進一步確定子View的大小。

ps:由于DecorView是頂級布局,所以它的MeasureSpec是通過窗口大小和自身的LayoutParams確定的。

performLayout布局

在performTraversals()方法中,首先調用performMeasure()方法來測量所有View和ViewGroup的寬高大小。之后就是調用performLayout()方法確定每個View和ViewGroup在其父布局中的位置。

布局涉及的方法
(1)View:layout()—>setFrame()
View布局流程:View通過調用layout()方法來確定它在父布局的位置,具體是在layout()方法內調用setFrame()方法,將該View的上下左右四個點位置作為參數(shù)傳入,從而確定了View在父布局的位置。

ViewGroup:layout()—>setFrame()—>onLayout()
ViewGroup布局流程:ViewGroup同樣先調用layout()方法,通過layout()方法內的setFrame()方法設置自己相對于父布局的位置。但是由于ViewGroup包含子View或子ViewGroup,所以需要重寫onLayout()方法,在onLayout()方法中遍歷自己的子View或子ViewGroup,分別調用它們的layout()方法來完成子View或子ViewGroup的布局。

總結:在performLayout()方法中首先調用的是頂級布局DecorView的Layout()方法,由于DecorView是一個ViewGroup,所以它調用的是父類View的layout()方法(原因在下面PS中介紹),并且在layout()方法中確定了自己的位置之后再重寫onLayout()方法來遍歷子View和子ViewGroup,通過調用View和ViewGroup的layout()方法來確定它們在父布局DecorView中的位置,這樣一步步往下遞歸遍歷,直到所有的View都確定了在父布局的位置為止。

PS:

1.ViewGroup的layout()方法是final修飾的,不能被重寫;View的layout()方法是可以被重寫的。

2.在ViewGroup中l(wèi)ayout()方法中最終調用的是View的layout()方法。

2.View的onLayout()方法是一個空方法,這是因為View沒有子View或子ViewGroup,不需要onLayout()方法。而ViewGroup的onLayout()方法是一個抽象方法,即ViewGroup的子類必須重寫這個方法,因為ViewGroup包含子View或子ViewGroup,需要在onLayout()方法中遍歷View或子ViewGroup并調用它們的layout()方法,從而確定它們在父布局中的位置。

performDraw繪制

在performTraversals()方法中,首先調用performMeasure()方法來測量所有View和ViewGroup的寬高大小。之后就是調用performLayout()方法確定每個View和ViewGroup在其父布局中的位置。最后就是調用performDraw進行具體的繪制。

繪制涉及的方法:
View:draw()—>drawBackground()—>onDraw()—>onDrawScrollBars()。

View繪制流程:首先調用的是draw()方法,在該方法內依次調用drawBackground()方法來繪制View背景,調用onDraw()方法繪制View內容,最后調用onDrawScrollBars()方法繪制裝飾。

ViewGroup:draw()—>drawBackground()—>onDraw()—>dispatchDraw()—>onDrawScrollBars()。

ViewGroup繪制流程:在ViewGroup的draw()方法中,在調用drawBackground()方法來繪制View背景、調用onDraw()方法繪制View內容之后還需要調用dispatchDraw()方法來繪制子View和子ViewGroup,具體是在dispatchDraw()方法內循環(huán)遍歷子View和子ViewGroup,并且調用它們的draw()方法進行繪制。

總結:在performDraw()方法中首先調用頂級布局DecorView的draw()方法繪制自身,由于DecorView是一個ViewGroup,所以在繪制完自己之后還要調用dispatchDraw()方法來遍歷繪制它的子View和子ViewGroup。這樣一步步往下遞歸遍歷繪制,直到所有View和ViewGroup都繪制完成為止。

PS:在View類中的onDraw()方法為空方法,具體的繪制邏輯需要在子類中的onDraw()方法中實現(xiàn)。

渲染

RenderThread渲染
在ViewRootImpl中完成了對界面的measure、layout和draw等繪制流程后,用戶依然還是看不到屏幕上顯示的應用界面內容,因為整個Android系統(tǒng)的顯示流程除了前面講到的UI線程的繪制外,界面還需要經(jīng)過RenderThread線程的渲染處理,渲染完成后,還需要通過Binder調用“上幀”交給surfaceflinger進程中進行合成后送顯才能最終顯示到屏幕上。本小節(jié)中,我們將接上一節(jié)中ViewRootImpl中最后draw的流程繼續(xù)往下分析開啟硬件加速情況下,RenderThread渲染線程的工作流程。由于目前Android 4.X之后系統(tǒng)默認界面是開啟硬件加速的,所以本文我們重點分析硬件加速條件下的界面渲染流程,我們先分析一下簡化的代碼流程:

/*frameworks/base/core/java/android/view/ViewRootImpl.java*/
private boolean draw(boolean fullRedrawNeeded) {
    ...
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
        ...
        // 硬件加速條件下的界面渲染流程
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        ...
    }
}

/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ...
    // 1.從DecorView根節(jié)點出發(fā),遞歸遍歷View控件樹,記錄每個View節(jié)點的繪制操作命令,完成繪制操作命令樹的構建
    updateRootDisplayList(view, callbacks);
    ...
    // 2.JNI調用同步Java層構建的繪制命令樹到Native層的RenderThread渲染線程,并喚醒渲染線程利用OpenGL執(zhí)行渲染任務;
    int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
    ...
}

從上面的代碼可以看出,硬件加速繪制主要包括兩個階段:

1、從DecorView根節(jié)點出發(fā),遞歸遍歷View控件樹,記錄每個View節(jié)點的drawOp繪制操作命令,完成繪制操作命令樹的構建;
2、JNI調用同步Java層構建的繪制命令樹到Native層的RenderThread渲染線程,并喚醒渲染線程利用OpenGL執(zhí)行渲染任務;

構建繪制命令樹
我們先來看看第一階段構建繪制命令樹的代碼簡化流程:

/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        // 原生標記構建View繪制操作命令樹過程的systrace tag
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
        // 遞歸子View的updateDisplayListIfDirty實現(xiàn)構建DisplayListOp
        updateViewTreeDisplayList(view);
        ...
        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
            // 獲取根View的SkiaRecordingCanvas
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            try {
                ...
                // 利用canvas緩存DisplayListOp繪制命令
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                ...
            } finally {
                // 將所有DisplayListOp繪制命令填充到RootRenderNode中
                mRootNode.endRecording();
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

private void updateViewTreeDisplayList(View view) {
        ...
        // 從DecorView根節(jié)點出發(fā),開始遞歸調用每個View樹節(jié)點的updateDisplayListIfDirty函數(shù)
        view.updateDisplayListIfDirty();
        ...
}

/*frameworks/base/core/java/android/view/View.java*/
public RenderNode updateDisplayListIfDirty() {
     ...
     // 1.利用`View`對象構造時創(chuàng)建的`RenderNode`獲取一個`SkiaRecordingCanvas`“畫布”;
     final RecordingCanvas canvas = renderNode.beginRecording(width, height);
     try {
         ...
         if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
              // 如果僅僅是ViewGroup,并且自身不用繪制,直接遞歸子View
              dispatchDraw(canvas);
              ...
         } else {
              // 2.利用SkiaRecordingCanvas,在每個子View控件的onDraw繪制函數(shù)中調用drawLine、drawRect等繪制操作時,創(chuàng)建對應的DisplayListOp繪制命令,并緩存記錄到其內部的SkiaDisplayList持有的DisplayListData中;
              draw(canvas);
         }
     } finally {
         // 3.將包含有`DisplayListOp`繪制命令緩存的`SkiaDisplayList`對象設置填充到`RenderNode`中;
         renderNode.endRecording();
         ...
     }
     ...
}

public void draw(Canvas canvas) {
    ...
    // draw the content(View自己實現(xiàn)的onDraw繪制,由應用開發(fā)者自己實現(xiàn))
    onDraw(canvas);
    ...
    // draw the children
    dispatchDraw(canvas);
    ...
}

/*frameworks/base/graphics/java/android/graphics/RenderNode.java*/
public void endRecording() {
        ...
        // 從SkiaRecordingCanvas中獲取SkiaDisplayList對象
        long displayList = canvas.finishRecording();
        // 將SkiaDisplayList對象填充到RenderNode中
        nSetDisplayList(mNativeRenderNode, displayList);
        canvas.recycle();
}

從以上代碼可以看出,構建繪制命令樹的過程是從View控件樹的根節(jié)點DecorView觸發(fā),遞歸調用每個子View節(jié)點的updateDisplayListIfDirty函數(shù),最終完成繪制樹的創(chuàng)建,簡述流程如下:

利用View對象構造時創(chuàng)建的RenderNode獲取一個SkiaRecordingCanvas“畫布”;

利用SkiaRecordingCanvas,在每個子View控件的onDraw繪制函數(shù)中調用drawLine、drawRect等繪制操作時,創(chuàng)建對應的DisplayListOp繪制命令,并緩存記錄到其內部的SkiaDisplayList持有的DisplayListData中;

將包含有DisplayListOp繪制命令緩存的SkiaDisplayList對象設置填充到RenderNode中;

最后將根View的緩存DisplayListOp設置到RootRenderNode中,完成構建。
以上整個構建繪制命令樹的過程可以用如下流程圖表示:


android渲染流程.jpg

執(zhí)行渲染繪制任務
經(jīng)過上一小節(jié)中的分析,應用在UI線程中從根節(jié)點DecorView出發(fā),遞歸遍歷每個子View節(jié)點,搜集其drawXXX繪制動作并轉換成DisplayListOp命令,將其記錄到DisplayListData并填充到RenderNode中,最終完成整個View繪制命令樹的構建。從此UI線程的繪制任務就完成了。下一步UI線程將喚醒RenderThread渲染線程,觸發(fā)其利用OpenGL執(zhí)行界面的渲染任務,本小節(jié)中我們將重點分析這個流程。我們還是先看看這塊代碼的簡化流程:

/*frameworks/base/graphics/java/android/graphics/HardwareRenderer.java*/
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
    // JNI調用native層的相關函數(shù)
    return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}

/*frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp*/
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
    ...
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
    return proxy->syncAndDrawFrame();
}

/*frameworks/base/libs/hwui/renderthread/RenderProxy.cpp*/
int RenderProxy::syncAndDrawFrame() {
    // 喚醒RenderThread渲染線程,執(zhí)行DrawFrame繪制任務
    return mDrawFrameTask.drawFrame();
}

/*frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp*/
int DrawFrameTask::drawFrame() {
    ...
    postAndWait();
    ...
}

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    // 向RenderThread渲染線程的MessageQueue消息隊列放入一個待執(zhí)行任務,以將其喚醒執(zhí)行run函數(shù)
    mRenderThread->queue().post([this]() { run(); });
    // UI線程暫時進入wait等待狀態(tài)
    mSignal.wait(mLock);
}

void DrawFrameTask::run() {
    // 原生標識一幀渲染繪制任務的systrace tag
    ATRACE_NAME("DrawFrame");
    ...
    {
        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
        //1.將UI線程構建的DisplayListOp繪制命令樹同步到RenderThread渲染線程
        canUnblockUiThread = syncFrameState(info);
        ...
    }
    ...
    // 同步完成后則可以喚醒UI線程
    if (canUnblockUiThread) {
        unblockUiThread();
    }
    ...
    if (CC_LIKELY(canDrawThisFrame)) {
        // 2.執(zhí)行draw渲染繪制動作
        context->draw();
    } else {
        ...
    }
    ...
}

bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    ATRACE_CALL();
    ...
    // 調用CanvasContext的prepareTree函數(shù)實現(xiàn)繪制命令樹同步的流程
    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
    ...
}

/*frameworks/base/libs/hwui/renderthread/CanvasContext.cpp*/
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
                                RenderNode* target) {
     ...
     for (const sp<RenderNode>& node : mRenderNodes) {
        ...
        // 遞歸調用各個子View對應的RenderNode執(zhí)行prepareTree動作
        node->prepareTree(info);
        ...
    }
    ...
}

/*frameworks/base/libs/hwui/RenderNode.cpp*/
void RenderNode::prepareTree(TreeInfo& info) {
    ATRACE_CALL();
    ...
    prepareTreeImpl(observer, info, false);
    ...
}

void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
    ...
    if (info.mode == TreeInfo::MODE_FULL) {
        // 同步繪制命令樹
        pushStagingDisplayListChanges(observer, info);
    }
    if (mDisplayList) {
        // 遍歷調用各個子View對應的RenderNode的prepareTreeImpl
        bool isDirty = mDisplayList->prepareListAndChildren(
                observer, info, childFunctorsNeedLayer,
                [](RenderNode* child, TreeObserver& observer, TreeInfo& info,
                   bool functorsNeedLayer) {
                    child->prepareTreeImpl(observer, info, functorsNeedLayer);
                });
        ...
    }
    ...
}

void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
    ...
    syncDisplayList(observer, &info);
    ...
}

void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
    ...
    // 完成賦值同步DisplayList對象
    mDisplayList = mStagingDisplayList;
    mStagingDisplayList = nullptr;
    ...
}

void CanvasContext::draw() {
    ...
    // 1.調用OpenGL庫使用GPU,按照構建好的繪制命令完成界面的渲染
    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                                      &(profiler()));
    ...
    // 2.將前面已經(jīng)繪制渲染好的圖形緩沖區(qū)Binder上幀給SurfaceFlinger合成和顯示
    bool didSwap =
            mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);
    ...
}

從以上代碼可以看出:UI線程利用RenderProxy向RenderThread線程發(fā)送一個DrawFrameTask任務請求,RenderThread被喚醒,開始渲染,大致流程如下:

syncFrameState中遍歷View樹上每一個RenderNode,執(zhí)行prepareTreeImpl函數(shù),實現(xiàn)同步繪制命令樹的操作;

調用OpenGL庫API使用GPU,按照構建好的繪制命令完成界面的渲染(具體過程,由于本文篇幅所限,暫不展開分析);

將前面已經(jīng)繪制渲染好的圖形緩沖區(qū)Binder上幀給SurfaceFlinger合成和顯示;

整個過程可以用如下流程圖表示:


執(zhí)行渲染過程.jpg

SurfaceFlinger合成顯示

SurfaceFlinger合成顯示部分完全屬于Android系統(tǒng)GUI中圖形顯示的內容,邏輯結構也比較復雜,但不屬于本文介紹內容的重點。所以本小節(jié)中只是總體上介紹一下其工作原理與思想,不再詳細分析源碼,感興趣的讀者可以關注筆者后續(xù)的文章再來詳細分析講解。簡單的說SurfaceFlinger作為系統(tǒng)中獨立運行的一個Native進程,借用Android官網(wǎng)的描述,其職責就是負責接受來自多個來源的數(shù)據(jù)緩沖區(qū),對它們進行合成,然后發(fā)送到顯示設備。如下圖所示:


surface合成顯示.jpg

從上圖可以看出,其實SurfaceFlinger在Android系統(tǒng)的整個圖形顯示系統(tǒng)中是起到一個承上啟下的作用:

對上:通過Surface與不同的應用進程建立聯(lián)系,接收它們寫入Surface中的繪制緩沖數(shù)據(jù),對它們進行統(tǒng)一合成。
對下:通過屏幕的后緩存區(qū)與屏幕建立聯(lián)系,發(fā)送合成好的數(shù)據(jù)到屏幕顯示設備。
圖形的傳遞是通過Buffer作為載體,Surface是對Buffer的進一步封裝,也就是說Surface內部具有多個Buffer供上層使用,如何管理這些Buffer呢?答案就是BufferQueue ,下面我們來看看BufferQueue的工作原理:

BufferQueue機制
借用一張經(jīng)典的圖來描述BufferQueue的工作原理:

BufferQueue原理圖.jpg

BufferQueue是一個典型的生產(chǎn)者-消費者模型中的數(shù)據(jù)結構。在Android應用的渲染流程中,應用扮演的就是“生產(chǎn)者”的角色,而SurfaceFlinger扮演的則是“消費者”的角色,其配合工作的流程如下:

應用進程中在開始界面的繪制渲染之前,需要通過Binder調用dequeueBuffer接口從SurfaceFlinger進程中管理的BufferQueue 中申請一張?zhí)幱趂ree狀態(tài)的可用Buffer,如果此時沒有可用Buffer則阻塞等待;

應用進程中拿到這張可用的Buffer之后,選擇使用CPU軟件繪制渲染或GPU硬件加速繪制渲染,渲染完成后再通過Binder調用queueBuffer接口將緩存數(shù)據(jù)返回給應用進程對應的BufferQueue(如果是 GPU 渲染的話,這里還有個 GPU處理的過程,所以這個 Buffer 不會馬上可用,需要等 GPU 渲染完成的Fence信號),并申請sf類型的Vsync以便喚醒“消費者”SurfaceFlinger進行消費;

SurfaceFlinger 在收到 Vsync 信號之后,開始準備合成,使用 acquireBuffer獲取應用對應的 BufferQueue 中的 Buffer 并進行合成操作;

合成結束后,SurfaceFlinger 將通過調用 releaseBuffer將 Buffer 置為可用的free狀態(tài),返回到應用對應的 BufferQueue中。

Vsync同步機制
Vysnc垂直同步是Android在“黃油計劃”中引入的一個重要機制,本質上是為了協(xié)調BufferQueue的應用生產(chǎn)者生成UI數(shù)據(jù)動作和SurfaceFlinger消費者的合成消費動作,避免出現(xiàn)畫面撕裂的Tearing現(xiàn)象。Vysnc信號分為兩種類型:

app類型的Vsync:app類型的Vysnc信號由上層應用中的Choreographer根據(jù)繪制需求進行注冊和接收,用于控制應用UI繪制上幀的生產(chǎn)節(jié)奏。根據(jù)第7小結中的分析:應用在UI線程中調用invalidate刷新界面繪制時,需要先透過Choreographer向系統(tǒng)申請注冊app類型的Vsync信號,待Vsync信號到來后,才能往主線程的消息隊列放入待繪制任務進行真正UI的繪制動作;
sf類型的Vsync:sf類型的Vsync是用于控制SurfaceFlinger的合成消費節(jié)奏。應用完成界面的繪制渲染后,通過Binder調用queueBuffer接口將緩存數(shù)據(jù)返還給應用對應的BufferQueue時,會申請sf類型的Vsync,待SurfaceFlinger 在其UI線程中收到 Vsync 信號之后,便開始進行界面的合成操作。
Vsync信號的生成是參考屏幕硬件的刷新周期的,其架構如下圖所示:


Vsync機制.jpg

總結

本文結合源碼完整的分析了應用Activity界面第一幀畫面顯示的完整流程,這其中涉及了App應用、system_server框架、Art虛擬機、surfaceflinger等一系列Android系統(tǒng)核心模塊的相互配合,有很多的細節(jié)也由于篇幅所限無法完全展開分析,感興趣的讀者可以結合AOSP源碼繼續(xù)深入分析。

參考 https://zhuanlan.zhihu.com/p/644645710

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容