setContentView大家应该比较熟悉,而不同的Activity,setContentView的绘制流程也不一样,这里所说的不同的Activity分别是:Activity和AppCompatActivity。我们一一来介绍它们的setContentView。
一、Activity的setContentView
我们先查看Activity的setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
第3行:initWindowDecorActionBar,从方法名称上我们可以猜测,应该是布局中actionBar的初始化过程,这里不是我们这次博客的重点,略过
第2行:这里getWindow返回的是Window对象,然后调用了Window对象的setContentView。Window是一个抽象类,setContentView也是一个抽象方法,所以我们要知道这里的Window对象的具体实现类是谁。想要知道Window对象是谁,我们需要从ActivityThread中来查找。
在ActivityThread中,我们找到performLaunchActivity,在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,
r.assistToken);
我们找到Activity的attach方法:
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, IBinder assistToken) {
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);
.......
}
mWindow就是上面getWindow方法返回的对象,从这里我们可以明显的看出来,mWindow对象的具体实现类是PhoneWindow。
所以就知道了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.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
第4行的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);
......
}
......
}
第4行:调用了generateDecor,这个方法很简单,就是创建了DecorView对象
第14行:调用了generateLayout方法,并且把刚刚创建的DecorView传了进去。这个方法很关键,我们进入查看:
protected ViewGroup generateLayout(DecorView decor) {
....
int layoutResource;
int features = getLocalFeatures();
.....
if ....
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);
.....
return contentParent;
}
这个方法内容很多,省略了一些代码。代码中,有一个if-else,这里主要是根据features,来给layoutResource赋值。
拿到了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();
}
(1)通过调用inflater的inflate方法,将刚刚得到的layoutResource渲染为View对象
(2)调用addView方法,将创建的View对象添加到DecorView中。
所以说DecorView中的子View就是layoutResource布局。
我们再次回到上面的generateLayout方法中,其中调用了findViewById获得了contentParent对象。我们看一下findViewById方法:
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
很明显,就是从DecorView中获取对应id的View对象,而这里的ID_ANDROID_CONTENT就是R.id.content。我们刚刚说了,DecorView的子View是layoutResource,如果我们是Empty Activity的话,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>
我们看到,我们findViewById的View就在这里,是一个FrameLayout。而generateLayout将这个FrameLayout返回了。我们找到上面调用generateLayout的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);
......
}
......
}
mContentParent接收了generateLayout方法的返回值,也就是说mContentParent是R.layout.screen_simple中id为content的FrameLayout对象。我们再回到调用installDecor的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.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
第14行:调用了LayoutInflater的inflate进行了渲染,layoutResID就是我们自己的布局,也就是调用setContentView时传进去的布局id。inflate具体的渲染过程,我们下次再说。其实inflate方法我们都用过,这里就是将我们的布局id,渲染到了我们上面提到的id为content的FrameLayout中。到此,Activity的setContentView就结束了。我们画一张图,来表示一下Activity的整体渲染层级
2.AppCompatActivity的setContentView
我们先进入AppCompatActivity的setContentView中查看
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
getDelegate返回的对象为AppCompatDelegateImpl。这里我就不带着找了,很容易就能找到这个类的。
查看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();
}
这里有几个地方很熟悉:
第4行:调用了DecorView的findViewById,拿到了id为content的View对象。但是不知道mSubDecor和Acitivity的setContentView流程中的DecorView是不是相同的,这里我们先记下来,稍后就知道了
第6行:resId就是我们自己layout的id,将这个layout渲染到id为content的布局上。
第3行:很多重要的工作就是在这个方法的流程中,我们进入查看:
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
....
}
}
刚刚说的mSubDecor是createSubDecor方法返回的,那它到底是如何创建的呢,我们进入createSubDecor查看:
private ViewGroup createSubDecor() {
.....
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
if (!mWindowNoTitle) {
......
} 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);
}
...
}
.....
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;
}
这里的代码很多,我精简了一下
第4行:这里的mWindow没有变化,还是PhoneWindow,所以getDecoreView是PhoneWindow的方法,我们进入查看:
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
installDecor很熟悉吧,这里执行的就是和Activity的setContentView一样的流程,上面对这个流程讲过了,我们直接略过。
我们回到createSubDecor方法中:
第9~20行:这里和Activity的setContentView流程中说的feature含义一样,根据Activity的模式,选择对应布局。这里我们选择一个比较简单的布局解释会更容易一些,也就是id为abc_screen_simple,我们看一下这个布局是什么样的:
<androidx.appcompat.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">
<androidx.appcompat.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" />
</androidx.appcompat.widget.FitWindowsLinearLayout>
这里有一个ViewStub,我们忽略,我们主要看include中的layout,这里引用了abc_screen_content_include,我们查看一下:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.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>
这里只有一个ContentFrameLayout,而这个ContentFrameLayout其实就是FrameLayout。OK,我们大概知道了这个subDecor的布局大概是什么样子了。
我们再回到createSubDecor方法中:
第24~25行:从subDecor中拿到id为action_bar_activity_content的View,也就是上面的ContentFrameLayout
第27行:从PhoneWindow中拿到id为content的View。这里应该很熟悉吧,就是Activity的setContentView流程中,id为content的View,也就是FrameLayout,在R.layout.screen_simple中。
第29~33行:这里是一个循环,将id为content的FrameLayout的所有子View移除,并且将这些View添加到刚刚从subDecor中拿到的id为action_bar_activity_content的布局中。
第35行:将id为content的FrameLayout它的id抹去,变为NO_ID
第36行:将subDecor中id为action_bar_activity_content的布局,id改为content。也就是说,之后如果在有findViewById(R.id.content),拿到的对象就是subDecor中的ContentFrameLayout对象了
第41行:将subDecor添加到了PhoneWindow中的DecorView里面。
经过上面流程探索,我们脑海中对AppCompatActivity的层级情况有了一个大概的印象,接下来我们还是用一张图来表示: