Android setContentView源码分析

概述

还记得初学Android的时候啥也不做,点击“Run”就可以看到程序跑起来,不一会界面就显示了”Hello World”。作为初学者只记得通过setContentView把布局文件和MainActivity关联起来,而具体是怎样实现的没有深究,后面在平常开发的时候经常会遇到疑问,例如使用hierarchyviewer工具查看布局层级发现在我们指定的布局外又包裹着其他父布局、经常看到其他地方说到PhoneWindow是个什么结构?带着这些疑问从头再跟下源码。

问题

  • Activity、Window、PhoneWindow、DecorView之间的关系
  • hierarchyviewer布局分析
  • setContentView方法分析
  • installDecor方法分析
  • generateDecor方法分析
  • generateLayout方法分析

hierarchyviewer

先看下最常见的代码

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
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.hcy.handlerdemo.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>

运行如下

打开SDK中的hierarchyviewer工具查看层级结构如下

可以看到除了最右边的RelativeLayout,外层还包裹了其他的布局,为啥会出现这种情况呢?

setContentView

在MainActivity中调用setContentView(R.layout.activity_main),会走到Activity的setContentView方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
源码路径:android-24/android/app/Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}

Activity中三个方法都会执行getWindow()的setContentView方法,这里的getWindow()的返回的是Window实例

1
2
3
public Window getWindow() {
return mWindow;
}

在Activity的attach方法中实例化

1
mWindow = new PhoneWindow(this, window);

查看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
29
30
31
32
33
源码路径:android-24/com/android/internal/policy/PhoneWindow.java
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) {
//第一次调用时mContentParent为null,通过installDecor实例化。
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { //是否设置了窗口过渡标识,默认为false
//移除mContentParent中所有的子View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//设置了窗口过渡标识的情况
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
//使用TransitionManager进行过渡的处理
transitionTo(newScene);
} else {
//不需要过渡时,通过LayoutInflater的inflate将资源文件转换为View树,并添加至mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//请求设置Window内容的属性值,将其写入一个WindowInsets类中
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
//已设置content View
mContentParentExplicitlySet = true;
}

同样PhoneWindow还有另外两个对应Activity中的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
29
30
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

可以看到,当调用 setContentView(View view)时,其内部会调用setContentView(View view, ViewGroup.LayoutParams params),只是LayoutParams 值为MATCH_PARENT, 与setContentView(int layoutResID)相比,由使用LayoutInflater的inflate解析换成了mContentParent的addView。

小节

  • 如果第一次调用setContentView,执行installDecor();
  • 如果不是第一次调用setContentView,且未设置窗口过渡标志,调用removeAllViews将窗口中所有子View移除(所以在程序中若多次调用setContentView方法显示界面不会有影响)
  • 如果设置了窗口过渡标志,使用TransitionManager进行过渡的处理,否则选择合适的方式将View添加到mContentParent中

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
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
源码路径:android-24/com/android/internal/policy/PhoneWindow.java
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//如果mDecor为null则通过generateDecor初始化
mDecor = generateDecor(-1);
//设置mDecor中子View焦点获取关系,FOCUS_AFTER_DESCENDANTS指定只有mDecor中所有子View
//都不获取焦点时才让mDecor获得焦点
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
//设置mDecor为整个Activity窗口的根节点
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
//将Runnable跑在用户界面线程
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//mDecor不为null设置当前窗口上下文为PhoneWindow
mDecor.setWindow(this);
}
if (mContentParent == null) {
//如果mContentParent为null则通过generateLayout初始化
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
//DecorContentParent位于com.android.internal.widget中,
//是一个接口由应用程序窗口的顶层Decor实现,该类主要为mDecor提供了许多标题/窗口装饰功能
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);
//mDecorContentParent设置图标和Logo的判断
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
(mIconRes != 0 && !mDecorContentParent.hasIcon())) {
mDecorContentParent.setIcon(mIconRes);
} else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
mIconRes == 0 && !mDecorContentParent.hasIcon()) {
mDecorContentParent.setIcon(
getContext().getPackageManager().getDefaultActivityIcon());
mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
}
if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
(mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
mDecorContentParent.setLogo(mLogoRes);
}
// 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) && !mIsStartingWindow) {
//使面板菜单失效
invalidatePanelMenu(FEATURE_ACTION_BAR);
}
} else {
//若decorContentParent为null,根据窗口是否为一个包含Title的窗口决定是否显示title
//如果窗口包含特征FEATURE_NO_TITLE,则隐藏窗口的title view 否则设置窗口的title
mTitleView = (TextView) 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);
}
}
}
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
//设置背景
mDecor.setBackgroundFallback(mBackgroundFallbackResource);
}
// Only inflate or create a new TransitionManager if the caller hasn't
// already set a custom one.
if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
//设置了窗口过渡标识的情况
if (mTransitionManager == null) {
//TransitionManager为空的时先初始化
final int transitionRes = getWindowStyle().getResourceId(
R.styleable.Window_windowContentTransitionManager,
0);
if (transitionRes != 0) {
final TransitionInflater inflater = TransitionInflater.from(getContext());
mTransitionManager = inflater.inflateTransitionManager(transitionRes,
mContentParent);
} else {
mTransitionManager = new TransitionManager();
}
}
//实例化各种过渡动画效果、进入、退出。。。
mEnterTransition = getTransition(mEnterTransition, null,
R.styleable.Window_windowEnterTransition);
mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReturnTransition);
mExitTransition = getTransition(mExitTransition, null,
R.styleable.Window_windowExitTransition);
mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
R.styleable.Window_windowReenterTransition);
mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
R.styleable.Window_windowSharedElementEnterTransition);
mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReturnTransition);
mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
R.styleable.Window_windowSharedElementExitTransition);
mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
USE_DEFAULT_TRANSITION,
R.styleable.Window_windowSharedElementReenterTransition);
if (mAllowEnterTransitionOverlap == null) {
//是否允许进入过渡动画重叠
mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowEnterTransitionOverlap, true);
}
if (mAllowReturnTransitionOverlap == null) {
//是否允许返回过渡动画重叠
mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
R.styleable.Window_windowAllowReturnTransitionOverlap, true);
}
if (mBackgroundFadeDurationMillis < 0) {
//背景消失的持续时间
mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
R.styleable.Window_windowTransitionBackgroundFadeDuration,
DEFAULT_BACKGROUND_FADE_DURATION_MS);
}
if (mSharedElementsUseOverlay == null) {
//另一种过渡效果
mSharedElementsUseOverlay = getWindowStyle().getBoolean(
R.styleable.Window_windowSharedElementsUseOverlay, true);
}
}
}
}

generateDecor方法分析

在installDecor中如果判断为null则通过generateDecor初始化,方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
源码路径:android-24/com/android/internal/policy/PhoneWindow.java
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());
}

这里根据上下文生成一个DecorView对象,DecorView是PhoneWindow类的内部类,继承自FrameLayout

generateLayout方法分析

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//从当前Window的Theme中获取一组属性值,赋给a
TypedArray a = getWindowStyle();
//窗口是否浮动
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);
}
//ActionBar导航栏是否不占布局空间叠加显示在当前窗口之上。
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
//是否设置从左边滑动消失
if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
requestFeature(FEATURE_SWIPE_TO_DISMISS);
}
//当前Activity是否支持全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
//透明状态栏标志位设置
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
false)) {
setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
& (~getForcedWindowFlags()));
}
//透明导航栏标志位设置
if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
false)) {
setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
& (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
}
//是否允许系统壁纸显示在窗口后面
if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
}
//窗口触摸事件标志位
if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB)) {
setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
}
a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMajor,
mFixedWidthMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMinor,
mFixedWidthMinor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedHeightMajor,
mFixedHeightMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedHeightMinor,
mFixedHeightMinor);
}
//是否使用窗口过渡动画,使用TransitionManager
if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
requestFeature(FEATURE_CONTENT_TRANSITIONS);
}
//是否运行Activity过渡动画
if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
}
//窗口是否透明
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
final Context context = getContext();
//获取当前应用的目标sdk版本号
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
//当前目标sdk版本号是否小于11
final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
//当前目标sdk版本号是否小于14
final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
//当前目标sdk版本号是否小于21
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
//是否需要菜单选项
final boolean targetHcNeedsOptions = context.getResources().getBoolean(
R.bool.target_honeycomb_needs_options_menu);
//是否有ActionBar
final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
//目标版本号小于11或者满足小于14且需要菜单选项且无ActionBar,则设置需要菜单选项
setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
} else {
//设置不需要菜单选项
setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
}
if (!mForcedStatusBarColor) {
//设置状态栏颜色
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
//设置导航栏颜色
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}
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)) {
//高版本对System bar的处理
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 (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (a.getBoolean(
R.styleable.Window_windowCloseOnTouchOutside,
false)) {
setCloseOnTouchOutsideIfNotSet(true);
}
}
//设置输入法的状态
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
mIsFloating)) {
/* All dialogs should have the window dimmed */
//dialog出现时窗口状态
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
if (!haveDimAmount()) {
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
}
//设置当前Activity的出现动画效果
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 (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 layoutResource;
//返回一个用于描述当前Window特征的整数值
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
//下面一大段判断根据features值选定窗口资源文件id
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} 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 {
//最常用的Activity窗口修饰布局文件,有title
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.
//最简单的Activity窗口修饰布局文件,无title
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//该步骤很重要,将layoutResource资源文件包含的View树添加到decor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//寻找com.android.internal.R.id.content节点
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) {
//设置进度条模式为indeterminate,
//该模式下进度不显示进度,只显示无限动画
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
//对于选择R.layout.screen_swipe_dismiss布局注册回调
registerSwipeCallbacks();
}
// 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;
}

该方法代码量较多,主要是做了如下操作:

  • Step1:从第4行到第205行,获取窗口style配置,并根据配置里的值判断是否显示标题栏,是否全屏,是否支持ActionBar浮动,动画效果等等
  • Step2:从209行到271行,获取窗口的features,根据features值给容器DecorView添加id为layoutResource布局的根布局
  • Step3:从273行到275行,传入LayoutInflater和指定的布局文件,通过inflate方法将布局文件解析成View,并添加到DecorView根布局当中
  • Step4:277行,从根布局中查找id为com.android.internal.R.id.content的ViewGroup,也就是整个方法的返回值。
  • Step5:278-333行,后续一系列操作,包括设置背景,标题,标题颜色等。

看下Step2中比较重要的一步操作mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)

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();
}

12行判断是否是浮动的窗口创建一个mDecorCaptionView ,13行将资源文件解析成View,之后指定布局的宽高为MATCH_PARENT,26行再把View赋给mContentRoot 。接下来,看看 id为layoutResource的布局到底实现了什么?以我们的代码为例,无标题,资源文件为R.layout.screen_simple(源码路径:platforms/android-24/data/res/layout/R.layout.screen_simple)

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
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/layout/screen_simple.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
This is an optimized layout for a screen, with the minimum set of features
enabled.
-->
<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>

结合hierarchyviewer图正好可以对应上面的布局

可以看到,我们啥也不干的情况下在我们的布局外面包裹着另外一层布局,一个LinearLayout包含了ViewStub 和FrameLayout,ViewStub 为懒加载默认不显示,id为content的FrameLayout作为我们布局文件的父布局,上面Step4查找该布局,之后作为整个generateLayout方法的返回值,而这里的mContentParent又在PhoneWindow的setContentView方法中作为布局填充的父容器,伪代码记忆如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void setContentView(int layoutResID) {
......
if (mContentParent == null) {
installDecor();
}
......
mLayoutInflater.inflate(layoutResID, mContentParent);
}
private void installDecor() {
......
if (mDecor == null) {
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
......
}
......
}

回调

在PhoneWindow的setContentView方法最后会执行回调

1
2
3
4
5
6
7
8
public void setContentView(int layoutResID) {
......
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
......
}

这里的cb位于Windows.java中

1
2
3
4
5
6
7
8
9
private Callback mCallback;
public final Callback getCallback() {
return mCallback;
}
public void setCallback(Callback callback) {
mCallback = callback;
}

在Activity的attach方法中看到进行了设置

1
2
3
4
final void attach(Context context, ActivityThread aThread,
......
mWindow.setCallback(this);
}

说明Activity中实现了该方法,onContentChanged方法如下:

1
2
public void onContentChanged() {
}

竟然是个空方法,setContentView走完会执行该方法,因此我们可以把Activity的各种View的findViewById()方法等都可以放到该方法中,系统会帮忙回调。

总结

  • 创建根视图DecorView对象mDecor
  • 根据程序中选择的Activity的Theme/Style等属性值为窗口添加布局属性和相应的修饰文件
  • 通过findViewById方法获取对应的根布局文件添加到mDecor
  • 通过inflate(加载xml文件)或addView(加载View)方法将Activity的布局文件添加到mContentParent区域(id为content的FrameLayout)
  • 回调Activity的onContentChanged方法

关系图

听说打赏的人运气都不会太差