03 - [甜湯] - AppCompatActivity.setContentView 流程

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

前面兩個章節(jié)學習了 Activity 的 setContentView 流程, 復習一下大概流程.

  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); 把我們要設置的布局文件ID, 加載到 mContentParent 中.
  6. 流程結(jié)束

可是目前我們所使用的 Activity , 都是直接繼承自 AppCompatActivity, 而 AppCompatActivity 最終也是繼承自 Activity, 那么今天來看一下 AppCompatActivity.setContentView 的流程

( 以下分析基于 Android 10.0 )

首先在 MainActivity 中點擊 setContentView 進入內(nèi)部

 public class MainActivity extends AppCompatActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
     }
 }

跳轉(zhuǎn)到了 AppCompatActivity 的 setContentView

@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

看到這里獲取了一個代理, 然后調(diào)用代理的 setContentView 方法, 繼續(xù)跟進, 看一下獲取的是什么代理, 點擊 getDelegate 進入.


private AppCompatDelegate mDelegate;

@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

看看究竟 create 創(chuàng)建了什么.

public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}

進入 create內(nèi). 發(fā)現(xiàn)創(chuàng)建的是 AppCompatDelegateImpl ,
原來 AppCompatDelegate 是一個抽象類, 而 AppCompatDelegateImpl 繼承自 AppCompatDelegate .

( 注: AppCompatDelegate 是一個可以放在任意Activity中,并且回調(diào)相應生命周期的類淤袜,在不使用 AppCompatActivity 的情況下, 也可以得到一致的主題與顏色 )

那么現(xiàn)在就清楚了. AppCompatActivity.setContentView 真正調(diào)用了 AppCompatDelegateImpl 中的 setContentView 方法.

進入 AppCompatDelegateImpl.setContentView

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mOriginalWindowCallback.onContentChanged();
}

先來看第一個方法 ensureSubDecor, 進入

// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
private ViewGroup mSubDecor;

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();
        ...
    }
}

上面省去了一些暫時不需要關注的代碼, 重點就是這個 mSubDecor 的初始化.
DecorView 知道, 因為上一章說到了, 可是這個 mSubDecor 是什么鬼. 就是一個簡單的 ViewGroup ?

進入 AppCompatDelegateImpl.createSubDecor()

private ViewGroup createSubDecor() {
//---------------------------------第一部分----------------------------------
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    ...
    ...
    a.recycle();

//---------------------------------第二部分----------------------------------
    // Now let's make sure that the Window has installed its decor by retrieving it
    mWindow.getDecorView();

//---------------------------------第三部分----------------------------------
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;

    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate( R.layout.abc_dialog_title_material, null);
            ...
        } else if (mHasActionBar) {
            ...
            ...
            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext).inflate(R.layout.abc_screen_toolbar, null);
            ...
            ...
            ...
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }

        if (Build.VERSION.SDK_INT >= 21) {
            ...
        } else {
            ...
        }
    }
    if (subDecor == null) {
            throw new IllegalArgumentException(
                    "AppCompat does not support the current theme features: { "
                            + "windowActionBar: " + mHasActionBar
                            + ", windowActionBarOverlay: "+ mOverlayActionBar
                            + ", android:windowIsFloating: " + mIsFloating
                            + ", windowActionModeOverlay: " + mOverlayActionMode
                            + ", windowNoTitle: " + mWindowNoTitle
                            + " }");
      }
   ...
   ...
   ...
//---------------------------------第四部分----------------------------------
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
        ...
        ...
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);

    ...
    ...

    return subDecor;
}

代碼量有點多, 我已省去了, 目前我們不需要關注的代碼. 只留下和流程相關的關鍵性代碼.
如上代碼所示, 我將 createSubDecor 方法整體分為了 4個部分.
?
?
createSubDecor() 第一部分
又見到了 TypedArray, 上一章也有這個, 還記得嗎? 不記得的回去翻一下加深下印象.
上章的 TypedArray 是取的是 Window的, 然后賦值給 PhoneWindow 的.
這里的 TypedArray 取的是 AppCompatTheme 的, 然后賦值給 AppCompatDelegateImpl 的.
兩者之間不要弄混淆了.

所以說, 第一部分的主要是從 AppCompatTheme 獲取 style, 然后逐個進行判斷, 然后賦值給 AppCompatDelegateImpl .

?
?

createSubDecor() 第二部分
mWindow.getDecorView();
看到這句, 熟悉嗎? 我猜測這里是初始化了DecorView 和 mContentParnt,
為了驗證. 我們直接進入 PhoneWindow 的 getDecorView() 方法.
PhoneWindow.getDecorView()

    @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

果不其然, 確實是去初始化了 DecorView 和 mContentParent. ( installDecor() 調(diào)用. installDecor方法具體流程, 這里不再說明, 上一章 [02-正菜] 中已經(jīng)詳細解釋 )

第二部分就是初始化 PhoneWindow 中的 DecorView 和 mContentParent

?
?
createSubDecor() 第三部分
看到第三部分是不是還是有點熟悉?
是不是和在 PhoneWindow 初始化 mContentParent 并且加載一個 LinearLayout 的邏輯一樣? 只是這里是給 subDecor 賦值.
那既然這樣, 我們還是先找到一個最簡單的布局文件看一下.
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
在我電腦上的路徑如下:
D:\SDK\extras\android\support\v7\appcompat\res\layout\R.layout.abc_screen_simple (家里的是 windows 系統(tǒng))

<android.support.v7.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <android.support.v7.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/abc_screen_content_include" />

</android.support.v7.widget.FitWindowsLinearLayout>

繼續(xù)尋找 include 的, abc_screen_content_include, 還是在上面的那個路徑下.

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <android.support.v7.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />
</merge>

思考一下, PhoneWindow 中給 DecorView 加載了一個 LinearLayout, 里面有兩部分, 其中一部分是一個 FrameLayout.
而這里呢, 給 subDecor 賦值了一個 FitWindowsLinearLayout 布局 , 里面也有兩部分, 其中一部分是一個 ContentFrameLayout .
這兩者看起來是不是很相似? 有沒有聯(lián)想到什么? 別著急繼續(xù)往下看.

第三部分根據(jù)條件給 subDecor 賦值. (加載不同的布局文件)

?
?
createSubDecor() 第四部分. 重點
為了方便, 我把第四部分的代碼拿下來放到這里.

//---------------------------------第四部分----------------------------------
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
        ...
        ...
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);

    ...
    ...

    return subDecor;
  1. 先是從 subDecor 中取出一個 ID 為 action_bar_activity_content 的控件賦值給 contentView 變量.
    這個 ID 就是上面布局文件中的 ContentFrameLayout

  2. 接著從 PhoneWindow 中的 DecorView 中獲取一個 ID 為 content 的 ViewGroup. 賦值給 windowContentView .
    回想一下, 這個ID 不就是 DecorView 中 LinearLayout 里面的 FrameLayout 嘛.

  3. 循環(huán)遍歷 windowContentView, 取出 windowContentView 中每一個子 View, 然后 windowContentView 先刪除這個View, 再添加到 ContentFrameLayout 中. 直至windowContentView 為空.

  4. 把 DecorView 中 LinearLayout 里面的 FrameLayout ID 改為 NO_ID.

  5. 把 subDecor 中 ContentFrameLayout ID 改為 R.id.content.

  6. 調(diào)用 PhoneWindow 的 setContentView 把 subDecor 加載到 PhoneWindow 中的 mContentParent 中.

  7. 返回 subDecor.

第四部分總的來說就是, 將 PhoneWindow --> DecorView 布局中 id 為 content 的 FrameLayout 所有子 View 遍歷添加到 subDecor 中 id 為 action_bar_activity_content 的 ContentFrameLayout 中去. 添加一個刪除一個. 遍歷完成后, 改變兩個 ViewGroup 的 id, 最后將 subDecor 添加到 PhoneWindow 的 mContentParent 中.

?
?
現(xiàn)在代碼繼續(xù)回到 AppCompatDelegateImpl.setContentView

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mOriginalWindowCallback.onContentChanged();
}

ensureSubDecor(); 已經(jīng)執(zhí)行完成, 初始化了 mSubDecor .
接著從 mSubDecor 中獲取 id 為 content 的控件, 并轉(zhuǎn)為 ViewGroup.
然后把我們傳入的資源文件 添加到 這個 ViewGroup 中去.
至此, 這整個流程分析完成.

在下一章, 會有一個總結(jié), 從網(wǎng)上找了幾張圖來總結(jié)和對比.

最后編輯于
?著作權歸作者所有,轉(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é)果婚禮上,老公的妹妹穿的比我還像新娘创橄。我一直安慰自己箩做,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布妥畏。 她就那樣靜靜地躺著邦邦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咖熟。 梳的紋絲不亂的頭發(fā)上圃酵,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音馍管,去河邊找鬼郭赐。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的捌锭。 我是一名探鬼主播俘陷,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼观谦!你這毒婦竟也來了拉盾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤豁状,失蹤者是張志新(化名)和其女友劉穎捉偏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泻红,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡夭禽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谊路。 大學時的朋友給我發(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
  • 正文 我出身青樓耗啦,卻偏偏與公主長得像凿菩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子帜讲,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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