眾所周知,Activity是Android系統(tǒng)的四大組件之一,扮演著界面展示的角色。作為Android開發(fā)人員,我們當(dāng)然也對(duì)setContentView()方法非常熟悉,就是這么簡(jiǎn)簡(jiǎn)單單的一行代碼,調(diào)用它就可以加載我們寫好的xml布局。對(duì)于有追求的我們來說,必須知其然更要知其所以然。那么,本文就主要介紹一下Activity布局加載流程的源碼分析,而且源碼版本基于Android 8.0。在介紹之前,先給大家一張圖直觀地感受一下。

在開始梳理Activity整個(gè)布局加載流程之前,我們對(duì)照上圖,先來大致了解一下幾個(gè)概念:
- Window:抽象類,表示一個(gè)窗口,Android系統(tǒng)中的界面都是以窗口的形式存在;
- PhoneWindow: Window的具體實(shí)現(xiàn)類,Activity布局加載流程主要在此類中完成;
- WindowManager: Window的管理類,管理著Window的添加、更新和刪除;
- WindowManagerService(WMS):系統(tǒng)窗口管理服務(wù)類,具體管理著系統(tǒng)各種各樣的Window;
- DecorView:Window的頂級(jí)View,主要負(fù)責(zé)裝載各種View。
一、Activity中的setContentView()方法
當(dāng)我們編寫一個(gè)Activity代碼時(shí),會(huì)調(diào)用setContentView()方法來加載我們的xml布局,通常情況調(diào)用方式如下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
... ...
}
}
那我們進(jìn)入Activity的setContentView()方法,一探究竟。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
Activity的setContentView()方法比較簡(jiǎn)單,總共只有2行代碼。最主要的是它調(diào)用了getWindow()的setContentView()方法。那么,這個(gè)getWindow()方法獲取的是什么呢?在上面的代碼中我已經(jīng)貼出來了,其實(shí)返回的就是一個(gè)Window對(duì)象,而且是以成員變量的形式返回。那么,我們自然而然就會(huì)想到這個(gè)成員變量mWindow是什么時(shí)候進(jìn)行賦值的。在Activity啟動(dòng)流程源碼解析一文中,我們通過分析知道,在Activity啟動(dòng)過程中,AMS通過Binder機(jī)制,會(huì)跨進(jìn)程調(diào)用到App進(jìn)程中主線程ActivityThread的scheduleLaunchActivity()方法。之后,通過主線程的Handler,會(huì)調(diào)用到handleLaunchActivity()方法,緊接著在其中又會(huì)調(diào)用到performLaunchActivity()方法,關(guān)于這個(gè)方法我們具體來看一下。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
... ...
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
} catch (Exception e) {
... ...
}
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
... ...
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
... ...
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
... ...
if (!r.activity.mFinished) {
// 回調(diào)onStart()方法
activity.performStart();
r.stopped = false;
}
}
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
... ...
}
return activity;
}
由源碼可知,通過反射的方式創(chuàng)建了Activity的實(shí)例,并且調(diào)用了它的attach()方法,我們來看一下這個(gè)方法具體做了什么。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
很明顯,我們可以看到之前所說的mWindow對(duì)象是在attach方法進(jìn)行初始化的,并且mWindow成員變量是PhoneWindow類的實(shí)例。而且,我們從這也可以知道一個(gè)Activity對(duì)應(yīng)著一個(gè)Window對(duì)象??匆谎畚恼麻_頭的那張圖,Activity的下一層為什么是PhoneWindow應(yīng)該就可以理解了吧。
那么,再回到之前Activity的setContentView()方法,既然getWindow()返回的是一個(gè)PhoneWindow對(duì)象,那么getWindow().setContentView(layoutResID)自然也就是去調(diào)用PhoneWindow的setContentView()方法。
二、PhoneWindow中的setContentView()方法
經(jīng)過上面的分析,我們知道調(diào)用Activity的setContentView()方法,之后會(huì)去調(diào)用PhoneWindow的setContentView()方法,那么我們一起來看一下具體實(shí)現(xiàn)。
// PhoneWindow # setContentView()
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
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è)ViewGroup對(duì)象,第一次mContentParent為空,所以會(huì)執(zhí)行installDecor()方法,我們看下它的具體實(shí)現(xiàn)。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
... ...
}
}
這個(gè)方法的源碼實(shí)現(xiàn)比較長(zhǎng),我們只關(guān)注重點(diǎn)部分。成員變量mDecor是一個(gè)DecorView對(duì)象,而DecorView繼承于FrameLayout,所以這里的mDecor其實(shí)就是一個(gè)FrameLayout對(duì)象。第一次mDecor為空,所以通過調(diào)用generateDecor()方法對(duì)它進(jìn)行初始化,我們看一下具體實(shí)現(xiàn)。
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
由源碼可知,這個(gè)方法其實(shí)就是通過new方式創(chuàng)建了一個(gè)DecorView對(duì)象并返回,沒什么好說的。
然后,我們?cè)俅位氐絠nstallDecor()方法,創(chuàng)建完DecorView對(duì)象并將其賦值給mDecor。之后,便通過generateLayout()方法,并將mDecor作為入?yún)沓跏蓟蓡T變量mContentParent。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
... ...
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
... ...
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks(contentParent);
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
由源碼可知,該方法中會(huì)先根據(jù)主題設(shè)置去選擇layoutResource,這個(gè)layoutResource其實(shí)也就是DecorView的子View的布局。這里,我們挑著看一下R.layout.screen_title布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
之后,通過調(diào)用DecorView的onResourcesLoaded()方法,將layoutResource的布局轉(zhuǎn)換并添加到DecorView中。
// DecorView # onResourcesLoaded()
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
我們看到onResourcesLoaded()方法中通過LayoutInflater的inflate()方法解析之前的 layoutResource,并將解析之后的View添加到DecorView中。
接著,我們?cè)倩氐絞enerateLayout()方法中,在完成上面的步驟之后,就對(duì)contentParent進(jìn)行初始化。
... ...
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
... ...
return contentParent;
// DecorView # findViewById()
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
由源碼可知,最后是將id為ID_ANDROID_CONTENT(com.android.internal.R.id.content)的View賦值給了成員變量mContentParent。至此,完成了mContentParent的初始化。讓我們?cè)倩剡^頭來看PhoneWindow的setContentView()方法,由于上面的代碼隔得比較遠(yuǎn)了,這里再放一下。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
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;
}
由源碼可知,在完成對(duì)mContentParent的初始化之后,調(diào)用了mLayoutInflater.inflate(layoutResID, mContentParent)方法。很明顯,這個(gè)方法就是將我們傳遞過來的layoutId對(duì)應(yīng)的xml布局文件進(jìn)行解析,并且作為子View添加到mContentParent中,從而完成了把我們的xml布局文件對(duì)應(yīng)的View添加到DecorView中。
至此,Activity、Window以及DecorView這三者的關(guān)系基本上理清楚了。但是,事情并沒有結(jié)束,因?yàn)檫@時(shí)候的DecorView 還沒有真正添加到Window上去,只是創(chuàng)建出對(duì)象并完成xml布局解析而已。這部分的內(nèi)容屬于Activity布局繪制,打算另開篇幅進(jìn)行具體展開。
總結(jié)
一個(gè)Activity包含一個(gè)Window對(duì)象,并且具體由PhoneWindow來實(shí)現(xiàn)。PhoneWindow將DecorView作為整個(gè)應(yīng)用窗口的根View,而這個(gè)DecorView又將屏幕劃分為兩個(gè)區(qū)域一個(gè)是TitleView一個(gè)是ContentView(對(duì)應(yīng)id為R.id.content),而我們所編寫的xml布局正是添加在ContentView中進(jìn)行展示的。