Android 性能优化之布局优化

时间:2022-12-09 18:42:29
  性能优化是任何一个软件最后都无法避开的一个话题。对于一个APP而言,创造出来不难,可是创造出一个高性能,各种顺畅的APP还是相当有难度的。而Android性能优化是一个比较大的话题,本文就从Android性能优化的最初也是最原始开始讲起。提到性能优化,往往都是去查内存泄漏,去查bitmap是否recycle,以及是否有资源永远无法释放。可是,我想Android性能优化的第一步应该是布局的优化,因为我们创造APP的时候,我们的第一步通常就是编写布局XML,可以说布局优化非常的重要。Android的屏幕刷新(渲染)是60帧每秒的,平均下来,一个界面的布局measure,layout,draw总时间必须在16ms内完成才不会让用户感觉到卡顿!

 首先我们要判断一个界面是否需要优化布局,这个很简单,标准上面已经说了,要尽量控制在16ms以内!

Android 性能优化之布局优化   Android 性能优化之布局优化

手机开发者选项—>GPU呈模式分析。 (测试机小米Max)

绿色是基线也就是16ms ,要尽量让条形柱状图控制在绿色基线以下。可以看看国内做的比较好的应用QQ,基本都控制在基线以下。

  判断的标准有了,接下来我们将按照如下的方法进行优化:

  1.布局的选择。

  2.减少布局的嵌套层次。

  3.防止过度重绘。

  4.使用Hierarchy View 工具进行布局优化。

  

 一、布局的选择:

 android官方给我提供的布局常用的要数LinearLayout、FrameLayout以及RelativeLayout用的是最多的也是最常用的,但是它们之间就没有性能的上的差别?在能满足布局需求的情况又如何选择?(比如实现子控件居中显示)

<A 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"
>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</A>
如果A是LinearLayout只需要加上 android:gravity="center"即可;如果A是FrameLayout 那么字View加上android:layout_gravity="center"也能完成;RelativeLayout的话,子View加上android:layout_centerInParent="true"。 然后我们需要测试下,在完成这样一个需求的情况下,这样一个布局耗时与消耗内存的关系。

经测试的得数据:(金立手机  android 5.1系统   测试6次):

LinearLayout       平均耗时:908.7ms        平均消耗内存:3.227M

FrameLayout      平均耗时:950.8ms        平均消耗内存:3.504M

RelativeLayout   平均耗时:998.5ms        平均消耗内存:5.083M

从数据我们可以的出一个初步的结论(在同等条件下):它们3个的性能排行榜:LinearLayout>FrameLayout>RelativeLayout


二、减少布局的嵌套:

 众所周知,android的视图需要经过测量,布局,以及绘制三个步骤。那么你嵌套的层次越多,布局需要递归查找子View的时间消耗就越多,最后整体完成measure,layout,draw的总时间就越长,那么画面将变得越卡顿。

<A xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<A xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
...

<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
...
</A>
</A>
测试下多层嵌套的影响:

嵌套1层:

A=LinearLayout    

View  measure  2     layout  2  draw  1

A=FrameLayout

View  measure  2     layout  2  draw  1

A=RelativeLayout

View  measure  4     layout  2  draw  1

嵌套2层:

A=LinearLayout    

View  measure  2     layout  2  draw  1

A=FrameLayout

View  measure  2     layout  2  draw  1

A=RelativeLayout

View  measure  8    layout  2  draw  1

嵌套三层:

A=LinearLayout    

View  measure  2     layout  2  draw  1

A=FrameLayout

View  measure  2     layout  2  draw  1

A=RelativeLayout

View  measure  16     layout  2  draw  1

由数据可以得出结论:LinearLayout和Framelayout无论嵌套多少层View的都只会measure 2次,layout 2次,draw 1次,而RelativeLayout 则是measure 2的(n+1)幂次方次,layout 2次,draw 1次:

也可初步得嵌套性能结论:LinearLayout=FrameLayout>RelativeLayout   (尤其是RelativeLayout需要慎用)。

减少布局的嵌套层次主要通过<include> 、<merge>(主)、 <ViewStub> 三大标签以及控件的属性来减少嵌套层次:

1.<inclue>标签:这个没什么好讲的主要是重用布局,一定程度上减小apk的大小以及和<merge>标签配合使用。但是需要注意的是如果,在使用<include>标签时,如果给<include>标签添加了属性,那么它会覆盖被引入的布局的根布局的相同属性。另外如给<include>标签添加了id属性那么就不直接使用findViewById了,需要找到先inflate<include>标签的跟布局,然后通过这个View进行findViewById。

2.<merge>标签:这个标签是主要用来减少嵌套层次的,比如如下布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout >
如果你直接把以上布局通过<include>标签添加到另外一个布局中,那么LinearLayout就会是让<View>嵌套层次增加一层布局,可是LinearLayout对于View而言并没有什么属性依赖,这时我们就可以把LinearLayout 换成<merge>标签了,这样可以减少一层嵌套。当然你可能说上面的例子没有什么建树,实际开发中谁会这么傻。嗯哼,我们Activity中setContentView实际上是把我们的跟布局设置到一个FrameLayout布局里面去了,这个时候如果我们自己写的根布局也是FrameLayout(没有background属性等一些特殊点的属性的话)我们完全可以用<merge>标签取代啊。下面说说<merge>标签的使用场景:

子控件没有对根布局有任何的属性依赖,父布局也没有background等一些特殊属性,对子控件而言完全就是个容器。

在LinearLayout里面嵌入一个布局(或者视图),而恰恰这个布局(或者视图)的根节点也是LinearLayout。

需要注意的是<merge />只能作为XML布局的根标签使用。当Inflate以<merge />开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。

3.<ViewSub>标签:这个标签虽然不是用来减少嵌套层次的,但是却能起到布局优化的作用。ViewStub是一个轻量级的View,没有尺寸,不绘制任何东西,因此绘制或者移除时更省时。(ViewStub不可见,大小为0),实现View的延迟加载,避免资源的浪费,减少渲染时间,在需要的时候才加载View。在加载完后会被移除,或者说是被加载进来的layout替换掉了。需要注意的是:ViewStub所要替代的layout文件中不能有<merge>标签。

<ViewStub
android:id="@+id/stub"
android:inflatedId="@+id/aim_layout”
android:layout="@layout/include_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
用ViewStub加载layout文件时,可以调用 setVisibility(View.VISIBLE) 或者 inflate()。

((ViewStub) findViewById(R.id.stub)).setVisibility(View.VISIBLE);
// or
View root = ((ViewStub) findViewById(R.id.stub)).inflate();
一旦ViewStub visible/inflated,则ViewStub将从视图框架中移除,其id stub也会失效。ViewStub被绘制完成的layout文件取代,并且该layout文件的root view的id是android:inflatedId指定的id aim_layout.

4.尽量使用控件的属性完成需求

比如要完成左右两端是图片中间是文字的布局,使用组合控件组合能完成,但是这绝对是下下之策。这个需求使用TextView的 android:drawableLeft=""和android:drawableRight=""

同样也是能完成的!同样利用这些属性能完成 Android 自定义自动清空EditText


三、防止过度重绘:

多次对同一个区域进行onDraw重新绘制,同样是比较消耗性能和时间的,因为我们看见的视图就像Photoshop视图层一样最上面的颜色会覆盖下面的,如果没有必要的话是需要减少重绘的。首先我们需要看怎样才是重绘?重绘的标准是什么?

通过手机开发者选项可以看手机重回结果:手机开发者选项—>过度重绘调试。

Android 性能优化之布局优化   Android 性能优化之布局优化

可以看见百度地图的重绘是控制在1次和2次之内的,很少有淡红色和深红色的。这是比较好的,也是我们要达到的目标,减少淡红色和深红色,防止过度重绘。

白色表示没有重复绘制(只绘制了1次),淡蓝色表示重绘1次(绘制了2次),淡绿色表示重绘2次(绘制了3次)淡红色表示重绘制3次(绘制了4次),深红色表示重绘4次活更多(绘制5次或者更多)。 PS:设置Activty的背景颜色为空或者透明都可以减少过度重绘!

造成重绘的原因主要有 1.嵌套的过多   2.自定View没有使用好invalidate或者requestLayout方法,主要从这2个方面进行优化!



四、Hierarchy View优化布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
>
<TextView
android:id="@+id/first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="使用Hierarchy View"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/first"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Android Logo"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:src="@mipmap/ic_launcher"/>

</LinearLayout>
</RelativeLayout>

Android 性能优化之布局优化

接下来我们来看看Hierarchy View的使用:

Android 性能优化之布局优化

 Windows窗口下表示的当前的手机运行进程,加粗的表示当前正在执行的进程。

 黄色框内的TreeView视图可以查看每个布局的详细信息。

 红色方框的LayoutView视图可以查看布局的大致情况。

 蓝色方框的TreeOverview是所有布局控件的缩略图。拖动它,然后在TreeView查看详细的信息。

TreeView中的绿色方框依次是保存成一张图片,以及保存成photoshop形式的文件,Android 性能优化之布局优化点击之后可以显示每个布局的详细信息。如下图:

Android 性能优化之布局优化

可以看出布局的measure、layout、draw的时间,下面的三个颜色圆圈点依次对应。绿色表示最快的前50%,黄色表示最慢的前50%,红色表示那一层里面最慢的view。显然,红色的部分是我们优先优化的对象。右下角的数据表示该View在同一层View中的序号,0表示至于它自己!可以看出上面的布局加载依次的总耗时:0.07+0.011+0.990=1.071ms   ,显然我们的布局里面红色的圆圈是可以优化的,RelativeLayout其实只是起到一个容器的作用,可以用LinearLayout替代。而里面的那个LinearLayoutt同样可以去除掉,这样优化之后结果如下图:

Android 性能优化之布局优化

 现在的总时间:0.051+0.009+0.956=1.016ms   这样相对于优化前快了0.055ms 。

 经过以上的优化方法,就能打造出一个比较高性能的布局了,当然性能优化的方法远不止这些,后续将会对性能优化进一步的探讨。