02 - [正菜] - Activity.setContentView流程

01 - [開胃菜] - Activity.setContentView 涉及到的類及相關(guān)概念
02 - [正菜] - Activity.setContentView流程
03 - [甜湯] - AppCompatActivity.setContentView 流程
04 - [完結(jié)] - setContentView 流程總結(jié)

2. 正菜 - Activity.setContentView

目前我們使用的所有 Activity 默認(rèn)都繼承自 AppCompatActivity, AppCompatActivity 中做了什么, 我們先不用去了解, 因?yàn)楹竺鏁?huì)說到.

AppCompatActivity 基類 仍然是 Activity, 所以我們先從 Activity 下手.

下面開始跟著我一步一步的分析.

2.1 Activity

我們直接進(jìn)入 Activity.java 中, 搜索 setContentView 找到參數(shù)為 int 類型的.

/**
  * Set the activity content from a layout resource.  The resource will be
  * inflated, adding all top-level views to the activity.
*/
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

注釋翻譯: 通過資源文件設(shè)置 Activity 內(nèi)容, 把所有頂級(jí) View 添加到 Activity 中

( 頂級(jí) View 的概念, 在 01 - 開胃菜中, 已經(jīng)說過 )

在第6行調(diào)用了 getWindow().setContentView(layoutResID); 并傳入了資源 ID.

先跳轉(zhuǎn)到 getWindow() 看一下.


@UnsupportedAppUsage
private Window mWindow;

/**
 * Retrieve the current {@link android.view.Window} for the activity.
 * This can be used to directly access parts of the Window API that
 * are not available through Activity/Screen.
 *
 * @return Window The current window, or null if the activity is not
 *         visual.
 */
public Window getWindow() {
    return mWindow;
}

注釋翻譯: 得到當(dāng)前的 Activity , 可以用來直接訪問部分無法通過 Activiy/Screen 訪問的 Window API

在 01 中已經(jīng)說過, Window 是一個(gè)抽象基類, 提供了頂級(jí)窗口的外觀和行為策略. 有一個(gè)唯一的實(shí)現(xiàn)類就是 PhoneWindow. 那么上面的調(diào)用 getWindow().setContentView(layoutResID); 其實(shí)就是實(shí)現(xiàn)類調(diào)用的. 我們進(jìn)入繼續(xù)跳轉(zhuǎn)到 PhneWindowsetContentView(layoutResID)

2.2 PhonwWindow

phoneWindow的 setContentView. PhoneWindow: 423行

ViewGroup mContentParent;

@Override
public void setContentView(int layoutResID) {
    //1
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
        //2
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

mContentParent 是一個(gè)放置窗口內(nèi)容的視圖, 它不是 mDecor 本身, 就是一個(gè)mDecor 的子View

這個(gè)方法其實(shí)我們需要關(guān)注的地方就兩個(gè)部分,

  1. 判斷mContentParent是否為 null

    • 為 null 就執(zhí)行初始化邏輯,

    • 不為 null 就移除所有容器內(nèi)的View.

  2. 是否使用了過度動(dòng)畫

    • 沒有就直接把我們傳入的資源 ID 加載到 父容器 mContentParent

    • 有就調(diào)用 transitionTo(Scene scene)

      • transitionTo(Scene scene) 最后也是將我們傳入的資源ID, 加載到 mContentParent中, 此處不在表述.

先來看初始化邏輯 installDecor()

PhoneWindow.java 2681 行

private void installDecor() {
    ...
    if (mDecor == null) {
        //初始化 DecorView
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        //傳入 PhoneWindow 本身
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        //初始化  mContentParent
        mContentParent = generateLayout(mDecor);
        ...
        ...
    }
}

有同學(xué)會(huì)問了, 這不是初始化 mContentParent 的嗎, 為什么要先初始化了一個(gè)叫 mDecor 的 ? 別著急, 慢慢往下看.

2.2.1 PhoneWindow 初始化 DecorView.

DecorView 是Window的頂層View,包含了Window的裝飾。它繼承了 FrameLayout.

( 頂層View 與 頂層Window 的關(guān)系在 01 中已經(jīng)說過. )

PhoneWindow.java 2315行

進(jìn)入到 generateDecor(int featureId) 中.

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

我們發(fā)現(xiàn), 這個(gè)方法就是創(chuàng)了一個(gè) DecorView對(duì)象并返回. ( 注意第三個(gè)參數(shù), 傳入的也是 PhoneWindow 本身. ) , 在 DecorView 的構(gòu)造方法內(nèi), 也調(diào)用了setWindow(window);

進(jìn)入到 DecorView 中會(huì)發(fā)現(xiàn)很多從我們傳入的Window 中來獲取屬性來初始化 DecorView 的屬性. 01 - 開胃菜中所說的兩者之間的關(guān)系, 在這里就能充分的說明了. (DecorView 包含Window的裝飾,例如,大小, 是否透明等屬性).

到這里 mDecor就初始化完畢了, 繼續(xù)回到installDecor() 中開始看 generateLayout(DecorView decor) 方法怎么初始化 mContentParent

2.2.2 PhoneWindow 初始化 mContentParent.

PhoneWindow.java 2336 行

這個(gè)方法內(nèi)有太多太多內(nèi)容, 但是有很多是我們現(xiàn)在不需要關(guān)注的, 我都以 ... 代替了, 保留了一些我們現(xiàn)在需要關(guān)注的代碼.

修改后如下所示

protected ViewGroup generateLayout(DecorView decor) { 
    //-------------------------------1----------------------------
    //獲取window屬性
    TypedArray a = getWindowStyle();
        ...
    //是不是浮動(dòng)窗口.
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    ...
    if (mIsFloating) {
        ...
    } else {
        ...
    }
      //是不是設(shè)置了notitle
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
       ...
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
       ...
    }
        ...
    //是不是設(shè)置了透明
    mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
        ...
    //-------------------------------2----------------------------
    // Inflate the window decor.
    int layoutResource;
    int features = getLocalFeatures();
 
    if (...) {
        layoutResource = R.layout.screen_swipe_dismiss;
        ...
    } else if (...) {
        if (mIsFloating) {
                ...
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        ...
    } else if (...) {
        layoutResource = R.layout.screen_progress;     
    } else if (...) {
        if (mIsFloating) {
            ...
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        ...
    } else if (...) {
        if (mIsFloating) {
            ...
            layoutResource = res.resourceId;
        } else if (...) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    } else if (...) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        //2.1
        layoutResource = R.layout.screen_simple;
    }
        
    //-------------------------------3----------------------------
    ...
    //3.1將布局文件加載到 DecorView 中.
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //3.2
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
 
        ...

    return contentParent;
}

我將這個(gè)方法大致分為三個(gè)步驟

  1. 獲取Window 的屬性, 然后賦值給 PhoneWindow的成員變量.
  2. 根據(jù) features 的值去加載不同的布局文件, (features也是根據(jù)Window設(shè)置的屬性獲取到的值) , 然后把布局文件ID 賦值給變量 layoutResource
  3. layoutResource 加載到 mDecor中. 最后根據(jù)指定的 ViewID獲取 mDecor中的View. 并返回.

重點(diǎn)代碼也是在第三步,

我們先看 3.1 onResourcesLoaded(mLayoutInflater, layoutResource)

DecorView.java 2074行

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ...
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
       ...
    } else {
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

省去無用代碼, 在第3行, 看到了熟悉的 inflater.inflate, 加載傳入的 layoutResource 布局. 在第7行, 把這個(gè) View 添加到 DecorView 中去.

那么加載的這個(gè)View 到底是什么樣的呢, 我們?cè)?code>generateLayout() 中隨便找一個(gè)最簡(jiǎn)單的布局文件來看. 2.1

//2.1
layoutResource = R.layout.screen_simple;
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

FrameLayout 的這個(gè)ID , content需要記住, 因?yàn)橄旅婢蜁?huì)說到這個(gè)ID.

接著看generateLayout() 中的3.2.

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;

ID_ANDROID_CONTENT 這個(gè)ID 是什么 ? 跟進(jìn)去看一下

Window.java 257行

/**
 * The ID that the main layout in the XML layout file should have.
 */
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

官方注釋翻譯

這個(gè) ID 是主布局 xml 文件中應(yīng)該具有的.

這個(gè) ID 就是剛才添加到 DecorView 布局中 FrameLayout的 ID , 等于說是 mContentParentDecorView中的一個(gè)ViewGroup, 是一個(gè)FrameLayout , 最后返回這個(gè) ViewGroup

我們可以猜想, 根據(jù) Window 不同屬性加載的不同布局中, 應(yīng)該都會(huì)有這樣一個(gè)ViewGroup, 并且ID 為 com.android.internal.R.id.content;


總結(jié)

現(xiàn)在來梳理一下整體流程.

  1. Activity 調(diào)用 setContentView .
  2. 執(zhí)行PhonwWindow.setContentView
  3. PhonwWindow.setContentView 中調(diào)用 installDecor 初始化DecorView ( installDecor中調(diào)用generateDecor() )
  4. 初始化 mContentParent ( installDecor 調(diào)用generateLayout())
    • 在初始化 mContentParent的同時(shí), 又根據(jù)Window 不同屬性加載不同的布局, 然后添加到 DecorView 中.
    • 最后獲取DecorView 中的一個(gè) ViewGroup 作為 mContentParent 并返回.
  5. PhoneWindow.setContentView 中, 調(diào)用 mLayoutInflater.inflate(layoutResID, mContentParent); 把我們要設(shè)置的布局文件ID, 加載到 mContentParent 中.
  6. 流程結(jié)束

用圖形來表示一下他們之間的相互關(guān)系


相互之間的關(guān)系
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容