1. 程式人生 > >View的繪製流程之二:View的繪製入口原始碼分析

View的繪製流程之二:View的繪製入口原始碼分析

一、回顧

由上一篇筆記 《View的繪製流程之一:setContentView()方法原始碼分析》,我們知道了 Activity 的 Layout 佈局最後會儲存在 DecorView 中的 Layout 佈局中的 FrameLayout 中,但是還沒有進行繪製,接下來,我們就來分析 DecorView 的佈局以及 Activity 的佈局是在什麼時候進行繪製的

我們知道 Activity 的啟動順序如下:

--> 棧頂的Activity的onPause() 
--> Instrumentation的newActivity() /*建立Activity*/
--> 待啟動Activity
的attach()
--> 待啟動Activity的onCreate() --> 待啟動Activity的onResume() --> ActivityThread.handleResumeActivity() /*將DecorView新增到Window*/

也就是說 DecorView 的佈局以及 Activity 的佈局都是在 Activity 的 onResume() 方法之後進行繪製的,具體位置在 ActivityThread.handleResumeActivity() 方法中將 DecorView 新增到 Window 中,同時進行繪製



二、原始碼分析

Step 1:

現在我們看到 ActivityThread 的 handleResumeActivity() 方法中
★ ActivityThread # handleResumeActivity()

final void handleResumeActivity(IBinder token,
                                boolean clearHide, boolean isForward, boolean reallyResume) {
    ....
    // 這個方法最終會呼叫 Activity的 onResume()方法
ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; .... // 如果該Activity是第一次啟動,而不是重新呼叫 onResume()方法重新啟動,那麼下面的條件為true if (r.window == null && !a.mFinished && willBeVisible) { // 1、獲取當前 Activity的 PhoneWindow物件 r.window = r.activity.getWindow(); // 2、獲取 DecorView,該DecorView已經在Activity的onCreate()方法的setContentView()初始化了 View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); // 3、獲取WindowManager的實現類WindowManagerImpl;WindowManagerImpl實現了ViewManager介面 ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); // 4、將DecorView儲存到Activity中 a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { // 標記已經將DecorView新增到Window a.mWindowAdded = true; // 5、將DecorView新增到Window // 由此可將,DecorView新增到 Window的過程跟我們將View新增到Window過程差不多 wm.addView(decor, l); } } else if (!willBeVisible) { r.hideForNow = true; } .... } else { .... } }

第一步:會獲取 Activity 的 PhoneWindow 物件(這個物件已經在 Activity 的 onCreate() 方法呼叫 setContentView() 方法過程中例項化了)
第二步:從 PhoneWindow 中獲取 DecorView,為接下來將 DecorView 新增到 Window 做準備
第三步:獲取 WindowManager 的實現類 WindowManagerImpl ,這個類是連線遠端 WindowManagerService 服務的一個紐帶, WindowManagerService 服務負責管理系統的 Window級別的 View 的,通過這個類可以將 DecorView 新增到 WindowManagerService
第四步:將 PhoneWindow 中的 DecorView 儲存到 Activity 中的 mDecor 屬性中,從這裡開始 DecorView 就和我們的 Activity 真正聯絡在一起了
第五步:新增到遠端 WindowManagerService 服務中

小結:
我們的 DecorView 已經新增到了 Window 中了,由原始碼我們知道將 DecorView 新增 Window 的過程跟我們手動新增一個 View到 Window 的流程差不多,所以下面我只簡要分析這個流程,具體細節可以看文章 《Window的View操作之一:新增View的過程》
 

Step 2:

現在我們知道通過 wm.addView(decor, l) 這一段程式碼就將 DecorView 新增到了遠端的 WindowManagerImpl,但是我們還是不知道 View 在哪裡被繪製,我們接著看到 WindowManagerImpl 中 addView() 方法
★ WindowManagerImpl # addView()

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    // 驗證 LayoutParams是否為 WindowManager的LayoutParmas,同時檢測一下令牌
    applyDefaultToken(params);
    // 呼叫 WindowManagerGlobal的 addView()方法將View新增到 Window
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerImpl 將 addView() 的邏輯交給了 WindowManagerGlobal 類進行處理,那麼我們接著看
★ WindowManagerGlobal # addView()

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    // 檢測View是否為空
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    // 檢測 display是否為空
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    // 再次檢測 LayoutParams是否為 WindowManager的 LayoutParams
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    // 1、將 LayoutParams強轉成 WindowManager的 LayoutParams
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ....
    synchronized (mLock) {
        ....
        // 2、建立ViewRootImpl,每新增一個View到Window,都會在 WindowManagerGlobal中建立
        // 一個 ViewRootImp;也就是說一個 ViewRootImpl對應 Window中的一個 View
        root = new ViewRootImpl(view.getContext(), display);
        // 3、為新增到 Window的View設定 LayoutParams
        view.setLayoutParams(wparams);
        // 4、將 View、對應的ViewRootImpl、對應的LayoutParams新增到相應的集合中進行管理
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try {
        // 5、最後將 View新增到 ViewRootImpl
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        ....
    }
}

第一步:做一些檢測工作,檢測 View 、LayoutParams 等的正確性,然後將 LayoutParams 強轉成 Window 的 LayoutParams,因為待會是要將 View 新增到 Window 中的,也就是要為 View 設定相應的LayoutParams

第二步:建立一個 ViewRootImpl ,新增到 Window 中的 View 都會對應這個一個 ViewRootImpl ,就是這個 ViewRootImpl 負責 View 的繪製,所以說一個Activity 的 DecorView 也對應者一個ViewRootImpl

第三步:為新增到 Window 中的 View 設定 LayoutParams

第四步:將 View、View對應的 ViewRootImpl、View 對應的 LayoutParams 分別新增到 mViews 、mRoots 、mParams 中進行管理,由此可知 WindowManagerGlobal 管理著新增到 Window 中的 View

第五步:最後將 View 新增到 ViewRootImpl,這樣 View 和 ViewRootImpl 就連線起來了
 

Step 3:

到這裡, View 已經初始化一系列的工作,但是還沒有新增到 Window 、也還沒有進行繪製;我們繼續看到 ViewRootImpl 中的 setView() 方法中
★ ViewRootImpl # setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            // 1、將傳遞進來的 View儲存在 ViewRootImpl中的 mView屬性中
            mView = view;
            ....
            // 2、在將View新增到 Window中前先呼叫 requestLayout()方法非同步重新整理佈局
            // 保證在接收系統的其他事件前能進行重新繪製
            requestLayout();
            ....
            try {
                ....
                // 3、將該View新增到遠端的 Session服務中進行顯示
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                ....
            }
            ....
        }
    }
}

現在我們找到答案了,在 ViewRootImpl 中的 setView() 方法中會先進行 View 的繪製,然後將繪製好的 View 新增到 Session 中進行顯示
首先,ViewRootImpl 會將 View 儲存在 mView 屬性中
然後,會呼叫 requestLayout() 方法,這個方法會進行排程 View 的繪製
最後,會呼叫 addToDisplay() 方法將 View 新增到遠端的 Session 服務,讓該服務對 View 進行顯示

小結:
我們知道 View 的繪製發生在 requestLayout() 方法中,這個方法會進行排程 View 的繪製;由原始碼可知,View的繪製是發生在 Activity 的 onResume() 方法之後,也就是說,如果是第一次啟動 Activity ,那麼在 onCreate() 和 onResume() 方法中是無法直接通過 view.getWidth() 、view.getHeight() 方法獲取 View 的寬高的,這一點請注意

好了,現在 View 的繪製、View 新增到 Window 都有了結果;接下來,我們開始分析 View 的繪製入口方法
requestLayout()
 

Step 4:

繼續看到 ViewRootImpl 中的 requestLayout() 方法
★ ViewRootImpl # requestLayout()

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        // 檢測當前執行緒是不是主執行緒
        checkThread();
        // 標記已經請求重新整理佈局了
        mLayoutRequested = true;
        // 呼叫方法去非同步重新整理佈局,繪製View
        // 這個方法會開始排程View的繪製
        scheduleTraversals();
    }
}

首先會檢測一下執行緒,然後就呼叫 scheduleTraversals() 方法開始排程任務去非同步重新整理佈局,看到這個方法
★ ViewRootImpl # scheduleTraversals()

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        // 標記已經排程了 View的繪製任務
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 排程任務開始 View的繪製,View的繪製邏輯儲存在 mTraversalRunnable物件中
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

★ ViewRootImpl.TraversalRunnable # 類結構

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

可見,最終會 post 一個 Runnable 給 mChoreographer ,讓其執行任務,也就是說當 mChoreographer 執行完該任務後就完成了 View 的繪製
 

Step 5:

看到 ViewRootImpl 的 doTraversal() 方法
★ ViewRootImpl # doTraversal()

void doTraversal() {
    // 由於已經在 scheduleTraversals()方法中設定了View繪製任務排程,所以mTraversalScheduled為true
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        // 開始繪製View
        performTraversals();
        ....
    }
}

重點來了:在這個方法中會呼叫 performTraversals() 方法開始繪製 View ,也就是說 View 的繪製入口就是這個 performTraversals() 方法
至此,我們已經知道了 View 的繪製入口的大致流程了,關於 View 繪製的具體流程可以看文章《View的繪製流程之三:View的繪製流程原始碼分析》


三、總結

  • DecorView 會在 Activity 啟動流程中的一個 handleResumeActivity() 方法中被新增到 遠端的 WindowManagerService 中

  • 當 DecorView 成功新增到 WindowManagerService 後就會開始繪製 DecorView ,同時也會開始繪製 Activity 的佈局;也就是說 Activity 的佈局會在這個時候 Activity 的 onResume() 方法之後進行繪製

  • 由於 View 的繪製發生在 Activity 的 onResume() 方法之後,所以如果是第一次啟動 Activity ,那麼在 onCreate() 和 onResume() 方法中是無法直接通過 view.getWidth() 、view.getHeight() 方法獲取 View 的寬高的

  • 在 ViewRootImpl 的 setView() 方法中會呼叫 requestLayout() 方法進行 View 繪製任務的排程,這個任務就是 ViewRootImpl的內部類 TraversalRunnable,在這個類的 run()方法中會呼叫 doTraversal() 方法,然後這個方法會呼叫 performTraversals() 方法進行 DecorView 以及 Activity 的 Layout 佈局的繪製;所以 ViewRootImpl中的 performTraversals()方法就是 View的繪製的入口方法

  • 在 ViewRootImpl 的 setView() 方法中會呼叫 addToDisplay() 方法將 View 新增到遠端的 Session 服務,讓該服務對 View 進行顯示

★ Activity佈局繪製的源頭流向:

ActivityThread.handleResumeActivity()
ViewManager.addView()
WindowManagerImpl.addView()
WindowManagerGlobal.addView()
ViewRootImpl.setView()
ViewRootImpl.requestLayout()
ViewRootImpl.scheduleTraversals()
ViewRootImpl.TraversalRunnable.run()
ViewRootImpl.doTraversal()
ViewRootImpl.performTraversals()