由setContentView()方法引發的思考

Android的setContentView()方法咱們平時用不少,可是有多少人會點進setContentView()方法裏面看看它的源碼到底是何方神聖呢,今天我就來看看從這個方法裏面究竟涉及到多少未知的知識。android

public class ViewActivity extends Activity {


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view);
    }
}
複製代碼

懷着好奇心我點下了setContentView()這個方法去尋根索源:bash

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
複製代碼

getWindow().setContentView(layoutResID)這是什麼鬼,而後點進去看看:app

個人天,居然看到setContentView()是一個叫Window類的抽象方法,Window相信每一個人都聽過,可是對於Android的Window相信不是全部人都瞭解,我也是同樣,而後我帶着問題翻閱了書本。

Windowide

摘自來自《Android開發藝術探索》的解釋:佈局

Window表示一個窗口的概念,在平常開發中直接接觸Window的機會並很少,可是在某些特殊時候咱們須要在桌面上顯示一個相似懸浮窗的東西,那麼這種效果就須要用到Window來實現。Window是一個抽象類,他的具體實現是PhoneWindow。建立一個Window是很簡單的事,只須要經過WindowManager便可完成。WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。Android全部的視圖都是經過Window來呈現的,不論是Activity、Dialog仍是Toast,他們的視圖實際上都是附加在Window上的,所以Window其實是View的直接管理者。post

IPC:Inter-Process Communication的縮寫,含義爲進程間通訊或者跨進程通訊,是指兩個進程之間進行數據交換的過程。ui

看了一大輪的文字概念,我就想睡覺了。可是看了那麼久,總算幾個關鍵詞PhoneWindowWindowManagerWindowManagerService。上面講到PhoneWindow是Window的實現類,那麼咱們先去看看PhoneWindow吧。this

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        ...
    }
複製代碼

在PhoneWindow確實找到了setContentView()方法的具體實現。 mContentParent是什麼?spa

ViewGroup mContentParent;
複製代碼

暫時還不知道它是什麼,那咱們當它是null吧,進入installDecor()方法看看:code

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                ...
                下面省略了一大堆UiOptions,setIcon,Transition的方法
            } else {
                ...
            }

            
    }
複製代碼

mDecor是什麼?

private DecorView mDecor;
複製代碼

DecorView是什麼? 書本是這樣寫的: ViewRoot對應於ViewRootImpl類,它是鏈接WindowManagerDecorView的紐帶,View的三大流程(onMeasure(),onLayout(),onDraw())均是經過ViewRoot來完成的。在ActivityThread中,當Activity對象被建立完畢後,會將DecorView添加到Window中,同時會建立ViewRootImpl對象,並將ViewRootImpl對象和DecorView創建關聯

我真的醉了,越翻越多本身不懂的概念出來:ViewRoot,ViewRootImpl,如今暫且作個筆記吧,先無論了。先看看咱們找到的線索:

當Activity對象被建立完畢後,會將DecorView添加到Window中.

這就是咱們要找的東西。DecorView原來是這樣用的。

回到installDecor()中,當mDecor爲空時,調用generateDecor(-1)方法:

protected DecorView generateDecor(int featureId) {
        ...
        return new DecorView(context, featureId, this, getAttributes());
    }
複製代碼

這裏建立了一個DecorView了,咱們發現DecorView實際上是一個FrameLayout,再回到installDecor(),這時候咱們知道mContentParent仍然爲null,那麼進入generateLayout(mDecor)方法:

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        //根據當前設置的主題來加載默認佈局
        TypedArray a = getWindowStyle();
        ...
        設置各類各樣的屬性
        ...
        
        //若是你在theme中設置了window_windowNoTitle,則這裏會調用到,其餘方法同理,
        //這裏是根據你在theme中的設置去設置的
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            
            requestFeature(FEATURE_ACTION_BAR);
        }
       
        //是否有設置全屏
        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        ...

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } //省略其餘判斷方法
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        //選擇對應佈局建立添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
    }
複製代碼

首先generateLayout會根據當前用戶設置的主題去設置對應的Feature,接着,根據對應的Feature來選擇加載對應的佈局文件,(Window.FEATURE_NO_TITLE)接下來經過getLocalFeatures來獲取你設置的feature,進而選擇加載對應的佈局,這也就是爲何咱們要在setContentView以前調用requesetFeature的緣由。

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
複製代碼

咱們還能看到contentParent實際上是一個叫com.android.internal.R.id.content的佈局,最後添加到DecorView上。generateLayout()方法最後返回的就是contentParent。好了,installDecor()方法走完了,建立了DecorView和在上面設置了一大堆屬性,並建立帶來了一個mContentParent,再回到PhoneWindow的setContentView()中

@Override
    public void setContentView(int layoutResID) {
        
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //把mContentParent加載到mLayoutInflater中,
            //而mLayoutInflater在上面generateLayout(DecorView decor)方法中
            //早已加載到DecorView中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //回調通知表示完成界面改變
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
複製代碼

此時已經建立完DecorView而且獲取到mContentParent,接着就是將你setContentView的內容添加到mContentParent中,也就是

mLayoutInflater.inflate(layoutResID, mContentParent);
 或者
 mContentParent.addView(view, params);
複製代碼

來到這裏該總結一下了:

真是千言萬語都在這圖中了,網上盜的圖真是萬能的,個人總結都在這個圖中了。 我之前一直不懂爲何setContentView()叫setContentView而不叫setView呢,那是由於咱們所建立的佈局實際上是Activity裏面的PhoneWindow建立出來的DecorView裏面的ContentView來的而已。一開始覺得你是老大,如今才發現你是個小弟大概就是這種感受吧。

等等,雖然知道setContentView()方法是怎麼來的,可是在看它的源碼中,咱們還發現了好幾個疑問:WindowManagerViewRootViewRootImplPhoneWindowWindowManagerService。他們幾個的關係又是怎麼個錯綜複雜呢?拿着這些線索,咱們下一篇文章再來探個究竟吧。

個人掘金: juejin.im/user/594e8e…

個人簡書: www.jianshu.com/u/b538ca57f…

相關文章
相關標籤/搜索