
這篇文章我不會去走一遍這幾位的源碼,只是提出幾個(gè)關(guān)于他們的問題,附上我的理解,是自己的一個(gè)筆記,也希望能幫到有同樣困惑的同學(xué)。
WindowManagerGlobal跟ViewRootImpl跟DocerView的關(guān)系
這個(gè)問題其實(shí)網(wǎng)上很多人寫過了,這里分享一下我的理解:
WindowManagerImpl中持有了WindowManagerGlobal,也就是實(shí)際邏輯都是用的WindowManagerGlobal,而WindowManagerGlobal是單例的,其中維護(hù)了
//WindowManagerGlobal.java
@UnsupportedAppUsage
private final ArrayList<View> mViews = new ArrayList<View>();
@UnsupportedAppUsage
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
里面按照順序存了整個(gè)app中的View和相對應(yīng)的ViewRootImpl,所以Global這個(gè)名字就是顧名思義,整個(gè)app就一個(gè)全局的,維護(hù)了所有的View,需要就來這里取并且分發(fā)對應(yīng)的ViewRootImpl對對應(yīng)的View做對應(yīng)的操作。
但是這里有個(gè)問題,mViews存在的意義是什么呢?我可以直接通過RootViewImpl拿到對應(yīng)的DecorView啊~
一個(gè)app可以存在幾個(gè)WindowManagerImpl,幾個(gè)WindowManagerGlobal? 有幾個(gè)DecorView,幾個(gè)ViewRootImpl,幾個(gè)PhoneWindow?
看了上面的問題,其實(shí)我們知道的是WindowManagerImpl可以存在多個(gè),很多使用的地方都可以拿到,可以直接new或者(WindowManagerImpl)outerContext.getSystemService(WINDOW_SERVICE);拿到,但是實(shí)際實(shí)現(xiàn)類WindowManagerGlobal是單例的。
而且既然知道了WindowManagerGlobal有mViews和mRoots,其實(shí)我們可以猜到DecorView和ViewRootImpl應(yīng)該也是存在多個(gè)的,但是他們之間是什么關(guān)系呢?
既然WindowManagerGlobal是個(gè)單例,那我們應(yīng)該可以反射調(diào)用它的getInsatance()方法獲取全局的對象,那同樣也可以得到mViews跟mRoots的值,我們發(fā)現(xiàn)ViewRootImpl中有一個(gè)mWindow的變量應(yīng)該是PhoneWindow,那我們同樣可以反射獲取到。
紙上得來終覺淺,讓我們寫個(gè)代碼來驗(yàn)證一下,一個(gè)Activity中存在一個(gè)Dialog和一個(gè)PopupWindow的時(shí)候是怎么表現(xiàn)的。
public static void testWindowManagerGlobal() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Class clazz = Class.forName("android.view.WindowManagerGlobal");
Field mViewsField = clazz.getDeclaredField("mViews");
mViewsField.setAccessible(true);
Field mRootsField = clazz.getDeclaredField("mRoots");
mRootsField.setAccessible(true);
Field mParamsField = clazz.getDeclaredField("mParams");
mParamsField.setAccessible(true);
Method instanceMedthod = clazz.getMethod("getInstance");
Object mGlobal = instanceMedthod.invoke(null);
Object mViews = mViewsField.get(mGlobal);
Object mRoots = mRootsField.get(mGlobal);
if (mViews instanceof List) {
for (Object o : (List) mViews) {
printDecorView(o);
}
}
if (mRoots instanceof List) {
for (Object o : (List) mRoots) {
printViewRootImpl(o);
}
}
}
public static void printViewRootImpl(Object viewRootImpl) throws NoSuchFieldException, IllegalAccessException {
Field mViewField = viewRootImpl.getClass().getDeclaredField("mView");
mViewField.setAccessible(true);
Object mViewObject = mViewField.get(viewRootImpl);
LogUtil.d("printViewRootImpl->" + viewRootImpl + " View:" + mViewObject.toString());
}
public static void printDecorView(Object decorView) throws IllegalAccessException {
StringBuffer sb = new StringBuffer(decorView.toString());
Field mWindowField = null;
try {
mWindowField = decorView.getClass().getDeclaredField("mWindow");
mWindowField.setAccessible(true);
Object mWindowObject = mWindowField.get(decorView);
sb.append(" PhoneWindow->").append(mWindowObject.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
sb.append(" PhoneWindow->").append(decorView.getClass().toString());
}
LogUtil.d("printDecorView->:" + sb.toString());
}
打印結(jié)果如下:
D: printDecorView->:DecorView@b67e275[MainActivity] PhoneWindow->com.android.internal.policy.PhoneWindow@db9420a
D: printDecorView->:DecorView@7bc5a7b[TestWindowActivity] PhoneWindow->com.android.internal.policy.PhoneWindow@4e63098
D: printDecorView->:DecorView@66130f1[TestWindowActivity] PhoneWindow->com.android.internal.policy.PhoneWindow@fb174d6
D: printDecorView->:android.widget.PopupWindow$PopupDecorView{3ac2357 V.E...... R.....I. 0,0-0,0} PhoneWindow->class android.widget.PopupWindow$PopupDecorView
D: printViewRootImpl->android.view.ViewRootImpl@dc6eb2d View:DecorView@b67e275[MainActivity]
D: printViewRootImpl->android.view.ViewRootImpl@b280862 View:DecorView@7bc5a7b[TestWindowActivity]
D: printViewRootImpl->android.view.ViewRootImpl@659df3 View:DecorView@66130f1[TestWindowActivity]
D: printViewRootImpl->android.view.ViewRootImpl@e567ab0 View:android.widget.PopupWindow$PopupDecorView{3ac2357 V.E...... R.....I. 0,0-0,0}
那我們根據(jù)結(jié)果看來:
-
WindowManagerGlobal的mViews和mRoots管理對應(yīng)的DecorView和ViewRootImpl,DecorView和ViewRootImpl是一一對應(yīng)的。 - Acticity跟Dialog都有自己一一對應(yīng)的 DecorView-ViewRootImpl-PhoneWindow,PopupWindow也會有自己的DecorView,區(qū)別是popupWindow的DecorView是
android.widget.PopupWindow$PopupDecorView,PopupWindow并沒有對應(yīng)的PhoneWindow。
所以,結(jié)論就是一個(gè)app可以存在多個(gè)WindowManagerImpl,有且只有一個(gè)WindowManagerGlobal,多個(gè)DecorView,多個(gè)ViewRootImpl,多個(gè)PhoneWindow。DecorView跟ViewRootImpl的數(shù)量是相同的而且是一一對應(yīng)的,但是PhoneWindow的數(shù)量可能會少于ViewRootImpl的數(shù)量.
ViewRootImpl是怎么成為root的?
這里我們需要知道一個(gè)接口ViewParent,顧名思義,它是所有的ViewGroup都需要實(shí)現(xiàn)的一個(gè)接口,這樣來實(shí)現(xiàn)view樹的一個(gè)層級結(jié)構(gòu)。
我們先看一下普通的view是怎么設(shè)置mParent的呢?代碼在ViewGroup:
//ViewGroup.java
// Child views of this ViewGroup
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
private int mChildrenCount;
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
...
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
...
}
在addView的時(shí)候代碼設(shè)置了child的mParent是自己,設(shè)置了childview的mParent是當(dāng)前ViewGroup,同時(shí)把childview放到mChildren屬性里面,這樣就關(guān)聯(lián)起來了。
requestLayout也是ViewParent的一個(gè)方法,大部分的ViewGroup以及LinearLayout、RelativeLayout都沒有重寫requestLayout方法,都是使用的View的requestLayout定義:
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
...
}
一直調(diào)用parent的requestLayout方法,既然ViewGroup都沒有重寫,那就得往上一直到最頂層的view,需要關(guān)注最頂層的view的requestLayout方法都是怎么寫的,那最頂層的View是誰呢?首先想到DecorView,但是DecorView也并沒有重寫requestLayout。那還有誰呢?我們發(fā)現(xiàn)ViewRootImpl其實(shí)并沒有繼承自View,但是它竟然實(shí)現(xiàn)了requestLayout方法!
所以不是View出身的ViewRootImpl到底是怎么成為一眾View的爹的呢?
也就是ViewRootImpl是怎么成為DecorView的parent的,是怎么關(guān)聯(lián)起來的?
調(diào)用棧如下(不畫圖了,沒幾行):
ActivityThread:handleResumeActivity
|- WindowManagerGlobal:addView
|- ViewRootImpl:setView
view.assignParent(this);
ViewRootImpl#setView調(diào)用了view.assignParent(this);,將自己設(shè)置成了view也就是DecorView的parent。
其實(shí)最頂層的View還是DecorView,ViewRootImpl只是說實(shí)現(xiàn)了ViewParent方法用來做View的事件處理跟分發(fā)。
ViewRootImp 很重要,它有2個(gè)主要作用:
- 實(shí)現(xiàn) android 的 View 系統(tǒng)的邏輯,發(fā)起布局、繪制等操作;
- 連接 應(yīng)用窗口 和 服務(wù)端 WMS 的橋梁。它里面有一個(gè)內(nèi)部類 H 實(shí)現(xiàn)了 IWindow 接口(Bn端)。對應(yīng)就是 WMS 里面 WindowState 當(dāng)中的 mClient(Bp端)。
setContentView發(fā)生了什么
第一個(gè)Hello World就知道需要把布局用一個(gè)setContentView()方法設(shè)置進(jìn)去就可以顯示,那到底這個(gè)方法發(fā)生了什么? 為什么一些Window相關(guān)的flag必須要在setContentView()之前設(shè)置,否則就不起作用呢?
我們先大致走完setContentView的整個(gè)流程,再來把每個(gè)我們關(guān)注的細(xì)節(jié)逐個(gè)擊破。
#Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
public void setContentView(View view) {
getWindow().setContentView(view);
}
...
public Window getWindow() {
return mWindow;
}
而mWindow是什么呢?我們搜mWindow =關(guān)鍵字,發(fā)現(xiàn)在Activity的attach(...)方法里面,mWindow = new PhoneWindow(this, window, activityConfigCallback);
也就是說實(shí)際上調(diào)用了PhoneWindow的setContentView方法。
我們用AndroidStudio查看源碼的時(shí)候可能查看不了PhoneWindow,因?yàn)樗?code>com.android.internal包下的,也就是所謂的隱藏API,不希望coder調(diào)用的,但是并不耽誤我們查看并理解它的原理。
我們查找的時(shí)候只需要雙擊Shift并勾選右上角Include Non-project items
PhoneWindow.java
public void setContentView(View view, ViewGroup.LayoutParams params) {
...
if (mContentParent == null) {
installDecor();
}
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
這里我們只看關(guān)鍵代碼:
上面的代碼判斷如果mDecor==null就去創(chuàng)建DecorView,DecorView是Activity的一個(gè)rootView,不存在的時(shí)候直接new出來,然后判斷mContentParent是否為null,在generateLayout(mDecor)中創(chuàng)建mContentParent,
也就是android.R.id.content這個(gè)我們耳熟能詳?shù)膇d,后面會根據(jù)類型再去創(chuàng)建R.id.decor_content_parent或者R.id.title。
installDecor做完了之后會繼續(xù)執(zhí)行mLayoutInflater.inflate(layoutResID, mContentParent);,這個(gè)我們就很熟悉了,也就是把 layoutResID對應(yīng)的xml加載成view并add到mContentParent下面。