参考《Android性能优化之布局优化》 侵删
绘制的原理
- Android需要把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
- CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染。
- GPU进行栅格化渲染。
- 硬件展示在屏幕上。
为了能够使得APP流畅,我们需要在每一帧16ms以内完成所有的CPU与GPU计算,绘制,渲染等等操作。也就是帧率为60fps,为什么帧率要为60fps呢,因为人眼与大脑之间的协作无法感知超过60fps的画面更新。这个流程的表现性能取决于View的复杂程度,View的状态变化以及渲染管道的执行性能。
Overdraw(过度绘制):描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,就会导致某些像素区域被绘制了多次,浪费大量的CPU以及GPU资源。(可以通过开发者选项,打开Show GPU Overdraw的选项,观察UI上的Overdraw情况)。
所以我们需要尽量减少Overdraw。
Android布局优化常用方法
综上,布局的优化其实说白了就是减少层级,越简单越好,减少overdraw,就能更好的突出性能。
1. 首先是善用相对布局RelativeLayout。布局里面有多个LinearLayout,如果可以使用RelativeLayout进行改变布局,则用RelativeLayout,减少不必要的层级,减少overdraw。这个可以使用Android IDE自带的工具Hierarchy View去看看布局层级关系。
2. 使用抽象布局标签include、merge、ViewStub
include标签常用于将布局中的公共部分提取出来,比如我们要在activity_main.xml中需要上述LinearLayout的数据,那么就可以直接include进去了。
共用的一个comm_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="16dp"
android:text="这个是MergeLayout"
android:textSize="16sp" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/comm_layout" />
</RelativeLayout>
merge标签是作为include标签的一种辅助扩展来使用,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。如上述include标签引入了之前的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"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="16dp"
android:text="这个是MergeLayout"
android:textSize="16sp"/>
</merge>
viewstub我们过去在布局里暂时不显示的布局,都会用Visible.GONE来先隐藏,然后在业务代码里将其显示,这样做的优点是比较灵活,但是缺点是耗费资源,即它仍会被Inflate,被实例化,设置属性。而viewstub是view的子类,他是一个轻量级View, 隐藏的,没有尺寸的View。他可以用来在程序运行时简单的填充布局文件,去电是ViewStub指定的布局被Inflate后,就不能再通过ViewStude来控制它了。如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/layout_merge"
layout="@layout/item_merge_layout" />
<Button
android:id="@+id/btn_view_show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="显示ViewStub"
android:textAllCaps="false"
android:layout_below="@+id/tv_content"/>
<Button
android:id="@+id/btn_view_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="50dp"
android:layout_toRightOf="@+id/btn_view_show"
android:text="隐藏ViewStub"
android:textAllCaps="false"
android:layout_below="@+id/tv_content"/>
<ViewStub
android:id="@+id/vs_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/item_test_linear_layout"
android:layout_below="@+id/btn_view_show"
android:layout_marginTop="10dp" />
</RelativeLayout>
使用ViewStub没那么灵活,限制比较多:
1. ViewStub只能Inflate一次,之后ViewStub对象会被置为空。
2. 某些布局属性要加在ViewStub而不是实际的布局上面,才会起作用,比如上面用的android:layout_margin*系列属性,如果加在TextView上面,则不会起作用,需要放在它的ViewStub上面才会起作用。而ViewStub的属性在inflate()后会都传给相应的布局。
ConstraintLayout允许你在不适用任何嵌套的情况下创建大型而又复杂的布局。它与RelativeLayout非常相似,所有的view都依赖于兄弟控件和父控件的相对关系。但是,ConstraintLayout比RelativeLayout更加灵活,目前在AndroidStudio中使用也十分方便,就和以前的拖拉控件十分相似。
一些Lint规则如下:
1. 使用组合控件: 包含了一个ImageView以及一个TextView控件的LinearLayout如果能够作为一个组合控件将会被更有效的处理。
2. 合并作为根节点的帧布局(Framelayout) :如果一个帧布局时布局文件中的根节点,而且它没有背景图片或者padding等,更有效的方式是使用merge标签替换该Framelayout标签 。
3. 无用的叶子节点:通常来说如果一个布局控件没有子视图或者背景图片,那么该布局控件时可以被移除(由于它处于 invisible状态)。
4. 无用的父节点 :如果一个父视图即有子视图,但没有兄弟视图节点,该视图不是ScrollView控件或者根节点,并且它没有背景图片,也是可以被移除的,移除之后,该父视图的所有子视图都直接迁移至之前父视图的布局层次。同样能够使解析布局以及布局层次更有效。
5. 过深的布局层次:内嵌过多的布局总是低效率地。考虑使用一些扁平的布局控件,例如 RelativeLayout、GridLayout ,来改善布局过程。默认最大的布局深度为10 。