源码分析 merge 标签减少布局层级的秘密(Android Q)
我在《Android 渲染性能优化——你需要知道的一切!》一文中介绍过,merge 标签用于减少 View 树的层次来优化 Android 的布局。
我们在布局中使用 merge 标签可以减少不必要的层级。
使用场景
merge 标签可以优化布局层级,那么它有哪些主要的使用场景呢?
- merge 最常见的是和 include 标签一块使用。这样在复用布局的时候,也就不会增加多余的布局嵌套了,解决了只有 include 标签带来的问题。
- 另外,在自定义组合 View 的时候(例如,继承自 FrameLayout、LinearLayout 等),我们通常会创建一个自定义一个布局,并且通过索引 id 添加到自定义 View 中,这时如果不用 merge 标签,无形中会增加了一层的嵌套。
例如,我们自定义了一个容器 View,类名为 TestLayout,继承自 LinearLayout,该自定义控件的填充布局就可以使用 merge 标签。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</merge>
TestLayout 继承自 LinearLayout,所以它本身就是一个 LinearLayout,如果我们将布局中的 merge 修改为 LinearLayout,就会出现2个 LinearLayout 嵌套,其中一个就是多余的。
merge 标签原理&源码解析
那么,merge 标签减少层级的原理是什么呢?
要想知道它的原理,最直接的方法就是查看 Android 源码。接下来我们来通过源码来分析它的实现原理。
通过阅读前文《Android Q LayoutInflater布局生成View源码详解》中的分析可知,Android 布局的生成视图对象,是通过 LayoutInflater 服务来处理的,LayoutInflater 的 inflate 方法负责解析布局并生成 View 对象。merge 标签的处理过程,就包含在这个过程中,我们来看 LayoutInflater 的 inflate 方法。
LayoutInflater 的 inflate 方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
……
final AttributeSet attrs = Xml.asAttributeSet(parser);
……
try {
……
//如果根标签是 merge 标签
if (TAG_MERGE.equals(name)) {
//如果根节点是 merge,并且 root 是 null 或者 attachToRoot == false,则抛出异常。
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//遍历布局并生成View
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//非 merge 跟标签,则创建根标签所代表的 View 对象。
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
//获取根标签 View 对象的视图属性,这里会使用根标签所设置的属性来生成属性对象。
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//对根标签设置它的属性
temp.setLayoutParams(params);
}
}
……
//以根标签对象为根,递归遍历它的 xml 子视图。
rInflateChildren(parser, temp, attrs, true);
……
}
}
}
}
对于以 merge 标签为根标签的布局文件:
- 将 merge 标签的布局文件,生成为视图时,root 必须存在,并且 attachToRoot 必须为 true,否则就会抛出 InflateException 异常。
- 接下来,调用 rInflate 方法,遍历布局中的子标签并生成 View。
- 这里会把 root (该布局文件在视图树上的父视图)和 attrs 传递给 rInflate 方法,用来创建布局文件的子视图。
对于以非 merge 标签为根标签的布局文件:
- 创建根标签所代表的 View 对象。这在 merge 标签时,并没有创建,因为 merge 并不是一个视图组件。
- 获取根标签 View 对象的视图属性,这里会使用根标签所设置的属性来生成属性对象,然后对根标签设置它的属性。这在 merge 标签时,也没有执行,所以 merge 标签上设置的属性不会生效。
- 以根标签对象为根,递归遍历它的 xml 子视图。这在 merge 标签时,是以 root 为根(作为参数),对 merge 标签的子标签进行遍历的。这一点就是 merge 标签会少一层的秘密!
LayoutInflater 的 rInflate 方法
void rInflate(XmlPullParser parser, View parent, Context context
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
……
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
……
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
……
}
…… ……
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
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);
}
}
……
}
该方法的目的是:遍历并且创建 xml 布局文件中,根标签的子标签所代表的的 View 组件对象。
- rInflate 方法通过递归解析 xml 布局文件内容,创建 View,并添加到 parent 中。
- 这里要注意,merge 标签必须是布局文件的根节点!
好了,到了这里,merge 标签的秘密,我们已经知道了。
总结
我们来总结下:
-
merge 标签可以用于减少 View 树的层次来优化 Android 的布局。
-
Android 布局的生成视图对象,是通过 LayoutInflater 服务来处理的,LayoutInflater 的 inflate 方法负责解析布局并生成 View 对象。
-
使用 merge 标签时,merge 标签必须是布局文件的根节点!
-
将 merge 标签的布局文件,生成为视图时,root 必须存在,并且 attachToRoot 必须为 true,否则就会抛出 InflateException 异常。
-
在 merge 标签上设置的属性无效!
-
在解析布局文件时,正常情况下会以根标签对象为根,递归遍历它的 xml 子视图;而在使用 merge 标签时,则是以 root 为根(作为参数),传递给 rInflate 方法创建子视图。这一点就是 merge 标签会少一层的秘密!
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。