01 - [開胃菜] - Activity.setContentView 涉及到的類及相關(guān)概念
02 - [正菜] - Activity.setContentView流程
03 - [甜湯] - AppCompatActivity.setContentView 流程
04 - [完結(jié)] - setContentView 流程總結(jié)
前面兩個(gè)章節(jié)學(xué)習(xí)了 Activity 的 setContentView 流程, 復(fù)習(xí)一下大概流程.
-
Activity
調(diào)用setContentView .
- 執(zhí)行
PhonwWindow.setContentView
-
PhonwWindow.setContentView
中調(diào)用installDecor
初始化DecorView
(installDecor
中調(diào)用generateDecor()
) - 初始化
mContentParent
(installDecor
調(diào)用generateLayout()
)- 在初始化
mContentParent
的同時(shí), 又根據(jù)Window
不同屬性加載不同的布局, 然后添加到DecorView
中. - 最后獲取
DecorView
中的一個(gè)ViewGroup
作為mContentParent
并返回.
- 在初始化
- 在
PhoneWindow.setContentView
中, 調(diào)用mLayoutInflater.inflate(layoutResID, mContentParent);
把我們要設(shè)置的布局文件ID, 加載到mContentParent
中. - 流程結(jié)束
可是目前我們所使用的 Activity , 都是直接繼承自 AppCompatActivity, 而 AppCompatActivity 最終也是繼承自 Activity, 那么今天來看一下 AppCompatActivity.setContentView 的流程
( 以下分析基于 Android 10.0 )
首先在 MainActivity 中點(diǎn)擊 setContentView 進(jìn)入內(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);
}
看到這里獲取了一個(gè)代理, 然后調(diào)用代理的 setContentView 方法, 繼續(xù)跟進(jìn), 看一下獲取的是什么代理, 點(diǎn)擊 getDelegate 進(jìn)入.
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);
}
進(jìn)入 create內(nèi). 發(fā)現(xiàn)創(chuàng)建的是 AppCompatDelegateImpl ,
原來 AppCompatDelegate 是一個(gè)抽象類, 而 AppCompatDelegateImpl 繼承自 AppCompatDelegate .
( 注: AppCompatDelegate 是一個(gè)可以放在任意Activity中,并且回調(diào)相應(yīng)生命周期的類,在不使用 AppCompatActivity 的情況下, 也可以得到一致的主題與顏色 )
那么現(xiàn)在就清楚了. AppCompatActivity.setContentView 真正調(diào)用了 AppCompatDelegateImpl 中的 setContentView 方法.
進(jìn)入 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();
}
先來看第一個(gè)方法 ensureSubDecor
, 進(jìn)入
// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
private ViewGroup mSubDecor;
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
...
}
}
上面省去了一些暫時(shí)不需要關(guān)注的代碼, 重點(diǎn)就是這個(gè) mSubDecor 的初始化.
DecorView 知道, 因?yàn)樯弦徽抡f到了, 可是這個(gè) mSubDecor 是什么鬼. 就是一個(gè)簡(jiǎn)單的 ViewGroup ?
進(jìn)入 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;
}
代碼量有點(diǎn)多, 我已省去了, 目前我們不需要關(guān)注的代碼. 只留下和流程相關(guān)的關(guān)鍵性代碼.
如上代碼所示, 我將 createSubDecor 方法整體分為了 4個(gè)部分.
?
?
createSubDecor() 第一部分
又見到了 TypedArray, 上一章也有這個(gè), 還記得嗎? 不記得的回去翻一下加深下印象.
上章的 TypedArray 是取的是 Window的, 然后賦值給 PhoneWindow 的.
這里的 TypedArray 取的是 AppCompatTheme 的, 然后賦值給 AppCompatDelegateImpl 的.
兩者之間不要弄混淆了.
所以說, 第一部分的主要是從 AppCompatTheme 獲取 style, 然后逐個(gè)進(jìn)行判斷, 然后賦值給 AppCompatDelegateImpl .
?
?
createSubDecor() 第二部分
mWindow.getDecorView();
看到這句, 熟悉嗎? 我猜測(cè)這里是初始化了DecorView 和 mContentParnt,
為了驗(yàn)證. 我們直接進(jìn)入 PhoneWindow 的 getDecorView() 方法.
PhoneWindow.getDecorView()
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
果不其然, 確實(shí)是去初始化了 DecorView 和 mContentParent. ( installDecor() 調(diào)用. installDecor方法具體流程, 這里不再說明, 上一章 [02-正菜] 中已經(jīng)詳細(xì)解釋 )
第二部分就是初始化 PhoneWindow 中的 DecorView 和 mContentParent
?
?
createSubDecor() 第三部分
看到第三部分是不是還是有點(diǎn)熟悉?
是不是和在 PhoneWindow 初始化 mContentParent 并且加載一個(gè) LinearLayout 的邏輯一樣? 只是這里是給 subDecor 賦值.
那既然這樣, 我們還是先找到一個(gè)最簡(jiǎn)單的布局文件看一下.
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, 還是在上面的那個(gè)路徑下.
<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 加載了一個(gè) LinearLayout, 里面有兩部分, 其中一部分是一個(gè) FrameLayout.
而這里呢, 給 subDecor 賦值了一個(gè) FitWindowsLinearLayout 布局 , 里面也有兩部分, 其中一部分是一個(gè) ContentFrameLayout .
這兩者看起來是不是很相似? 有沒有聯(lián)想到什么? 別著急繼續(xù)往下看.
第三部分根據(jù)條件給 subDecor 賦值. (加載不同的布局文件)
?
?
createSubDecor() 第四部分. 重點(diǎn)
為了方便, 我把第四部分的代碼拿下來放到這里.
//---------------------------------第四部分----------------------------------
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;
先是從 subDecor 中取出一個(gè) ID 為 action_bar_activity_content 的控件賦值給 contentView 變量.
這個(gè) ID 就是上面布局文件中的 ContentFrameLayout接著從 PhoneWindow 中的 DecorView 中獲取一個(gè) ID 為 content 的 ViewGroup. 賦值給 windowContentView .
回想一下, 這個(gè)ID 不就是 DecorView 中 LinearLayout 里面的 FrameLayout 嘛.循環(huán)遍歷 windowContentView, 取出 windowContentView 中每一個(gè)子 View, 然后 windowContentView 先刪除這個(gè)View, 再添加到 ContentFrameLayout 中. 直至windowContentView 為空.
把 DecorView 中 LinearLayout 里面的 FrameLayout ID 改為 NO_ID.
把 subDecor 中 ContentFrameLayout ID 改為 R.id.content.
調(diào)用 PhoneWindow 的 setContentView 把 subDecor 加載到 PhoneWindow 中的 mContentParent 中.
返回 subDecor.
第四部分總的來說就是, 將 PhoneWindow --> DecorView 布局中 id 為 content 的 FrameLayout 所有子 View 遍歷添加到 subDecor 中 id 為 action_bar_activity_content 的 ContentFrameLayout 中去. 添加一個(gè)刪除一個(gè). 遍歷完成后, 改變兩個(gè) 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.
然后把我們傳入的資源文件 添加到 這個(gè) ViewGroup 中去.
至此, 這整個(gè)流程分析完成.
在下一章, 會(huì)有一個(gè)總結(jié), 從網(wǎng)上找了幾張圖來總結(jié)和對(duì)比.