Android进阶——性能优化之布局优化实战经验小结(五)

引言

前面一篇Android进阶——性能优化之布局渲染原理和底层机制详解(四)简单介绍了下Android的UI渲染机制,包括系统层和应用层的渲染UI的机制,为将要采取的一些系列措施提供理论依据,这篇主要针对实战中的布局优化常用的措施进行总结,性能优化系列文章链接:

一、布局优化概述

通过前面一篇Android进阶——性能优化之布局渲染原理和底层机制详解(四)我们大致地明确了Android UI的渲染过程,可以主要分为系统层的渲染工作和应用层的渲染任务,作为普通APP开者来说对于其渲染机制我们无法进行优化,那些是ROOM开发或者系统开发者层面做的事,比如说Android GPU 设计的缓冲机制,Button等其他View元素转换成特定的矢量图形是一个时间消耗过程,再把矢量图形传递给GPU又是一个时间消耗过程,而由CPU传递给GPU同样是一个非常耗时的过程。这样就意味着,GPU中进行栅格化所节省下来的时间,可能在这里被消耗大半。于是Open GL它提供了一个类似缓存到机制:CPU上传到GPU中的资源,可以作为缓冲保存在GPU当中,在下次再次利用的过程中,就省去了CPU的格式转换和CPU上传到GPU的过程消耗。Android系统就灵活利用到了这一点,它在系统启动过程中,就将主题中的系统资源以一个单一向量图形的形式上传至GPU,以后在调用系统资源时,就可以直接在GPU中取到相应资源,而不需要转换和传递。这就是加载Android系统图片非常快速的原因。然而,随着业务的复杂性各种动画、UI,GPU的缓冲机制变得几乎形同虚设,因为每一个图片都是不同的,都无法复用,因此GPU中的缓存资源只能通过不断被覆盖来达到相应效果,于是Android系统提供了差异化绘制机制,简单来说就是缓存的旧资源与即将写入的新资源进行对比,只对发生了改变的部分进行重新处理。以此缓解GPU的压力。在上面,我们有提到,有DisplayList对CPU处理好的格式资源以及需要进行的相应的绘制指令,进行接收。这里的DispalyList在特殊情况下,可以对其接收的信息进行复用,举例来说:如果一个button改变了其位置:GPU可以将DisplayList中的信息可以进行复用。如果一个button改变了其大小或者其形状,表面颜色发生改变(视觉上的形体,色彩改变 ):GPU就无法使用之前CPU传递来的DisplayList,需要通过CPU进行重新格式转换,然后将命令和转换好的资源存入一个新的DisplayList当中。
为了不给APP用户造成感官上的卡顿,就必须要确保在每16ms内完成两件事:将UI 对象转换为一系列多边形和纹理CPU传递处理数据到GPU。所以显然我们要尽可能地缩短这两部分的时间,即说需要尽量减少CPU 执行xml转换成对象的时间GPU 减少重复绘制的时间
这里写图片描述

二、Overdraw过度绘制

1、Overdraw概述

理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,换句话说在多层次的UI结构里面,如果不可见的UI也在做绘制的操作或者在自定义View的onDraw方法做了过多的重绘,就会导致屏幕上的某个像素在同一帧的时间内被绘制了多次,浪费了不必要的CPU以及GPU资源,这种现象就是叫过度绘制Overdraw,其实GPU 的绘制过程有点和刷墙类似,逐层进行且每隔16ms刷一次,所以有可能因为图层覆盖的关系导致无用的图层也进行绘制。但是在Android系统中任何时候当View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。而此流程的表现性能取决于View的复杂程度,View的状态变化以及渲染管道的执行性能, 所以我们需要尽量减少Overdraw来节省CPU和GPU资源
这里写图片描述

2、Overdraw的检测

这里写图片描述
Android手机本身就自带Overdraw的检测工具,只需要打开设置——>开发者选项——>调试GPU过度绘制——>显示过度绘制区域,分别使用了四种颜色层次来代表过度绘制的程度,如下图所示
这里写图片描述

  • 原色——没有过度绘制
  • 蓝色——过度绘制一次
  • 绿色——过度绘制两次
  • 粉色——过度绘制三次
  • 红色——过度绘制四次及以上
    所以开发中的目标是尽量减少粉色和红色的区域,尽可能地实现更多的蓝绿色或原色区域,单页面的3X(粉红色区域) Overdraw小于25%,Activity界面启动时间一般低于300ms,对了也可以通过adb命令开启和关闭Overdraw检测(每次开启之后你得重新进入检测的APP)
//开启Overdraw检测
adb shell setprop debug.hwui.overdraw show
//关闭Overdraw检测
adb shell setprop debug.hwui.overdraw false

三、布局优化工具的使用

1、GPU渲染性能的工具

和Overdraw检测工具一样,Android手机本身也自带了检测GPU渲染的工具,只需要打开设置——>开发者选项——>GPU呈现模式分析——>在屏幕上显示为条形图,通过打开这个工具,然后再使用我们的APP,会根据GPU具体的绘制过程而呈现出不同颜色的条形图,从而大致定位问题所在。
这里写图片描述
而在Android M之后,GPU Profiling工具把渲染操作拆解成如下8个详细的步骤进行显示。
这里写图片描述

2、Hierarchy Viewer

虽然在最新版本中已经被Goolge 置为不推荐使用了,但是如果想要使用也可以前往下载旧版本的tools下载完后解压,双击打开Hierarchy Viewer使用,关于Hierarchy Viewer使用教程有很多这里简单介绍下步骤( 若Hierarchy Viewer不能连接真机的问题可以通过ViewServer这个库解决):

  • 打开Hierarchy Viewer找到并双击Attach要调试的app的进程,自动加载布局
    这里写图片描述
  • 找到对应的节点,双击后放大会看到这样的View的基本信息和渲染花费的时间
    这里写图片描述
  • 同时还可以得知系统对于APP渲染速度的评价分别用红黄蓝三种颜色小球表示
    这里写图片描述

3、Layout Inspactor

Layout Inspactor是最新版本中Google新出的层级检测工具,他可以帮助我们在程序运行的时候分析布局文件(ui绘制时间与嵌套层级),从而找到性能瓶颈(需要在API >= 16 的机子运行),Layout Inspector 只能分析出Android Studio当前“正在运行的APP”的视图布局结构,其他应用的视图布局结构是无法显示的,另外想要分析一个第三方应用的视图结构可以使用Android Device Monitor(安卓设备监视器),我在使用的过程中遇到一些莫名其妙的bug,感觉还不够稳定,至于使用也很方便简单介绍下,首先运行我们的APP 然后在Tools——>Android ——>Layout Inspactor 就可以打开了,成功打开后看到如下的界面
这里写图片描述
其中右侧是我们选中view的属性,左侧则是布局层级显示

四、布局优化的一般原则

总所周知我们的Android UI 本质上就是一棵ViewTree结构,如果ViewGroup里面有很深的子控件层级或者有不必要的子控件,在实现业务的前提下布局结构肯定越简单越好,因为需要测量与绘制的控件越少,耗时越低,性能越好。而层级越少意味着在绘制控件的时候需要参考的父/子控件越少,而APP在开发中遇到渲染问题无非可以分为两大类:过度绘制布局冗杂,所以布局优化普遍遵循的原则:

  • 布局嵌套层次越少越好
  • 布局中控件数越少越好
  • Overdraw情况越低越好

五、布局优化的措施

1、降低Overdraw,减少不必要的背景绘制

包括默认主题提供的背景颜色、Activity的默认背景、View和ViewGroup的背景,如果没有必要就不要多余去设置,其中尤其需要注意Activity使用的Theme可能会默认的加上背景色,不需要的情况下可以去掉。

//去掉Activity使用的Theme可能会默认的加上背景色
<style name="AppTheme" parent="android:Theme.Light.NoTitleBar">
    <item name="android:windowBackground">@null</item>
</style>

2、减少嵌套层次及控件个数

Android的布局文件的加载是LayoutInflater利用pull解析方式来解析,然后根据节点名通过反射的方式创建出View对象实例,同时嵌套子View的位置受父View的影响,类如RelativeLayout、LinearLayout等经常需要measure两次才能完成,而嵌套、相互嵌套、深层嵌套等的发生会使measure次数呈指数级增长,所费时间呈线性增长,所以随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增长,性能也就越差。

2.1、使用merge标签减少布局嵌套层次

merge多用于替换顶层FrameLayout或者include布局时(并非是意味着在我们缩写的XML文件中根节点是FrameLayout的才可以替换),用于消除因为引用布局导致的多余嵌套。但是只能用于根布局(注意include标签实质上对于布局优化层面来说无重大作用,他的核心思想是重用)
这里写图片描述

扫描二维码关注公众号,回复: 2396660 查看本文章
  • 使用ConstraintLayout替代常见嵌套布局,减少布局层次
  • -

3、使用Canvas的clipRect和clipPath方法限制View的绘制区域

一个Activity对应有一个画布Canvas,这个画布提供了很多的API,我们可以通过调用画布的API来绘图以及对画布做一些操作,clipRect方法用来裁切画布上的一个矩形区域,该矩形区域用Rect对象来描述。调用了clipRect之后,画布的可绘制区域减小到和Rect指定的矩形区域一样大小。所有的绘制将限制在该矩形范围之内。

4、通过imageDrawable方法进行设置避免ImageView的background和imageDrawable重叠

Android中,所有的view均可以设置background。ImageView除了能够设置background之外,还能设置ImageDrawable。若使用ImageView的background属性来设置默认背景图,imageDrawable来设置需要加载的图片。就会导致当图片加载到页面后,默认背景图被挡住了,但是却仍然被绘制,导致过度绘制情况的发生。

5、借助ViewStub按需延迟加载

推迟创建对象、延迟初始化,不仅可以提高性能,也可以节省内存(初始化对象不被创建)。Android定义了ViewStub类,ViewStub是轻量级且不可见的视图,它没有大小,没有绘制功能,也不参与measure和layout,资源消耗非常低,具体参见Android进阶——布局优化之灵活借助ViewStub实现懒加载

6、选择合适的布局类型

LinearLayout下没有设置weight属性下,仅仅会进行一次测量,相对于相对布局会高效些,而设置了weight属性的时候相对布局层次更少些,在不影响层级深度的情况下,使用LinearLayout而不是RelativeLayout,可以使用相对布局减少层级的就使用相对布局,否则使用线性布局。因为RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,才会让子View调用2次onMeasure。如果非要是嵌套,那么尽量避免RelativeLayout嵌套RelativeLayout,所以得结合自己业务来取舍。

7、熟悉API尽量借助系统现有的属性来实现一些UI效果

  • 借助TextView的CompoundDrawable来实现类似ImageView+TextView的效果
  • 灵活使用TextView的行间距lineSpacingExtra属性,默认是0,是一个绝对高度值,可用来实现类似分段的效果
  • 用LinearLayout自带的分割线
  • 使用Space控件定义Item之间间距

以上仅仅是庞大的api中的一部分,还有很多没有举出,总之你得非常熟悉api。

<?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"
    android:divider="@drawable/divider"
    android:showDividers="middle|beginning|end">
    <TextView
        android:drawableLeft="@drawable/icon_2"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="我的设备"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <Space
        android:layout_width="match_parent"
        android:layout_height="15dp"/>

    <TextView
        android:drawableLeft="@drawable/icon_3"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="我的订单"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

小结

布局优化的一切出发点都是为了保证所有渲染操作都能在16ms内完成,布局优化也是建立在具体的业务中,这篇文章仅仅是提供了一个思想,不要盲目地生搬硬套,结合具体的业务代码和场景才是真正可取的,否则优化无从谈起。

猜你喜欢

转载自blog.csdn.net/crazymo_/article/details/80149093