Android筆記(四)DecorView & ViewRootImpl & Window

這三者之間的關係網絡上有很多篇不錯的blog。比如這篇。爲了讓自己更加熟悉View繪製的流程,在這裏特意將這三者的關係好好梳理一番。
爲了理清這三者的關係,還是先上一張圖
這裏寫圖片描述

  1. ViewRootImpl是ViewRoot的實現類,ViewRoot不是view,而是整個ViewTree的管理者。
  2. DecorView是整個ViewTree的根佈局視圖
  3. ViewRoot通過在Activity.attach()中將DecorView綁定到Activity對應的Window中,在addView時涉及到跨進程通信
  4. 這裏的跨進程通信主要就是ViewRoot和WMS之間通信,通過傳遞IWindowSession和IWindow來完成。

按照個人理解,在window起來的時候,先完成的工作是得到一個最初的View,用作最底層的view(或者說放在Window裏面作爲根view,事實上不存在這種將view放置在window中的說法,因爲window只是一個虛擬的窗口),這個view也就是我們通常說的DecorView。得到這個DecorView之後,接下來的工作就是通過addView將這個View呈現出來,這裏纔會涉及到ViewRootImpl,這樣DecorView纔算是真正與window綁定在一起了。

Window創建過程

PhoneWindow是Window的實現類,裏面幾個和DecorView相關的屬性

ViewGroup mContentParent;//ViewGroup,也就是我們的R.id.content
TextView mTitleView;//也就是我們的R.id.title
DecorContentParent mDecorContentParent; //接口,用來設置一些window的屬性

我們以Activity的啓動創建Window爲例來進行說明,除此之外Dialog、Toast等在啓動時也會創建Window。
我們知道,在啓動一個activity時,activity中會走到onCreate()方法中執行

setContentView(R.layout_main);

這個方法實際上做了下面的事情

getWindow().setContentView(R.layout.main);//getWindow()就是獲取mWindow對象,
//這個對象怎麼來的?說明在onCreate()之前就已經new PhoneWindow()了。
//但具體在哪兒,我們留作彩蛋一
initWindowDecorActionBar();

Activity的啓動較爲複雜,後面會專門花時間來講解。Acitvity的啓動最終都會由ActivityThread的handleLaunchActivity()來完成啓動。(這裏囉嗦一句,下面的代碼已經是在Activity所在進程中執行的了,從AMS到Activity這之間的過程本文暫不分析,大致就是ApplicationThread作爲媒介在AMS和Activity進程之間的交互了,最終在用戶進程中執行下面這些操作)。
先是調用handleLaunchActivity(),再在裏面執行performLaunchActivity()

//ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ``````
    //獲取WindowManagerService的Binder引用(proxy端)。
    WindowManagerGlobal.initialize();

    //會調用Activity的onCreate,onStart,onResotreInstanceState方法,彩蛋二
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        ``````
        //會調用Activity的onResume方法.
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        ``````
    } 

}

其中performLaunchActivity中做的重要事情就是1.創建Activity;2.將activity.atttch();3在attach()中創建window對象

...
//通過反射創建Acitivity
ClassLoader cl = r.packageInfo.getClassLoader();
activity=mInstrumentation.newActivity(cl,component.getClassName(),r.intent);
...
//在activity中創建window對象
activity.attach(appContext,this,getInstrumentation(),r.token,..window);
...省略號

在Activity中的attach方法中,完成了 Activity中window的創建,而且設置相關回調接口,包括我們常見的onAttachedToWindow、dispatchTouchEvent()等。此外還將ActivityThread中的信息傳遞到Activity

attachBaseContext();
mWindow = new PhoneWindow(this,window);
mWindow.setCallback(this);
...
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;

這樣我們就在啓動一個Acitivity時,創建好了一個window。但此時window中啥都沒有。但可以解釋上面的彩蛋一。創建window到Activity執行onCreate()調用setContentView()之間經歷了什麼?
其實上面performLaunchActivity()的流程到了attach()之後,還有其他的操作,下面接着寫attach()完之後performLaunchActivity()還做了啥

if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
...
if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
...

原來是通過mInstrumentation間接執行了Activity中的onCreate(),onRestoreInstaneState()等方法,這樣就可以解開彩蛋二的謎底了。此外,mInstrumentation是在上一步attach()中創建Window時創建的。

我們在activity中解析xml文件,添加view到window是怎樣做到的呢。首先,我們需要想到的是,一個window對應一個根view。這個view就是DecorView。添加view的過程在setContentView()中完成。包括創建DecorView,添加view,顯示view。
Activity中的setContentView()方法

public void setContentView(View view,ViewGroup.LayoutParams params){
    getWindow().setContentView(view,params);
    initWindowDecorActionBar();
}

Window的具體實現是PhoneWindow。因此我們只需要看PhoneWindow中的相關邏輯。

在PhoneWindow的setContentView方法中,如果沒有DecorView,那就創建一個。

//mContentParent是DecorView的子佈局ViewGroup,也就是我們的R.id.content
if(mContentParent = null){
    installDecor();
}

然後解析我們的xml或者加載view到mContentParent上,其中mLayoutInflater在PhoneWindow構造時被賦值

mLayoutInflater.inflate(layoutResID,mContentParent);

執行完上面這些,我們其實已經走過了Activity聲明週期的onCreate() onStart()方法。此時我們已經

  • 在attach()方法中 創建了window
  • 在setContentView()中將view添加到了window。

完成這些後,還只是完成了Window的創建以及與Activity的綁定,還沒有將Window添加到WMS中,所以還不會顯示出來(mDecorView還沒有被wms識別)。
讓我們在次回到handleLaunchActivity方法中,發現上面的工作做了這麼多,其實也還只是執行了performLaunchActivity(),而顯示流程則需要接下來的handleResumeActivity()方法來完成。handleResumeActivity的核心代碼如下

...
performResumeActivity();彩蛋三
...
if(a.mVisibleFromClient && !a.mWindowAdded){
    a.mWindowAdded = true;
    wm.addView(decor,l);
}
...

從方法命名我們大概可以猜出,performResumeActivity()中一定是執行了mInstrumentation.callActivityOnResume()操作,從而回調Activity中的onResume()方法顯示。完成這後,後面會做一個很重要的操作,就是

wm.addView(decor,l);//有木有很熟悉,我們自己創建Window的時候
//是不是也是這樣子直接wm.addView(view,l);

這樣我們就把根佈局”作爲一個窗口”加載到了windowManager中.
到了這一步之後,window總算是給添加到WMS了。要分析WMS是如何添加window的,就得硬着頭皮往WMS中分析了。從繼承關係我們得知addView操作最終執行的是

//WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

接着往裏看

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //創建ViewRootImpl,作爲WMS管理view/window的橋樑
    ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);    
    root.setView(view, wparams, panelParentView);//最終的顯示靠這個,彩蛋4
    ...
}

ViewRootImpl中持有WMS端的Session代理對象

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    //獲取IWindowSession的代理類【見小節2.8.1】
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mDisplay = display;
    mThread = Thread.currentThread(); //主線程
    mWindow = new W(this); 
    mChoreographer = Choreographer.getInstance();
    ...
}

最終的顯示要靠root.setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  synchronized (this) {
    ...
    //通過Binder調用,進入system進程的Session
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
          getHostVisibility(), mDisplay.getDisplayId(),
          mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
          mAttachInfo.mOutsets, mInputChannel);
    ...
  }
}

進一步就是WMS的Display部分了,這裏不做更深的研究。就此打住!!!

DecorView介紹

DecorView爲頂級View,事實上也具有普通View的特性,其佈局爲FrameLayout佈局。以前的版本中,DecorView是PhoneWindow的內部類,PhoneWindow是Window抽象類的唯一實現,足以表示兩者的關係。不過最新的版本中DecorView已經獨立成了一個普通類,裏面有三個最重要的屬性
這裏寫圖片描述

  • mWindow 窗口,後續加載content內容用到

  • mContentRoot 用於放置R.id.content的ViewGroup

  • mDecorCaptionView 和多窗口相關

這樣我們就清楚地知道了DecorView裏面的佈局。對於普通Activity這兩這個子View(mContentRoot mDecorCaptionView都有,對於沒有多窗口的,則只有mContentRoot)。將view添加到DecorView主要是靠下面這個方法onResourceLoaded()
這裏寫圖片描述

layoutResource爲我們傳進來的R.layout.xxx資源。先創建mDecorCaptionView
這裏寫圖片描述
第1992行清楚地向我們顯示了DecorCaptionView的加載。再回到上一幅圖,加載完DecorCaptionView之後就加載mContentRoot(01919行所示),然後需要做的事情就是先判斷mDecorCaptionView是否爲空,如果爲空則只把mContentRoot直接添加到DecorView中。如果不爲空則先把mDecorCaptionView添加到DecorView,然後把mContentRoot添加到mDecorCaptionView中。大致的圖如下
這裏寫圖片描述
到這裏我們就完成了整個DecorView的構造,下面就要進行其放置過程。事實上,DecorView的構造是在PhoneWindow中進行的。大概流程爲:Activity中的setContentView最終會調用到PhoneWindow中的setContentView。在這個方法中首先就要判斷DecorView是否存在,不存在就創建。DecorView的添加是在ActivityThread中的handlerResumeActivity添加的。

wm.addView(decor,l);

從這兒就可以看出Activity中onCreate在onResume方法之前,先創建DecorView,然後放置DecorView。WindowManager是一個抽象類,我們需要在其實現類WindowManagerImpl中去查看addview方法。結果發現真正調用的是WindowManagerGlobal類中的addView方法
這裏寫圖片描述
也就是說,RootViewImpl和DecorView上下文一一對應。
這裏寫圖片描述
從上面可以看出真正的添加DecorView操作是在這兒,調用ViewRootImpl.setView方法,在這個方法內部,會通過跨進程的方式向WMS(WindowManagerService)發起一個調用,從而將DecorView最終添加到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此關聯,至於詳細過程這裏不展開來說了。完成了

  • mViews.add(view);
  • mRoots.add(root);
  • mParams.add(wparams)
  • root.setView()
    之後,會在root.setView()中調用requestLayout()開始遍歷

最後通過WMS調用ViewRootImpl#performTraverals方法開始View的測量、佈局、繪製流程。
後面將會講解ViewTree遍歷的時機

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章