概述
我们经常使用setContentView方法在Activity中显示页面布局,那我们编写的layout又是如何加载到Activity呢?下面我们通过阅读核心源码分析。
在进行分析之前,我们先来看一张包含关系图:

下面根据这张关系图逐步分析,最后再对这张关系图进行总结
Activity的创建
Activity都是配置在AndroidManifest.xml中,但它是如何创建的呢?追踪到入口类ActivityThread的performLaunchActivity方法:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
...
}
上面的代码中,有一句关键代码:
activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass());
Activity是由Instrumentation类中的newActivity方法创建的,关键源码如下:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null
? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
newActivity方法又调用了AppComponentFactory的instantiateActivity方法,关键源码如下:
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
看到这里就明白了AndroidManifest.xml配置的Activity是通过反射创建的
PhoneWindow的创建
在ActivityThread的performLaunchActivity方法中,有以下一段代码:
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
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.attach这句代码,该方法传递了一个Window对象,attach方法中有一句关键代码:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
由源码可知,当Activity创建完后,会为当前的Activity创建一个PhoneWindow对象
DecorView的创建
DecorView的创建,我们从Activity的setContentView的源码开始分析:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
由源码可知,Activity的setContentView实际是调用了PhoneWindow的setContentView方法,跟踪源码:
@Override
public void setContentView(int layoutResID) {
...
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 == null时会调用installDecor方法,跟踪源码:
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);
...
}
....
installDeco中会先generateDecor方法,关键代码如下:
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
generateDecor方法会创建一个DecorView对象并返回,追溯源码可知DecorView是一个FrameLayout,到这里我们就明白DecorView是何时创建的
布局加载
在PhoneWindow的installDecor()方法中有以下一段关键代码:
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mDecor.makeOptionalFitsSystemWindows();
...
}
当mContentParent为null时会调用到generateLayout(mDecor)方法,追踪源码:
protected ViewGroup generateLayout(DecorView decor) {
...
// 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_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");
}
...
return contentParent;
}
}
由源码注释可知generateLayout方法的核心功能是完成DecorView的布局加载,根据不同的主题样式会加载不同的系统默认布局,这里我们以screen_simple.xml布局为例:
<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>
screen_simple.xml布局包含了一个ViewStub和FrameLayout,那这个布局是如何被加载到DecorView呢?generateLayout方法中有以下一句代码:
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
跟踪到DecorView的onResourcesLoaded方法:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
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();
}
由源码可知,layoutResource通过LayoutInflate加载后,调用DecorView的addView方法将布局挂载到DecorView上,但是到这里也只是完成了DecorView布局加载,那我们自己编写的layout布局又是在何时加载呢?我们继续跟踪源码,在mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);后执行了以下代码:
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
在DecorView加载完默认布局后,会查找Id为ID_ANDROID_CONTENT的控件(默认布局中的FrameLayout控件)并作为generateLayout方法的返回值,那我们编写的布局是加载到这个控件上吗?我们回到setContentView方法中,可观察到以下代码:
mLayoutInflater.inflate(layoutResID, mContentParent);
到这里我们知道了,我们自己编写的布局是被加载到DecorView中Id为ID_ANDROID_CONTENT的控件上
总结
至此,我们来总结一下最开始的包含关系图,当Activity创建后会创建出一个PhoneWindow对象,当在Activity中调用setContentView时,实际上是调用了PhoneWindow的setContentView方法,此时PhoneWindow会创建根布局DecorView并根据主题样式为DecorView加载对应的默认系统布局,在默认的系统布局中包含了一个Id为ID_ANDROID_CONTENT的控件,而我们编写的布局就是加载到这个控件中的。
网友评论