性能优化之一就是layout的优化,
as 常识:
布局是否合理主要影响的是页面测量时间的多少,我们知道一个页面的显示测量和绘制过程都是通过递归来完成的,多叉树遍历的时间与树的高度h有关,其时间复杂度 O(h),如果层级太深,每增加一层则会增加更多的页面显示时间,所以布局的合理性就显得很重要。
那布局优化有哪些方法呢,主要通过减少层级、减少测量和绘制时间、提高复用性三个方面入手。总结如下:
- 减少层级。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。
- 提高显示速度。使用 ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。
- 布局复用。可以通过includ标签来提高复用。
- 尽可能少用wrap_content。wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 。
- 删除控件中无用的属性。
这里主要说第一点的merge。
对比一下吧,使用merge之前,和使用merge之后的效果:第一张图片是没有使用merge的,第二章是使用merge的。
对应布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.spreadtrumshitaoli.layoutoptimize.MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Not merge" android:textSize="30dp"/> </RelativeLayout>
对应布局:
<merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.spreadtrumshitaoli.layoutoptimize.MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Not merge" android:textSize="30dp"/> </merge>
可以看到少了RelativeLayout 这一层布局。
现在认识到merge可以起到优化作用,辣么,什么场景下用好呢?为什么merge可以优化布局,但是大多数layout没有使用merge呢?
- 如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
- 自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。
- 知道当前父布局的布局是什么,可以使用merge并添加相应的layout属性(正规军(源码中)没有使用),推荐星为0星,不过也是一个方案。
针对3,举个例子:
刚才的例子,relativelayout中,有属性android:layout_alignParentRight 等,那么子添加到这个layout中的merge里,你也可以写这个属性。就在图1对应的layout中,我们
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.spreadtrumshitaoli.layoutoptimize.MainActivity"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="Not merge" android:textSize="30dp"/> <include layout="@layout/merge_tag"></include> </RelativeLayout>
merge_tag如下:其中的android:layout_alignParentBottom="true" 是有效的。
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:text="@string/app_name" android:layout_alignParentBottom="true" android:textColor="@android:color/holo_green_light" android:layout_height="wrap_content" android:layout_width="match_parent"/> </merge>
这种情况不推荐使用。
主要记住1、2的使用场景即可,其实merge标签使用并不太多,更多的是include和viewstub这两个。
下篇文章会介绍一下include及viewstub。
merge为什么会这样?父布局的属性,merge里可以使用,merge到底是view、viewgroup还是什么呢,它是不是个layout?
使用的时候,有没有要注意的点呢?
先来看看merge标签:
在layoutinflater中,有源码
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
}
其中,rInflate中部分代码如下:
{
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);
}
可以看到,会把merge解析到成一个view(注意是解析,merge并不是view,只不过吧内容解析了,因为merge里面的是view),然后add到父布局中,带的param是父布局的。所以上面的3点也就得到解释了。
注意的点:
- merge必须放在布局文件的根节点上。
- merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
- merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
- 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。(其中第三个参数可以省略:源码:
)public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
android 源码例子:
- only in /sprdroid8.1_trunk/frameworks/base/tests/SharedLibrary/lib/res/layout/
1<?xml version="1.0" encoding="utf-8"?> 2 3<merge xmlns:android="http://schemas.android.com/apk/res/android"> 4 <TextView android:id="@+id/name" 5 android:layout_width="wrap_content" 6 android:layout_height="wrap_content"/> 7 <TextView android:id="@+id/street" 8 android:layout_width="wrap_content" 9 android:layout_height="wrap_content"/> 10 <TextView android:id="@+id/cityStateZip" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content"/> 13 <TextView android:id="@+id/country" 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content"/> 16</merge>
sprdroid8.1_trunk/frameworks/base/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/AddressView.java
public classAddressView extends LinearLayout
View view = LayoutInflater.from(context).inflate(R.layout.address, this);