WindowManager、ViewRootImpl、DocerView幾個(gè)問題的理解

這篇文章我不會去走一遍這幾位的源碼,只是提出幾個(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是單例的。
而且既然知道了WindowManagerGlobalmViewsmRoots,其實(shí)我們可以猜到DecorViewViewRootImpl應(yīng)該也是存在多個(gè)的,但是他們之間是什么關(guān)系呢?
既然WindowManagerGlobal是個(gè)單例,那我們應(yīng)該可以反射調(diào)用它的getInsatance()方法獲取全局的對象,那同樣也可以得到mViewsmRoots的值,我們發(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é)果看來:

  1. WindowManagerGlobal的mViews和mRoots管理對應(yīng)的DecorView和ViewRootImpl,DecorView和ViewRootImpl是一一對應(yīng)的。
  2. 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è)PhoneWindowDecorViewViewRootImpl的數(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è)主要作用:

  1. 實(shí)現(xiàn) android 的 View 系統(tǒng)的邏輯,發(fā)起布局、繪制等操作;
  2. 連接 應(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下面。

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

友情鏈接更多精彩內(nèi)容