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)到 PhneWindow
的 setContentView(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)注的地方就兩個部分,
-
判斷
mContentParent
是否為 null為 null 就執(zhí)行初始化邏輯,
不為 null 就移除所有容器內(nèi)的View.
-
是否使用了過度動畫
沒有就直接把我們傳入的資源 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;
}
我將這個方法大致分為三個步驟
- 獲取
Window
的屬性, 然后賦值給PhoneWindow
的成員變量. - 根據(jù)
features
的值去加載不同的布局文件, (features
也是根據(jù)Window
設(shè)置的屬性獲取到的值) , 然后把布局文件ID 賦值給變量layoutResource
- 將
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 , 等于說是 mContentParent
是 DecorView
中的一個ViewGroup
, 是一個FrameLayout
, 最后返回這個 ViewGroup
我們可以猜想, 根據(jù) Window 不同屬性加載的不同布局中, 應(yīng)該都會有這樣一個ViewGroup, 并且ID 為 com.android.internal.R.id.content;
總結(jié)
現(xiàn)在來梳理一下整體流程.
-
Activity
調(diào)用setContentView .
- 執(zhí)行
PhonwWindow.setContentView
-
PhonwWindow.setContentView
中調(diào)用installDecor
初始化DecorView
(installDecor
中調(diào)用generateDecor()
) - 初始化
mContentParent
(installDecor
調(diào)用generateLayout()
)- 在初始化
mContentParent
的同時, 又根據(jù)Window
不同屬性加載不同的布局, 然后添加到DecorView
中. - 最后獲取
DecorView
中的一個ViewGroup
作為mContentParent
并返回.
- 在初始化
- 在
PhoneWindow.setContentView
中, 調(diào)用mLayoutInflater.inflate(layoutResID, mContentParent);
把我們要設(shè)置的布局文件ID, 加載到mContentParent
中. - 流程結(jié)束
用圖形來表示一下他們之間的相互關(guān)系