【Android 源碼解析】一、setContentView 初探

最近在 CSDN 看了某大神的幾篇源碼解析的文章,自己再回顧整理一遍。

一、從 Activity 的 setContentView 開始

Activity 提供了三個(gè)重載的 setContentView 方法

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

可以看到 Activity 的 setContentView 內(nèi)部都先調(diào)用了 getWindow 的 setContentView 方法,然后調(diào)用了 Activity 的 initWindowDecorActionBar 方法。

二、Window 類

getWindow 方法返回的是 Activity 的 Window 類的成員變量 mWindow 。
這里簡要介紹一下 Window 類:

  • Window 類是一個(gè)抽象類,它的唯一實(shí)現(xiàn)類是 PhoneWindow;
  • PhoneWindow 有一個(gè)內(nèi)部類 DecorView,DecorView 是 Activity 的根 View;
  • DecorView 繼承自 FramLayout;

三、PhoneWindow 的 setContentView 方法

Window 類的 setContentView 方法都是抽象的,直接看 PhoneWindow 類的 setContentView 方法。

1、setContentView(int layoutResID)

第一個(gè)方法傳入的參數(shù)是布局的資源 ID:

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();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

在 setContentView 方法中首先判斷成員變量 mContentParent 是否為 null,如果是第一次調(diào)用,mContentParent 為 null,調(diào)用 PhoneWindow 的 installDecor 方法,如果 mContentParent 不為 null,則判斷是否設(shè)置 FEATURE_CONTENT_TRANSITIONS 的 Window 屬性(默認(rèn)false),如果沒有設(shè)置該屬性就移除 mContentParent 內(nèi)所有的所有子View;

1.1、PhoneWindow 類的 installDecor 方法

那么 installDecor 方法里面做了 什么呢?

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            //根據(jù)窗口的風(fēng)格修飾,選擇對應(yīng)的修飾布局文件,并且將id為content的FrameLayout賦值給mContentParent
            mContentParent = generateLayout(mDecor);
            //......
            //初始化一堆屬性值
        }
    }

在 installDecor 方法里上來并沒有先處理 mContentParent,而是先判斷 mDecor 成員變量是否為 null,如果 mDecor 為 null,就調(diào)用 generateDecor 方法給 mDecor 賦值,generateDecor 的代碼很簡單:

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

就是通過 DecorView 的構(gòu)造方法 new 了一個(gè) DecorView 對象返回。 所以說 PhoneWindow 的 mDecor 是 DecorView 類的成員變量,也就是所有內(nèi)容的根 View。

1.2、generateLayout 方法

此時(shí) mDecor 不為 null 了,如果 mContentParent 為 null,則調(diào)用 generateLayout 方法創(chuàng)建 mContentParent :

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        //......
        //依據(jù)主題style設(shè)置一堆值進(jìn)行設(shè)置

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        //......
        //根據(jù)設(shè)定好的features值選擇不同的窗口修飾布局文件,得到layoutResource值

        //把選中的窗口修飾布局文件添加到DecorView對象里,并且指定contentParent值
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        //......
        //繼續(xù)一堆屬性設(shè)置,完事返回contentParent
        return contentParent;
    }

在 generateLayout 方法里面首先根據(jù)應(yīng)用主題 style 設(shè)置一堆值進(jìn)行設(shè)置,我們設(shè)置的 android:theme 屬性都是在這里的 getWindowStyle 方法中獲取的,而我們在代碼中通過 requestWindowFeature() 設(shè)置的屬性是在 getLocalFeature 方法中獲取的,這也是為什么 requestWindowFeature() 代碼要在 setContentView() 前面執(zhí)行。
然后根據(jù)設(shè)定好的 features 值選擇不同的窗口修飾布局文件,得到布局文件的 layoutResource 值,LayoutInflater 把布局的資源文件解析成 View 之后,添加到 DecorView 中,這個(gè) View 就是 PhoneWindow 的 mContentRoot 成員變量,而 mContentParent 就是布局文件中 ID 為 @android:id/content 的 FramLayout。
再回到 setContentView 方法中,如果 Window 沒有設(shè)置 FEATURE_CONTENT_TRANSITIONS 的話,就通過 LayoutInflater 把布局文件加載到 mContentParent 中。

2、setContentView(View view) 和 setContentView(View view,ViewGroup.LayoutParams params) 方法

一個(gè)參數(shù)的方法也是調(diào)用了兩個(gè)參數(shù)的方法,只是 params 參數(shù)直接設(shè)置為 MATCH_PARENT。

@Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

可以看到與參數(shù)為 layoutResID 的方法不同之處在于直接調(diào)用了 ViewGroup 的 addView 方法將布局加載到 mContentParent 上面。
加載完 View 后,兩個(gè)方法最后都調(diào)用了 Callback 的 onContentChanged 方法來通知對應(yīng)的 Activity 視圖內(nèi)容發(fā)生了變化。getCallback 方法返回的是 Window 的 mCallback 成員變量,這個(gè)成員變量是通過 setCallback 方法進(jìn)行賦值的,毫無疑問,Activity 實(shí)現(xiàn)了這個(gè)接口,并且在 attach 方法中通過 mWindow.setCallback(this) 進(jìn)行設(shè)置,Activity 的 onContentChanged 方法是一個(gè)空方法,當(dāng) Activity setContentView 或者 addContentView 時(shí)會調(diào)用該方法。

3、Activity 的 initWindowDecorActionBar

private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }

至于 Window 的 setContentView 方法執(zhí)行完了之后的 initWindowDecorActioonBar 方法就是創(chuàng)建一個(gè) Actionbar 并設(shè)置一些默認(rèn)顯示等。
Activity 調(diào)運(yùn) setContentView 方法自身不會顯示布局的,一個(gè) Activity 的開始實(shí)際是 ActivityThread 的 main 方法,當(dāng)啟動 Activity 調(diào)運(yùn)完 ActivityThread 的 main 方法之后,接著調(diào)用 ActivityThread 類 performLaunchActivity 來創(chuàng)建要啟動的 Activity 組件,在創(chuàng)建 Activity 組件的過程中,還會為該 Activity組件創(chuàng)建窗口對象和視圖對象;接著 Activity 組件創(chuàng)建完成之后,通過調(diào)用 ActivityThread 類的 handleResumeActivity 將它激活。
在 handlerResumeActivity 中調(diào)用 Activity 的 makeVisible 方法顯示我們上面通過 setContentView 創(chuàng)建的 mDecor 視圖族。

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

參考:
Android應(yīng)用setContentView與LayoutInflater加載解析機(jī)制源碼分析

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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