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 中做了什么, 我們先不用去了解, 因為后面會說到.

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

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

2.1 Activity

我們直接進入 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)容, 把所有頂級 View 添加到 Activity 中

( 頂級 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 是一個抽象基類, 提供了頂級窗口的外觀和行為策略. 有一個唯一的實現(xiàn)類就是 PhoneWindow. 那么上面的調(diào)用 getWindow().setContentView(layoutResID); 其實就是實現(xiàn)類調(diào)用的. 我們進入繼續(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 是一個放置窗口內(nèi)容的視圖, 它不是 mDecor 本身, 就是一個mDecor 的子View

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

  1. 判斷mContentParent是否為 null

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

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

  2. 是否使用了過度動畫

    • 沒有就直接把我們傳入的資源 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é)會問了, 這不是初始化 mContentParent 的嗎, 為什么要先初始化了一個叫 mDecor 的 ? 別著急, 慢慢往下看.

2.2.1 PhoneWindow 初始化 DecorView.

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

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

PhoneWindow.java 2315行

進入到 generateDecor(int featureId) 中.

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

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

進入到 DecorView 中會發(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 行

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

修改后如下所示

protected ViewGroup generateLayout(DecorView decor) { 
    //-------------------------------1----------------------------
    //獲取window屬性
    TypedArray a = getWindowStyle();
        ...
    //是不是浮動窗口.
    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;
}

我將這個方法大致分為三個步驟

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

重點代碼也是在第三步,

我們先看 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行, 把這個 View 添加到 DecorView 中去.

那么加載的這個View 到底是什么樣的呢, 我們在generateLayout() 中隨便找一個最簡單的布局文件來看. 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 的這個ID , content需要記住, 因為下面就會說到這個ID.

接著看generateLayout() 中的3.2.

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

ID_ANDROID_CONTENT 這個ID 是什么 ? 跟進去看一下

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;

官方注釋翻譯

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

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

我們可以猜想, 根據(jù) Window 不同屬性加載的不同布局中, 應(yīng)該都會有這樣一個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的同時, 又根據(jù)Window 不同屬性加載不同的布局, 然后添加到 DecorView 中.
    • 最后獲取DecorView 中的一個 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)容合作請聯(lián)系作者
  • 序言:七十年代末宝穗,一起剝皮案震驚了整個濱河市猜绣,隨后出現(xiàn)的幾起案子坛猪,更是在濱河造成了極大的恐慌小槐,老刑警劉巖蛙紫,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拍屑,死亡現(xiàn)場離奇詭異,居然都是意外死亡惊来,警方通過查閱死者的電腦和手機丽涩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裁蚁,“玉大人矢渊,你說我怎么就攤上這事⊥髦ぃ” “怎么了矮男?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長室谚。 經(jīng)常有香客問我毡鉴,道長,這世上最難降的妖魔是什么秒赤? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任猪瞬,我火速辦了婚禮,結(jié)果婚禮上入篮,老公的妹妹穿的比我還像新娘陈瘦。我一直安慰自己,他們只是感情好潮售,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布痊项。 她就那樣靜靜地躺著锅风,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鞍泉。 梳的紋絲不亂的頭發(fā)上皱埠,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音咖驮,去河邊找鬼边器。 笑死,一個胖子當(dāng)著我的面吹牛游沿,可吹牛的內(nèi)容都是我干的饰抒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼诀黍,長吁一口氣:“原來是場噩夢啊……” “哼袋坑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起眯勾,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枣宫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吃环,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體也颤,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年郁轻,在試婚紗的時候發(fā)現(xiàn)自己被綠了翅娶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡好唯,死狀恐怖竭沫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骑篙,我是刑警寧澤蜕提,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站靶端,受9級特大地震影響谎势,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杨名,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一脏榆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧台谍,春花似錦姐霍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至介衔,卻和暖如春恨胚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炎咖。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工赃泡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乘盼。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓升熊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親绸栅。 傳聞我的和親對象是個殘疾皇子级野,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

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