自从1983年第一台图形用户界面的个人电脑问世以来,几乎所有的PC操作系统都支持可视化操作,Android也不例外。对于所有Android Developer来说,我们接触最多的控件就是View。通常,我们使用自定义View,需要了解最多的除了事件分发,就是View的绘制过程。然而关于View的绘制,涉及到的知识点纷繁复杂,这么多的代码知识,要梳理起来,肯定是先要找个头。那么平常我们用的最多的方法是哪个方法呢?当然是setContentView()

setContentView

首先我们直接在Android Studio中找到一个Activity(请注意,本文分析的是Activity,如果你看的是AppCompatActivity,实际代码会有出入),然后找到setContent方法然后点进去,我们可以看到

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

然后查找getWindow()方法

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

得知调用的是Window类的setContent()方法。然后再全类搜索mWindow,在attach方法中找到了赋值语句。

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
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) {
···
mWindow = new PhoneWindow(this, window);
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);
}
···
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();
···
}

然后就引出了我们今天主要分析的对象PhoneWindow。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@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;
}

@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
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;
}

三个方法大体的处理流程是:

  1. 初始化DecorView
  2. 检查并处理转场动画
  3. 将实际要显示的Layout或者View添加到mContentParent中
  4. 通知Callback(即Activity)调用onContentChanged方法

DecorView

在上面的流程里面,相对比较重要的就是第一步的初始化DecorView,也就是installDecor()方法,下面我们继续分析这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void installDecor() {
···
if (mDecor == null) {
mDecor = generateDecor(-1);
···
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
···
}
···
}

installDecor()方法里,主要做的就是两件事,一个是generateDecor,生成mDecor,还有一个是generateLayout,生成mContentParent。generateDecor中,没有太多复杂逻辑,就是做一些判断,然后实例化出来一个DecorView,这里需要说一点的就是在Android7.0以前,DecorView是PhoneWindow的内部类,7.0以后,DecorView单独提出来变成了一个类,所以如果有用到反射的话,这里可能会出现问题,需要做好版本判断。然后我们看下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
protected ViewGroup generateLayout(DecorView decor) {
//通过系统获取样式
TypedArray a = getWindowStyle();
//然后一系列的判断,获取样式里的属性,然后设置features
//例如是否悬浮,是否有Title等等
···
int layoutResource;
int features = getLocalFeatures();
//拿到刚才设置的features,作出一系列if eles判断,找出对应的resourceId
//然后调用DecorView的onResourcesLoaded,对这个layout进行inflate
···
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

//通过com.android.internal.R.id.content找到对应的mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//在经过一系列后续设置
···
mDecor.finishChanging();
//最后返回contentParent
return contentParent;
}

在generateLayout中,大概的流程是这样的。

  1. 先获取样式
  2. 设置样式到Window的features里
  3. 拿到features,判断对应的resourceId
  4. 通过resourceId,inflate出来一个ViewGroup,添加到mDecor中
  5. 再通过findViewById,找到刚刚inflate的ViewGroup中的 com.android.internal.R.id.content,作为mContentParent
  6. 在经过一系列设置
  7. 返回contentParent

小结

到此,我们的setContentView就已经基本走完了,剩下的就等着Activity、WindowManager、WindowManagerGlobal、ViewRootImpl去调用了,这些类的调用,涉及到了Activity的启动流程,我们会在其他笔记中详细分析这一过程。

下面会上一张整个setContentView的时序图,用来巩固一下刚才的流程。

上图为Android setContentView的时序图

系列文章

Android 视图及View绘制分析笔记之setContentView
View绘制分析笔记之onMeasure
View绘制分析笔记之onLayout
View绘制分析笔记之onDraw


原创文章,转载请先联系作者:spiny.tech@gmail.com