Android 性能优化(二)之布局优化面面观

1、初识布局优化

经过《Android性能优化(一)之启动加速35%》咱们得到了闪电般的App启动速度,那么在应用启动完毕以后,UI布局也会对App的性能产生比较大的影响,若是布局写得糟糕,显而易见App的表现不可能流畅。javascript

那么本文我一样基于实际案例,针对应用的布局进行优化进而提高App性能。java

2、60fps VS 16ms

根据Google官方出品的Android性能优化典范60帧每秒是目前最合适的图像显示速度,事实上绝大多数的Android设备也是按照每秒60帧来刷新的。为了让屏幕的刷新帧率达到60fps,咱们须要确保在时间16ms(1000/60Hz)内完成单次刷新的操做(包括measure、layout以及draw),这也是Android系统每隔16ms就会发出一次VSYNC信号触发对UI进行渲染的缘由。android

若是整个过程在16ms内顺利完成则能够展现出流畅的画面;然而因为任何缘由致使接收到VSYNC信号的时候没法完成本次刷新操做,就会产生掉帧的现象,刷新帧率天然也就跟着降低(假定刷新帧率由正常的60fps降到30fps,用户就会明显感知到卡顿)。性能优化

Drop Frame Occur

做为开发人员,咱们的目标只有一个:保证稳定的帧率来避免卡顿。

3、Avoid Overdraw

理论上一个像素每次只绘制一次是最优的,可是因为重叠的布局致使一些像素会被屡次绘制,Overdraw由此产生。微信

咱们能够经过调试工具来检测Overdraw:设置——开发者选项——调试GPU过分绘制——显示过分绘制区域。网络

overdraw

原色 – 没有过分绘制 – 这部分的像素点只在屏幕上绘制了一次。
蓝色 – 1次过分绘制– 这部分的像素点只在屏幕上绘制了两次。
绿色 – 2次过分绘制 – 这部分的像素点只在屏幕上绘制了三次。
粉色 – 3次过分绘制 – 这部分的像素点只在屏幕上绘制了四次。
红色 – 4次过分绘制 – 这部分的像素点只在屏幕上绘制了五次。工具

在实际项目中,通常认为蓝色便是能够接受的颜色。布局

咱们来看一个简单却隐藏了不少问题的界面,App的设置界面。在没有优化以前打开Overdraw调试,能够看到界面大多数是严重的红色:见下图。post

设置界面初始

贴出这个布局的代码性能

<?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:background="#F1F0F0"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/update_phone"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="修改手机号"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:id="@+id/update_phone_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />

            <ImageView
                android:id="@+id/update_phone_dot"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:layout_toLeftOf="@id/update_phone_iv"
                android:src="@drawable/message_logo_red" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_forgetPassword"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="找回密码"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/privacy_setting"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="隐私设置"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:id="@+id/privacy_setting_iv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />

            <ImageView
                android:id="@+id/privacy_setting_dot"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:layout_toLeftOf="@id/privacy_setting_iv"
                android:src="@drawable/message_logo_red" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_messageSetting"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/accountSetting_messageSetting"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <CheckBox
                android:id="@+id/setting_checkbox_c_messageSetting"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:checked="true" />
        </RelativeLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/setting_lv_feedback_m"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/accountSetting_feedback"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_score"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/accountSetting_score"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:layout_marginLeft="10dp"
            android:background="#FFDDDDDD" />

        <RelativeLayout
            android:id="@+id/setting_lv_aboutus"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="@string/about_us"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="@color/white"
        android:orientation="vertical">

        <RelativeLayout
            android:id="@+id/setting_lv_changeStatus"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/white"
            android:paddingLeft="10dp"
            android:paddingRight="10dp">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:background="@color/white"
                android:text="我要招人"
                android:textColor="#FF555555"
                android:textSize="14sp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:src="@drawable/arrow_right" />
        </RelativeLayout>
    </LinearLayout>

    <Button
        android:id="@+id/setting_btn_exitLogin"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="30dp"
        android:background="@color/white"
        android:gravity="center"
        android:text="@string/me_exitbtn"
        android:textColor="#FFFF5A5A"
        android:textSize="16sp" />

</LinearLayout>复制代码

分析布局可知:多层布局重复设置了背景色致使Overdraw。
那么咱们结合产品的需求(任何不结合具体场景优化都是耍流氓):

  • 去掉每行RelativeLayout的背景色;
  • 去掉每行TextView的背景色;

备注:一个容易忽略的点是咱们的Activity使用的Theme可能会默认的加上背景色,不须要的状况下能够去掉。

去掉背景色以后再看一下Overdraw;

设置界面优化后

对比一下优化后的布局的颜色,能够看出Overdraw降到了能够接受的程度。

备注:有些过分绘制都是不可避免的,须要结合具体的布局场景具体分析。

4、减小嵌套层次及控件个数

  • Android的布局文件的加载是LayoutInflater利用pull解析方式来解析,而后根据节点名经过反射的方式建立出View对象实例;
  • 同时嵌套子View的位置受父View的影响,类如RelativeLayout、LinearLayout等常常须要measure两次才能完成,而嵌套、相互嵌套、深层嵌套等的发生会使measure次数呈指数级增加,所费时间呈线性增加;

由此获得结论:那么随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增加,性能也就越差。

幸运的是,咱们有Hierarchy Viewer这个方即可视化的工具,能够获得:树形结构总览、布局view、每个View(包含子View)绘制所花费的时间及View总个数

备注: Hierarchy Viewer不能链接真机的问题能够经过ViewServer这个库解决;

设置界面初始状态

设置界面初始状态View个数及绘制时间

使用Hierarchy Viewer来看查看一下设置界面,能够从下图中获得设置界面的一些数据及存在的问题:

  • 嵌套共计7层(仅setContentView设置的布局),布局嵌套过深;
  • measure时间1.569ms,layout时间0.120ms,draw时间16.128ms,合计共计耗时17.871ms;
  • 共绘制85个View,5个多余定位,以及若干个无用布局。

优化方案:

  • 将以前使用RelativeLayout来作的能够替换的行换为TextView;
  • 去掉以前多余的无用布局;

如今咱们再使用Hierarchy Viewer来检测一下:

优化以后的布局层次

优化以后的View个数及绘制时间

优化后:
1. 控件数量从85个减小到26个,减小69%;
2. 绘制时间从17.8ms减小到14.756ms,下降17%;

总结:
1. 一样的UI效果可使用不一样的布局来完成,咱们须要考虑使用少的嵌套层次以及控件个数来完成,例如设置界面的普通一行,能够像以前同样使用RelativeLayout嵌套TextView以及ImageView来实现,可是明显只使用TextView来作:嵌套层次、控件个数都更少。
2. 优化过程当中使用低端手机更易发现瓶颈;

5、Profiling GPU Rendering

根据Android性能优化典范,打开设备的GPU配置渲染工具——》在屏幕上显示为条形图,能够协助咱们定位UI渲染问题。

GPU呈现模式分析

从Android M版本开始,GPU Profiling工具把渲染操做拆解成以下8个详细的步骤进行显示。

渲染八步骤

  1. Swap Buffers:表示处理任务的时间,也能够说是CPU等待GPU完成任务的时间,线条越高,表示GPU作的事情越多;
  2. Command Issue:表示执行任务的时间,这部分主要是Android进行2D渲染显示列表的时间,为了将内容绘制到屏幕上,Android须要使用Open GL ES的API接口来绘制显示列表,红色线条越高表示须要绘制的视图更多;
  3. Sync & Upload:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减小该段区域的执行时间,咱们能够减小屏幕上的图片数量或者是缩小图片的大小;
  4. Draw:表示测量和绘制视图列表所须要的时间,蓝色线条越高表示每一帧须要更新不少视图,或者View的onDraw方法中作了耗时操做;
  5. Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就须要仔细检查本身的布局是否是存在严重的性能问题;
  6. Animation:表示计算执行动画所须要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就须要检查是否是使用了非官方的动画工具或者是检查动画执行的过程当中是否是触发了读写操做等等;
  7. Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操做;
  8. Misc Time/Vsync Delay:表示在主线程执行了太多的任务,致使UI渲染跟不上vSync的信号而出现掉帧的状况;出现该线条的时候,能够在Log中看到这样的日志:

备注:GPU配置渲染工具虽然能够定位出问题发生在某个步骤,可是并不能定位到具体的某一行;当咱们定位到某个步骤以后可使用工具TraceView进行更加详细的定位。TraceView的使用能够参照《Android性能优化(一)之启动加速35%》

6、Use Tags

merge标签

merge能够用来合并布局,减小布局的层级。merge多用于替换顶层FrameLayout或者include布局时,用于消除由于引用布局致使的多余嵌套。
例如:须要显示一个Button,布局以下;

<?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="vertical"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Merge标签演示" /> </LinearLayout>复制代码

咱们经过UiAutoMatorViewer(无需root,相比Hierarchy Viewer只能查看布局层次,不能获得绘制时间)看一下布局的层次

顶级视图下多了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"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Merge标签演示" /> </merge>复制代码

再看下布局的层次:

使用Merge以后少了LinearLayout嵌套

能够看到使用Merge标签进行优化以后布局嵌套就少了一层,Button做为父视图第三层FrameLayout的直接子视图。

注意:merge标签经常使用于减小布局嵌套层次,可是只能用于根布局。

ViewStub标签

推迟建立对象、延迟初始化,不只能够提升性能,也能够节省内存(初始化对象不被建立)。Android定义了ViewStub类,ViewStub是轻量级且不可见的视图,它没有大小,没有绘制功能,也不参与measure和layout,资源消耗很是低。
一、

<ViewStub
        android:id="@+id/mask"
        android:layout="@layout/b_me_mask"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />复制代码
ViewStub viewStub = (ViewStub)view.findViewById(R.id.mask);
viewStub.inflate();复制代码

App里常见的视图如蒙层、小红点,以及网络错误、没有数据等公共视图,使用频率并不高,若是每一次都参与绘制实际上是浪费资源的,均可以借助ViewStub标签进行延迟初始化,仅当使用时才去初始化。

include标签

include标签和布局性能关系不大,主要用于布局重用,通常和merge标签配合使用,因和本文主题关联不大,此处不展开讨论。

7、其它

  1. 自定义控件时,注意在onDraw不能进行复杂运算;以及对待三方UI库选择高性能;
  2. 内存对布局的影响:如同Misc Time/Vsync Delay步骤产生的影响,在以后内存优化的篇章详细讲。

8、总结

布局优化的通用套路

  1. 调试GPU过分绘制,将Overdraw下降到合理范围内;
  2. 减小嵌套层次及控件个数,保持view的树形结构尽可能扁平(使用Hierarchy Viewer能够方便的查看),同时移除全部不须要渲染的view;
  3. 使用GPU配置渲染工具,定位出问题发生在具体哪一个步骤,使用TraceView精准定位代码;
  4. 使用标签,Merge减小嵌套层次、ViewStub延迟初始化。

通过这几步的优化以后,通常就不会再有布局的性能问题,同时仍是要强调:优化是一个长期的工做,同时也必须结合具体场景:有取有舍!

参考:Android性能优化典范

欢迎关注微信公众号:按期分享Java、Android干货!

欢迎关注
相关文章
相关标签/搜索