夜夜使用的setContentView()里面到底做了什么?

一、寫在前面

我們都知道繼承Activity的onCreate()方法然后setContentView(R.layout.xxx)可以設置我們自己的布局文件,但是布局文件生成的View到底添加到哪里去了呢?首先在Activity這個類中我們可以看到有個mWindow變量,這個變量是在attach()方法里面創(chuàng)建的:mWindow = new PhoneWindow(...),然后在我們setContentView()的時候會創(chuàng)建一個mDecor變量,這就是我們Activity的第一個View,然后再往里面添加各種View,包括我們自己布局的View。此文就是分析下這些View都是怎么添加的。

以前的以前我們自己的Activity都是直接繼承Activity這個類的,從API21開始,我們一般都繼承AppCompatActivity了,下面分別分析。

注:以下源碼版本為API28。

二、繼承Activity的時候

// 這是我們普通的Activity,繼承onCreate,添加我們的布局
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.xxx);
}

// 類:Activity
public void setContentView(@LayoutRes int layoutResID) {
    // 跟蹤getWindow()可以看到mWindow = new PhoneWindow(...),返回的是一個PhoneWindow
    // 所以這里跳到PhoneWindow的setContentView(...)
    // IDEA里面可以直接按ctrl+alt+單擊彈出實現(xiàn)此方法的類,選擇PhoneWindow的進入就行
    getWindow().setContentView(layoutResID);
    // ...
}

// 類:PhoneWindow
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    }
    // ...
    // 把我們自己的布局文件inflate后添加到mContentParent中
    // mContentParent就是我們自己的布局的父View
    mLayoutInflater.inflate(layoutResID, mContentParent);
    // ...
}

// 類:PhoneWindow
private void installDecor() {
    // ...
    if (mDecor == null) {
        // 1、生成DecorView
        // mDecor是PhoneWindow的一個變量,同時PhoneWindow也是mDecor的一個變量
        mDecor = generateDecor(-1);
        // ...
    }
    // ...
    if (mContentParent == null) {
        // 2、生成mContentParent
        // 從前面我們知道這就是我們自己的布局的父類
        mContentParent = generateLayout(mDecor);
        // ...
    }    
}
1、生成DecorView
// 類:PhoneWindow
protected DecorView generateDecor(int featureId) {
    // ...
    // 直接創(chuàng)建了一個DecorView,點擊去查看DecorView是繼承了FrameLayout
    return new DecorView(context, featureId, this, getAttributes());
}
2、生成mContentParent
// 類:PhoneWindow
protected ViewGroup generateLayout(DecorView decor) {
    // 獲取窗口屬性,也就是AndroidManifest.xml中設置的主題
    // View的屬性在layout.xml里面配置,Window的屬性在主題里面配置
    TypedArray a = getWindowStyle();
    // 這里是一系列的主題設置
    // ...

    // 根據上面設置的屬性,加載不同的根布局,所有布局都一定會有一個View的id是ID_ANDROID_CONTENT,
    // 也就是mContentParent,也就是我們setContentView()傳入的View的父View
    // 這里隨便選一個布局,比如:R.layout.screen_simple
    // 該文件在源碼中的路徑:frameworks/base/core/res/res/layout/screen_simple.xml
    // 下載源碼方法:https://blog.csdn.net/qiantujava/article/details/102847414
    // 在線源碼地址:http://aospxref.com/android-9.0.0_r45/xref/frameworks/base/core/res/res/layout/screen_simple.xml
    layoutResource = R.layout.screen_simple;

    // 1、解釋上面選定的layout修飾布局文件
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // 這里findViewById其實調用的是getDecorView().findViewById(id)
    // 上面所有布局都會有ID_ANDROID_CONTENT這個id,也就是:android:id="@android:id/content"
    // 我們自己的布局文件就是添加到這個ViewGroup中的
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    // ...
    return contentParent;
}

1、解釋修飾布局文件

// 類:DecorView
// 解釋layout文件
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    // ...
    // Caption有標題的意思
    mDecorCaptionView = createDecorCaptionView(inflater);
    // 解釋剛才那個布局screen_simple.xml,里面會有ID_ANDROID_CONTENT
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
        // 沒搞清楚mDecorCaptionView是個什么東西,先不管這個
    } else {
        // 沒有mDecorCaptionView的時候,直接把root添加到DecorView中
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    // ...
}

上面有幾個重點關注的變量:
mWindow是PhoneWindow的對象,是Activity的一個變量,在Activity.attach()方法里面就創(chuàng)建了;
mDecor是DecorView的對象,是PhoneWindow的一個變量;
mContentRoot是修飾布局文件inflate得到的View,添加到了mDecor中;
mContentParent是mContentRoot中id為ID_ANDROID_CONTENT的View;
最后我們setContentView傳入的View是添加到mContentParent的。
所以我們可以得到個大概的布局關系圖:

image

三、繼承AppCompatActivity

// 這是我們普通的Activity,繼承onCreate,添加我們的布局
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.xxx);
}

// 類:AppCompatActivity
public void setContentView(@LayoutRes int layoutResID) {
    // 點進去getDelegate()可以看到返回的是AppCompatDelegateImpl
    // 所以這里調用的是AppCompatDelegateImpl.setContentView()
    getDelegate().setContentView(layoutResID);
}

// 類:AppCompatDelegateImpl
public void setContentView(int resId) {
    // 確保mSubDecor是否已經生成
    // 這里還不知道m(xù)SubDecor是個什么東西,下面再看
    ensureSubDecor();
    // 把我們自己的布局添加到contentParent
    // 其實從ensureSubDecor()里面可以知道這個android.R.id.content對應的View已經不是前面分析的mContentParent了
    // 而是mSubDecor里面的一個ContentFrameLayout
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    // ...
}

// 類:AppCompatDelegateImpl
private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();
        // ...
    }
}

// 類:AppCompatDelegateImpl
private ViewGroup createSubDecor() {
    // 設置各種主題
    // ...
    // 跟蹤這個mWindow,可以看到是從Activity傳過來的,所以這個也是PhoneWindow
    // getDecorView()里面就一個判斷,如果mDecor==null,就installDecor()
    // installDecor()在上面已經分析了,一毛一樣的
    // installDecor()后就生成了mDecor和mContentParent了
    mWindow.getDecorView();

    // 根據不同的主題inflate不同的布局文件,然后賦值給subDecor
    // 所以,subDecor就是一個普通的ViewGroup
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
    // ...
    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
    // ...
    // 上面所有的布局都會有個id=R.id.action_bar_activity_content的ContentFrameLayout
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);

    // 上面mWindow.getDecorView()已經生成mContentParent了,id就是android.R.id.content
    // 所以這里windowContentView其實就是mContentParent
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // 把windowContentView的所有子view都添加到contentView中
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        // 把windowContentView的id設為了NO_ID,把contentView的id設成了android.R.id.content
        // 此后各個view的關系還是:DecorView->mContentRoot->mContentParent(就是windowContentView)
        // 但是mContentParent里面的view已經全部移到contentView中了,同時contentView還沒添加到DecorView
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
        // ...
    }

    // 把contentView添加到DecorView中
    mWindow.setContentView(subDecor);
    // ...
    return subDecor;
}

// 類:PhoneWindow
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // 前面createSubDecor()中調了mWindow.getDecorView()已經生成了mContentParent,所以直接跳過
    if (mContentParent == null) {
        installDecor();
    }
    // ...
    // 這個view是前面?zhèn)鬟^來的subDecor
    mContentParent.addView(view, params);
    // ...
}

由上面的分析,我們同樣可以得到以下總結:
mWindow是PhoneWindow的對象,是Activity的一個變量,在Activity.attach()方法里面就創(chuàng)建了;
mDecor是DecorView的對象,是PhoneWindow的一個變量;
mContentRoot是修飾布局文件inflate得到的View,添加到了mDecor中;
mContentParent是mContentRoot中id為ID_ANDROID_CONTENT的View,但是被設置為NO_ID了;
mSubDecor是含有R.id.action_bar_activity_content的布局,mSubDecor會被添加到mContentParent中;
contentView是ContentFrameLayout,是mSubDecor中id=R.id.action_bar_activity_content的view,最后會被設成id=android.R.id.content。
最后我們setContentView()傳入的view是會被添加到id=android.R.id.content的view中的,
所以我們可以得到個大概的布局關系圖:

image

四、繼承Activity和繼承AppCompatActivity的區(qū)別

從上面的分析可以看出來,繼承AppCompatActivity的時候,布局中多了個mSubDecorcontentView,之前我們的布局是添加到mContentParent的,現(xiàn)在是添加到contentView了,其實換湯不換藥,只是在中間插入了一層view。為什么要這樣呢?因為在API28的時候官方大力推薦MaterialDesign,在中間插入了一層mSubDecor就可以為所欲為的加入MaterialDesign的東西了,應該是醬紫吧。

五、寫在最后

其實看源碼也沒有那么難嘛,一開始看可能會覺得超復雜,跳來跳去的,谷歌什么破工程師,寫出來的代碼亂七八糟。但是當你看的多了,這里的多包括看的次數(shù)多和看的內容多,看的次數(shù)多了你會對這段源碼印象更深刻,下次再回來看的時候不會再那么陌生了,而看的內容多的話,你會發(fā)現(xiàn)越來越清晰了,各個點都是有關系的,之前那個點可能沒看懂,看到這個點的時候忽然明白了上個點,都是有聯(lián)系的。
PS. 標題里面的夜夜并沒有說假話,現(xiàn)在是凌晨4點、

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容