這三者之間的關係網絡上有很多篇不錯的blog。比如這篇。爲了讓自己更加熟悉View繪製的流程,在這裏特意將這三者的關係好好梳理一番。
爲了理清這三者的關係,還是先上一張圖
- ViewRootImpl是ViewRoot的實現類,ViewRoot不是view,而是整個ViewTree的管理者。
- DecorView是整個ViewTree的根佈局視圖
- ViewRoot通過在Activity.attach()中將DecorView綁定到Activity對應的Window中,在addView時涉及到跨進程通信
- 這裏的跨進程通信主要就是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遍歷的時機