性能优化之布局优化篇二 使用<merge>标签

本文探讨性能优化中的布局优化策略,重点解析<merge>标签在减少视图层级、提升UI效率方面的作用。通过理解inflate()方法的工作原理,阐述如何利用<merge>优化Android应用的布局解析过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

在xml布局文件中,多余的布局节点和嵌套会导致解析变慢。使用<merge>标签可以减少视图层级的嵌套,达到布局优化的效果。

什么情况下需要考虑使用Merge标签呢?
1.一个布局文件需要被添加到另一个父布局中(可以在xml中使用<include>添加,或在java代码中使用inflate()添加)时,可以使用merge作为该布局的根节点。这样,当被添加进父布局时根节点会自动被忽略,所有的子节点直接被添加到父布局。
2.布局文件的根节点是<FrameLayout>,不需要设置background或padding等属性,其作为Activity的视图时,可以使用<merge>代替<FrameLayout>(因为Activity内部会自动创建一个FrameLayout,setContentView()传入的布局会添加到FrameLayout中)。

二、代码示例
在开发中,一个通用布局经常会使用<include>标签添加到多个父布局中。
比如,现在有一个布局文件button_view.xml。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/subroot"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

我们使用<include>标签将button_view添加到activity_main.xml中。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/root"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <include layout="@layout/button_view"/>
</LinearLayout>

此时的View层级结构如图所示。

接下来,将button_view.xml的根节点修改为<merge>。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/subroot"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</merge>

再次观察View层级结构,button_view自身的根节点已经不存在了。

三、源码分析

当我们在布局根节点中使用了<merge>标签,Android内部是如何忽略掉根节点,直接添加子View的呢?
在Android中,xml布局文件是通过LayoutInflater类中的inflate(int resource,ViewGroup root,boolean attachToRoot)方法,转化为View对象的。关于inflate()方法的内部实现,可参考文章 “inflate()方法详解和源码分析”
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

在inflate()方法中,将布局resource转化为XmlResourceParser对象来解析xml文件,接下来调用inflate(XmlPullParser parser,ViewGroup root,boolean attachToRoot)方法。


public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
	    ......

            final String name = parser.getName();

            // 根布局标签是"merge"
            if (TAG_MERGE.equals(name)) {
	        // 满足root!=null&&attachToRoot=true才行,也就是说"merge"无法独立存在,必须要添加到ViewGroup中
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                // 调用rInflate()方法
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {// 根布局标签不是"merge",调用createViewFromTag()把根布局的View创建出来
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                if (root == null || !attachToRoot) {
                    result = temp;
                }

		......
            }
        } catch (Exception e) {
	    ......
        }

        return result;
    }
}
方法内部,根据参数parser获取到根布局节点的名称。如果根节点是merge,会调用调用rInflate()方法,最后返回参数root;否则,直接根据根节点的名称,调用createViewFromTag()方法创建对应的View对象,返回该view对象或参数root。

进入rInflate(XmlPullParser parser,View parent,Context context,AttributeSet attrs,boolean finishInflate)方法。
void rInflate(XmlPullParser parser, View parent, Context context,
              AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    final int depth = parser.getDepth();
    int type;

    // 循环遍历布局中的所有标签
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        ......

	final String name = parser.getName();

        // 根据标签名称创建对应的View对象
        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);
	// 把View对象添加到参数parent中
        viewGroup.addView(view, params);
    }

    ......
}
rInflate()是一个通用的方法,这里紧接着上面的逻辑,以merge标签为例。方法内部,通过while循环,找出merge标签中的每一个节点,分别创建对应的View对象,直接把View对象添加到参数parent中。到这里,我们就明白了merge标签内部的View是如何被直接添加到父视图ViewGroup中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值