Android开发 之 过度绘制优化

        Android系统自2007年最开始的阿童木(AndroidBeta)、发条机器人(Android 1.0)诞生以来,至2019年Android 10,发展可谓十分迅速,Android 11 又将在2020年第三季度发布,我们拭目以待。

        而Android手机app使用起来卡顿反应慢的问题也一直被用户所诟病,因此,作为开发者,我们不仅仅只是完成业务功能的开发,更多的需要关注性能优化,提高app运行流畅度,改善用户体验。接下来将介绍性能优化中过度绘制的优化方法及其实践。

一、过度绘制的概念

        过度绘制是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素。例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容。

        但是,系统仍然需要绘制堆叠中的卡片被遮盖的部分。这是因为堆叠的卡片是根据画家算法(也就是按从后到前的顺序)来渲染的。 按照这种渲染顺序,系统可以将适当的透明度混合应用于阴影之类的半透明对象。

       过度绘制通常是不必要的,最好避免。它会浪费 GPU 时间来渲染与用户在屏幕上所见内容无关的像素,进而导致性能问题。尽管现今高版本系统已经做了大量的优化,和在高端配置手机上影响并不是很大,但在我们中国的市场上,仍然有大量的用户使用的是低版本系统和低端配置手机,这部分用户的用户体验仍不可忽视。同时作为开发者的我们,也应自我要求严谨规范,致力于开发出优质的应用。

二、过度绘制的分析

      Android开发的方便在于Android系统有强大的开发者选项-调试模式和开发工具(Android studio)内置强大高效的分析工具。

1、调试GPU过度绘制

device-2020-05-20-145503.png

打开“调试GPU过度绘制”选项,是酱紫~

device-2020-05-20-145533.png

可以看到,手机界面显示了各种颜色,我们可以看看几个知名的App变成什么样子

百度地图

device-2020-05-20-145607.png

得物App(得到运动x潮流x好物)

device-2020-05-20-145745.png

有原色、蓝色、绿色、粉色、红色五种颜色,分别代表不同程度的过度绘制,👉看developer官网说明

image.png

每个颜色的说明如下:

  • 原色:没有过度绘制
  • 蓝色:1 次过度绘制
  • 绿色:2 次过度绘制
  • 粉色:3 次过度绘制
  • 红色:4 次及以上过度绘制

       过度绘制的存在会导致界面显示时浪费不必要的资源去渲染看不见的背景,或者对某些像素区域多次绘制。一些过度绘制是无法避免的,比如某些按钮的文字及其背景,又比如某些页面或区域的背景色和通用主题背景色不同;通常的优化原则是保持页面为绿色及以下,原色和蓝色是最理想的;应避免粉色和红色这两种严重的过度绘制区域,在我们实际开发中,避免不了也应尽量减少粉色和红色的过度绘制区域。

2、布局检查

6571589959128_.pic_hd_副本.png

这里以Android studio为例,选择的Tools选项下的Layout Inspector,可打开内置的布局检查器,界面如下:

image.png

       可以看到界面布局控件的树形关系,在这个检查器中,不仅可以看到app显示的内容元素所对应的布局控件,还可以看到布局控件之间的并列或嵌套关系及嵌套层级。可以用来分析哪些是不必要的控件,哪些是可以合并的布局,哪些不必要的嵌套层级可以减少等等。

三、过度绘制的优化

1、移除不需要的background

       默认情况下,布局是没有背景的,而视觉设计每个页面都是有背景色的。每个activity注册在Androidmainfest.xml中时,一般默认主题都会有一个默认的windowBackground ,如果在activity根布局中根据视觉设计已添加backgroud背景色,那么就可以在Androidmainfest.xml的配置中移除默认的windowBackground,比如image.png;也可以配置一个通用背景色的theme,如图image.png,移除所有activity根布局中的backgroud。

      简而言之,就是不要出现重叠的背景设置。移除前后的对比如下:

device-2020-05-20-180315.png    VS   device-2020-05-20-180637.png 

      可以看到移除backgroud后,原来的粉色区域消失,绿色区域大量减少,主要以绿色为主变为主要以蓝色为主,优化效果很明显。

2、移除不必要的android:background=""

      这个主要是针对布局xml文件,子布局background和其父布局background背景色相同的情况,可以移除子布局的android:background=""设置,以避免不必要的背景色绘制。

3、减少布局的嵌套层级

     通常有以下几种方法:

     1) 尽量使用RelativeLayout或FrameLayout,避免使用LinearLayout增加布局的嵌套层级;

     2) 在相同的嵌套层级时,尽量使用LinearLayout,避免使用RelativeLayout增加布局的测量绘制次数;

     3) 可以采用ConstraintLayout约束布局,其子控件使用约束关系,从而使大量控件减少了其被嵌套的层级。

4、使用merge标签

     使用merge标签的本质还是减少布局的嵌套层级,这里之所以单独列出来是因为和其他方式有所不同,同时也有些需要注意的事项。

    1) merge标签一般和include标签配合使用,既减少了布局的嵌套层级,同时include布局重用减少了布局的代码量。merge + include通常用于FramLayout和LinearLayout,实际上RelativeLayout也是可以使用merge,但一般都不这么用,因为RelativeLayout本身位置关系复杂一点,处理不当很容易适得其反。

   2) 自定义控件使用merge标签

     很多人自定义布局习惯继承FrameLayout,inflate加载res目录下的xml文件,xml文件中根布局可能是任意类型的布局,无形中增加了布局的嵌套层级。在这里,可以根据实际情况的需要,自定义布局继承xml文件中根布局的类,而xml文件中根元素使用merge标签,减少布局的嵌套层级。

image.png 

image.png      

    对于自定义布局,采用这个方法需注意的是:

    a) 当 Inflate 以该标签开头的布局文件时, 必须指定一个父 ViewGroup, 并且必须设定 attachToRoot 为 true.

    b) <merge />的属性设置不会生效,需要在自定义布局的代码中设置布局属性.

5、toolbar的优化

     这里列出toolbar是因为toolbar比较典型,适用于通常情况。大多数app的视觉设计的布局中都有toolbar这个元素,activity的界面则呈线性布局,顶端是toolbar,下面是页面内容,则toobar可以抽离出一个单独xml布局文件。而系统原生的androidx.appcompat.widget.Toolbar不能作为xml布局文件的根标签,需要对Toolbar包裹一个布局。因此可以在activity的xml布局文件中,根布局采用LinearLayout,toolbar的xml布局根标签采用<merge />,在activity的xml布局文件中进行include引用toolbar的xml布局文件toobar.xml。如下所示

image.png    image.png

6、使用 clipRect() 和 quickReject() 优化

       当某些控件不可见时,如果还继续绘制更新该控件,就会导致过度绘制。在自定义View的OnDraw方法中,用canvas.clipRect来指定绘制的区域,防止重叠的组件发生过度绘制。比如DrawerLayout就是非常不错的例子,看源码:

    image.png

可以看到,DrawerLayout调用了canvas.clipRect方法

   image.png

Canvas的clipRect方法说明:

Intersect the current clip with the specified rectangle, which is expressed in local coordinates.

简单说就是canvas只绘制指定的区域,此区域以外的区域则不绘制。

四、实践总结

       过度绘制优化的内容并不多,因此学习和理解起来比较容易;尽管如此,实践也是非常重要的,因为在实践中会遇到通常的场景、特定的场景和一些意想不到的问题。在实践中思考,在思考后实践,积累丰富的经验,提升相应的技能。

      一个非常重要的心得就是,在平时的开发中,如遇到难解决的问题、或很大的收获、抑或是特别的想法,需记录分析、还是与朋友同事分享、又或是实践尝试,都应及时去做,趁热打铁。

     我将在未来的工作中继续保持边实践边思考。哦了~