Android源码分析——Activity的绘制

Activity 的setContent()流程

以前的Activity,都是直接继承Activity.java,而现在的Activity则基本都是继承AppCompatActivity.java,自然setContent()是不一样的,那么先捋一捋旧的

Activity.java

先从Activity.java开始看起。

1
2
3
4
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

可以看到,getWindow()方法获取了一个自己Activity持有的Window对象的引用,再调用这个对象的setContent(),之后做一个初始化流程。Window类是一个抽象类:

1
2
3
4
5
6
7
8
9
10
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/

看注释,这大概是一个Activity所呈现界面的顶层Window。他的实现类只有一个,是PhoneWindow。那么就来看看这个PhoneWindow类的setContentView()方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public void setContentView(int layoutResID) {
//首先这里有一个ContentParent,如果为空则做一个初始化
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 {
/*
不需要动画,直接开始加载布局,这里是将layoutResID布局加载到了mContentParent上
而layoutResID是我们交给setContent()的那个布局id
因此我们的Activity最终显示的页面就是加载 到了mContent上
*/
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

再看看mContentParent的定义:

1
2
3
4

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;

可以看到,这个mContentParent其实就是一个ViewGroup

所以在setContent()中主要做了两件事:

  • 初始化一个Window持有的ContentParent(即ViewGroup)对象
  • 将布局文件加载到ContentParent上
    那么现在看看这个所谓的初始化过程做了什么,即installDecor():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {//第一步,发现mDecor没有初始化
//生成一个mDecor对象,并对其初始化
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//让mDecor获取一个当前window的引用
mDecor.setWindow(this);
}
if (mContentParent == null) {//第二步,发现mContentParent没有初始化
//用前面的mDecor生成一个mContentParent对象并初始化
mContentParent = generateLayout(mDecor);

// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
//在mDecor中找一下是否有一个DecorContentParent
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
//有?对这个做初始化
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}

final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}

mDecorContentParent.setUiOptions(mUiOptions);
//…………

} else {//没有?那么从这里开始
//获取一个作为title的view并初始化
mTitleView = findViewById(R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
final View titleContainer = findViewById(R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
mContentParent.setForeground(null);
} else {
mTitleView.setText(mTitle);
}
}
}
//对这个mDecor设置背景(回调)
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}

//之后就是一些无关紧要的东西了
}

再看看这个mDecor是何方神圣:

1
2
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
1
2
3
4
** @hide */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//…………
}

原来mDecor就是一个FrameLayout了。
那么这个初始化过程就分为了两步:

  • 初始化mDecor(一个FrameLayout)
  • 借助mDecor初始化mContentParent

再来分别看看两者是如何初始化的,显示mDecor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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());
}

先是用想办法和获取一个context,然后再调用新的构造器,这里的featureId传进来的是-1。然后看构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
mFeatureId = featureId;

mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);

mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
mForceWindowDrawsStatusBarBackground = context.getResources().getBoolean(
R.bool.config_forceWindowDrawsStatusBarBackground)
&& context.getApplicationInfo().targetSdkVersion >= N;
mSemiTransparentStatusBarColor = context.getResources().getColor(
R.color.system_bar_background_semi_transparent, null /* theme */);

updateAvailableWidth();
//前面不是有一个在发现mDecorView不为Null时要赋予一个当前window引用吗?这里就是在初始化完成后再做的
setWindow(window);

updateLogTag(params);

mResizeShadowSize = context.getResources().getDimensionPixelSize(
R.dimen.resize_shadow_size);
initResizingPaints();
}

至此一个DecorView就初始化完成了,他实际上是一个FrameLayout。接下来看看这个mContentParent是如何通过DecorView来生成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//这里先拿到一些属性
TypedArray a = getWindowStyle();
//…………
//这里开始先是对每一种属性做判断了,比如是否悬浮?是否无标题?等等
//具体方法和我们写自定义View时是一样的,这里省略了
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}

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);
}
//………………
//这里开始取出部分app相关的信息,比如targetsdk
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
//………………

WindowManager.LayoutParams params = getAttributes();
//这里是和高端设备相关的设置
// Non-floating windows on high end devices must put up decor beneath the system bars and
// therefore must know about visibility changes of those.
if (!mIsFloating && ActivityManager.isHighEndGfx()) {
if (!targetPreL && a.getBoolean(
R.styleable.Window_windowDrawsSystemBarBackgrounds,
false)) {
setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
}
if (mDecor.mForceWindowDrawsStatusBarBackground) {
params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
}
}

//………………

if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
R.styleable.Window_windowAnimationStyle, 0);
}

// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mBackgroundResource == 0) {
mBackgroundResource = a.getResourceId(
R.styleable.Window_windowBackground, 0);
}
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
}
mBackgroundFallbackResource = a.getResourceId(
R.styleable.Window_windowBackgroundFallback, 0);
if (false) {
System.out.println("Background: "
+ Integer.toHexString(mBackgroundResource) + " Frame: "
+ Integer.toHexString(mFrameResource));
}
}
if (mLoadElevation) {
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
}
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}

// Inflate the window decor.
//这里开始,就来真的了
//这个int值代表了要加载的布局的id
int layoutResource;
//所需的属性
int features = getLocalFeatures();
//然后,根据属性不同的需求,获取不同的布局文件id
// 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 {
// Embedded, so no decoration is needed.
//记住这个布局文件id
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
//标识着这个decorview开始改变了
mDecor.startChanging();
//将刚才那个布局文件,加载到decor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//通过findviewbyid()的方式获取这个contentParent,记住这个ID_ANDROID_CONTENT
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

//………………

// 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;
}
//为decorview设置背景
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();
//最后,返回这个contentParent
return contentParent;
}

总结一下,就是给这个framelayout————DecorView设置了一种布局,然后通过findviewbyid的方式获取一个contentparent的。那么这两者有什么关系呢?观察到前面提到了两个id,联系就在这里!所以接下来看看具体设置布局的逻辑。

首先看看加载布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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();
}

看到layoutInflater就知道了,这里果然是加载layoutResource指向的那个布局,这里加载后为一个叫做root的View,然后通过调用addView()方法————我们知道DecorView本身是一个FrameLayout————将root加载到自己这个FrameLayout中。

接下来看看layoutResource所引用的布局R.layout.screen_simple,即screen_simple.xml的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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>

嗯,一个LinearLayout,包含了一个ViewStub占位和一个FrameLayout。做一个猜测,这就是我们Activity最普通的初始界面,即一个状态栏+一个主界面。然后发现下面那个FrameLayout的id是content,再回到刚才方法中,通过findviewbyid初始化找到contentParent的时候用的id是哪个?

1
2
3
4
/**
* 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引用的view就是我们的主布局要加载的地方,也就是在刚才那个xml文件中的FrameLayout!

到此为止,一个installDecor()的过程基本完成了,来捋一捋。

首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到mContentParent中就可以了。

回过头来看看setContentView:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

@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 {
//这里可以看到,前面初始化结束后,果然是将我们自己写的布局加载到了mContentParent中!
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

至此,Activity的setContent()流程就是走完了,大致知道了布局是怎么加载进来的。接下来看看新的AppCompatActivity是如何加载布局的

AppCompatActivity

接下来再看看AppCompatActivity是如何加载布局的
先看AppCompatActivity.java的setContentView()方法:

1
2
3
4
5

@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}

这里通过getDelegate()方法获取了一个对象的引用,再调用他的setContentView()方法,相当于做了一个代理。
那么现在问题拆分为两步:

  • 代理的对象是如何创建的
  • 代理对象的setContentView()是如何执行的

先看第一个问题:

1
2
3
4
5
6
7
8
9
10
11

/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}

这里用来做代理的,是一个AppCompatDelegate对象,叫mDelegate,他是通过一个静态方法create()创建的,那么先看看这个类是什么:

1
2
3
<p>An {@link Activity} can only be linked with one {@link AppCompatDelegate} instance,
* therefore the instance returned from {@link #create(Activity, AppCompatCallback)} should be
* retained until the Activity is destroyed.</p>

再来看看他的create()方法:

1
2
3
4
5
6
7
8
/**
* Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
*
* @param callback An optional callback for AppCompat specific events
*/
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}

可以看到,这里最终是根据不同的sdk版本来创建不同的AppCompatDelegateImplxxx对象,分别点进去看看后会发现,最终都是到了AppCompatDelegateImplV9.java,然后:

1
class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
1
2
3
AppCompatDelegateImplV9(Context context, Window window, AppCompatCallback callback) {
super(context, window, callback);
}

所以最终是调用了父类的构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
AppCompatDelegateImplBase(Context context, Window window, AppCompatCallback callback) {
mContext = context;
mWindow = window;
mAppCompatCallback = callback;

mOriginalWindowCallback = mWindow.getCallback();
if (mOriginalWindowCallback instanceof AppCompatWindowCallbackBase) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
// Now install the new callback
mWindow.setCallback(mAppCompatWindowCallback);

final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
context, null, sWindowBackgroundStyleable);
final Drawable winBg = a.getDrawableIfKnown(0);
if (winBg != null) {
mWindow.setBackgroundDrawable(winBg);
}
a.recycle();
}

这样就完成了。接下来看看setContentView()是如何执行的。
进入AppCompatDelegateImplV9.java的setContentView():

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void setContentView(int resId) {
//确保创建一个SubDecor
ensureSubDecor();
//通过findviewbyid的方式找到android.R.id.contentd代表的view,作为一个contentParent
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//清空
contentParent.removeAllViews();
//将我们自己的布局文件加载到这个contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}

再看看这个SubDecor是什么:

1
2
3
4

// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
private ViewGroup mSubDecor;

所以我们自己写的布局文件最终是被加载到了一个id为content的ViewGroup上,而这个ViewGroup是通过subDecor来找到的,而这个SubDecor也是一个ViewGroup。那么重点就是ensureSubDecor()了,他的作用应该就是初始化一个SubDecor了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//创建一个SubDecor
mSubDecor = createSubDecor();

// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
onTitleChanged(title);
}

applyFixedSizeWindow();
//做一个install?
onSubDecorInstalled(mSubDecor);
//标识已经installed
mSubDecorInstalled = true;

// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!isDestroyed() && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}

现在就分为了两步:

  • mSubDecor是如何被创建的
  • 创建成功之后做了什么

第一个问题,看createSubDecor()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192

private ViewGroup createSubDecor() {
//和自定义View时获取属性类似,这儿是从AppCompatTheme获取了属性
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//这里判断如果没有加这个属性的话会抛出异常
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
//接下来就是普通的挨个遍历属性了
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();

// Now let's make sure that the Window has installed its decor by retrieving it
//这里通过Window对象(即PhoneWindow)调用了getDecorView()方法,猜测是获取Decor
//这里是重点,待会儿分析
mWindow.getDecorView();

final LayoutInflater inflater = LayoutInflater.from(mContext);
//创建了一个subDecor引用,还未实例化
ViewGroup subDecor = null;

//根据不同需求,让subDecor 装载不同的布局
if (!mWindowNoTitle) {
if (mIsFloating) {
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);

// Floating windows can never have an action bar, reset the flags
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
/**
* This needs some explanation. As we can not use the android:theme attribute
* pre-L, we emulate it by manually creating a LayoutInflater using a
* ContextThemeWrapper pointing to actionBarTheme.
*/
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

Context themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
} else {
themedContext = mContext;
}

// 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);

mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());

/**
* Propagate features to DecorContentParent
*/
if (mOverlayActionBar) {
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (mFeatureProgress) {
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
}
if (mFeatureIndeterminateProgress) {
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
}
}
} 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) {
// If we're running on L or above, we can rely on ViewCompat's
// setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(subDecor,
new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
final int top = insets.getSystemWindowInsetTop();
final int newTop = updateStatusGuard(top);

if (top != newTop) {
insets = insets.replaceSystemWindowInsets(
insets.getSystemWindowInsetLeft(),
newTop,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}

// Now apply the insets on our view
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
} else {
// Else, we need to use our own FitWindowsViewGroup handling
((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
new FitWindowsViewGroup.OnFitSystemWindowsListener() {
@Override
public void onFitSystemWindows(Rect insets) {
insets.top = updateStatusGuard(insets.top);
}
});
}
}

//到此为止,subDecor算是实例化完毕了

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
+ " }");
}

if (mDecorContentParent == null) {
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}

// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
//这里开始重点来了

//从subDecor中拿到了一个ContentFrameLayout,注意id为R.id.action_bar_activity_content
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//从window中拿到一个id为content的ViewGroup
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// There might be Views already added to the Window's content view so we need to
// migrate them to our content view
// 这里,依次从window中那个viewgroup中取出子View
//然后将他们放入那个从subDecor中拿到的Content中
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}

// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
//全部挪完之后,给原来window中的那个ViewGroup把id值为NO_ID
windowContentView.setId(View.NO_ID);
//然后偷梁换柱,把那个ContentFrameLayout的id设为了content
contentView.setId(android.R.id.content);

// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
//把那个背景也去掉了
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}

// Now set the Window's content view with the decor
//狸猫换太子,直接把subDecor给了Window
mWindow.setContentView(subDecor);

contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}

@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});

return subDecor;
}

再来看看那个重点标记的方法:

1
2
3
4
5
6
7
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}

哦?原来window的getDecorView()方法其实就是前面提到过的installDecor()方法诶!之前说过,installDecor()方法是什么作用来着?

首先要初始化一个叫做DecorView的FrameLayout,他是和当前Window息息相关的。我们知道一个Activity,他的显示界面这个模块是交给了Window管理,而在Window中则是交给了DeocrView。这个DecorView会根据不同的需求(主题)来给自己填上一个不同的布局。然后在加载进来的这个布局中,有一个ViewGroup是专门用来显示我们编写的界面的,这个ViewGroup会通过findViewById()的形式在DecorView中找到,然后交给mContentParent,这样我们要将自己写的布局加载进来的时候,就是直接加载到mContentParent中就可以了。

马上接触到真相了,再随便找个刚才所引用到的布局文件看看,比如R.layout.abc_screen_simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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>

还有abc_screen_content_include.xml:

1
2
3
4
5
6
7
8
9
10
<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>

看看那个id:action_bar_activity_content,而且他还是很个ContentFrameLayout 发现没有?这里的SubDecorView和以前的DecorView逻辑是很像的!都是以自己作为一个大的ViewGroup,里面放另一个小ViewGroup,在这个小ViewGroup中,还有一个ViewGroup作为根布局。

捋一捋刚才的流程:

  • 首先创建了两个DecorView,一个就是以前Activity直接用的那个DecorView,另一个叫做SubDecorView
  • 将旧DecorView的content内容交给SubDecorView的content
  • 将SubDecorView作为一个整体,交给DecorView

总之,就是一个替换的过程。

再回到前面看看:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public void setContentView(int resId) {
//这里做了刚才所说的一切,现在是两个DecorView嵌套起来了
ensureSubDecor();
//id为content的ViewGroup现在的内容其实就是以前的DecorView用的那个
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//清空
contentParent.removeAllViews();
//将我们自己的布局文件加载到这个contentParent中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}

至此,Activity的AppCompatActivity的setContent()的流程都分析完了,总结一下:

  • 一个Activity,有很多功能,其中的“展示东西给别人看”这个功能,是交给自己的一个Window对象来管理的。
  • Window包含一个ViewGroup,作为根ViewGroup
  • 根ViewGroup,根据不同的需求(即主题定义等)会加载不同的布局文件
  • 以最基本的一种布局来讲,他包含一个title和一个content
  • 我们setContent()时传入的布局文件id所指向的那个布局文件,会被加载到这个content中
  • Activity和AppCompatActivity在这里的区别在于,Activity单纯的用一个DecorView,AppCompatActivity则是在原来的基础上,加了一个SubDeocrView,将旧的DecorView的内容放到SubDecorView的content中,然后将SubDecorView作为整体放入旧的DecorView的content中,也就是说,一个DecorView包裹着一个SubDecorView