Android中的View加载是怎样执行的
xx
目次
在 Android 中,Activity
是四大组件之一,负责将 视图(View
)与 生命周期 关联。当一个 Activity
创建时,通常会通过 setContentView
设置内容,其本质是将一个 View
添加到系统预留的容器中。
在这个过程中,xml 文件会被解析成一个树状结构的 View。本文主要介绍 xml 布局是如何转化为 View 的。
LayoutInflater 的核心流程 #
加载 xml 布局文件时,会用到 LayoutInflater
,解析逻辑就在其中。
inflate
的关键逻辑如下(简化保留核心部分):
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
| public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
// merge 标签会合并到父布局中(内部不能再嵌套 merge)
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建根节点 View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 解析子节点
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
return (root == null || !attachToRoot) ? temp : root;
}
}
|
这里可以看到分为两种情况:
merge
标签:直接展开合并到父布局,内部不能再嵌套 merge
。- 普通标签:通过
createViewFromTag
创建根节点,然后递归解析子节点。
rInflate 的解析子节点逻辑 #
rInflate
方法中,会继续递归创建子 View 并添加到父容器:
1
2
3
4
5
6
7
8
9
10
11
12
| // LayoutInflater.java
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归解析子节点
rInflateChildren(parser, view, attrs, true);
// 添加到父容器
viewGroup.addView(view, params);
}
|
而 rInflateChildren
只是对 rInflate
的再封装:
1
2
3
| final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
|
View 的创建方式 #
在创建 View 时,分为两类:
- 常见控件(TextView、ImageView、Button 等):通过
AppCompatViewInflater
的枚举分支直接创建。 - 其他自定义或不在枚举中的控件:通过反射调用构造方法创建。
这也是为什么所有自定义 View 必须要有 固定签名的构造方法(如 (Context context, AttributeSet attrs)
)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| private View createViewByPrefix(Context context, String name, String prefix) {
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor == null) {
Class<? extends View> clazz = Class.forName(
prefix != null ? (prefix + name) : name,
false,
context.getClassLoader()
).asSubclass(View.class);
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
return constructor.newInstance(mConstructorArgs);
}
|
其他细节 #
- 如果 xml 中定义了
android:onClick
,会在这里解析并绑定对应的方法(虽然现在已经很少使用这种写法)。 - 布局文件中所有非
merge
根节点及其子 View,最终都会通过 rInflate
的递归过程,逐一生成并挂载到父容器。