opoojkk

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

这里可以看到分为两种情况:

  1. merge 标签:直接展开合并到父布局,内部不能再嵌套 merge
  2. 普通标签:通过 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 时,分为两类:

  1. 常见控件(TextView、ImageView、Button 等):通过 AppCompatViewInflater 的枚举分支直接创建。
  2. 其他自定义或不在枚举中的控件:通过反射调用构造方法创建。

这也是为什么所有自定义 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);
}

其他细节 #

标签:
Categories: