overdraw优化小结

原文出处:http://blog.csdn.net/huli870715/article/details/39646575 

★ 预备知识

在Android的开发过程中,drawing performance往往是我们最关注也是努力去优化的一个点。而造成drawing perfomance的元凶之一就是overdraw。那么

1. 什么是overdraw?

overdraw发生在应用每次请求在其它物体上绘制内容的时候。例如:一个白色背景的窗口,在它上面有一个按钮。当系统绘制按钮时,要绘制在已存在的白色背景上,这就是overdraw。

2. 如何识别overdraw?

在Anroid的开发者工具中勾选上Show GPU overdraw。

blob.png

该工具会使用三种不同的颜色绘制屏幕,来指示overdraw发生在哪里以及程度如何,其中:

  • 没有颜色: 意味着没有overdraw。像素只画了一次。

  • 蓝色: 意味着overdraw 1倍。像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。

  • 绿色: 意味着overdraw 2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。

  • 浅红: 意味着overdraw 3倍。像素绘制了四次,小范围可以接受。

  • 暗红: 意味着overdraw 4倍。像素绘制了五次或者更多。这是错误的,要修复它们。

下图展示了优化前的overdraw:

blob.png

从上图中可以看到,底下的标签选择区域基本呈现暗红,上面的标签展示区域的文本也是浅红色。甚至是在绘制内容之前,就有一个1倍overdraw的蓝色背景。看来,该界面有大量优化空间。

★ 工具准备

  • android自带工具Hierarchy Viewer。可以查看整个窗口view树的层级接口,以及各个节点的绘制时间。

  • 上述的开发者选项中的Show GPU Overdraw只有在4.2以上的Android机器上才有,对于那些没有4.2以上机器的同学(我的小米2s默默流泪),只能使用模拟器。由于Android自带的模拟器渣成翔且不支持硬件加速,这里强烈推荐酷炫屌炸天的第三方模拟器: genymotion,谁用谁知道。tip:在linux上跑该虚拟机尤其流畅,甚至赛过真机

  • Trace for OpenGL ES。该工具也是Android自带的调试工具,已在ADT中集成。它能够以可视化的方式看到底层的openGl ES是如何绘制每一帧的,在调试绘制性能问题时无比强大。如下图:

blob.png

左侧的列表显示了底层openGL ES的每个命令调用,这里我使用Filter筛选出了所有的绘制命令。右侧的底部的FrameSummary显示了该帧绘制出的界面,而右侧顶部的Details区域非常重要,它以可视化的方式显示了在一帧的绘制过程中,当前的状态,可以配合左侧的glDraw命令看到当前界面是怎样一步步绘制出来的,这样,很容易就能看出哪些像素被重画了。

★ 优化过程

1. 善用merge标签,合并冗余节点

merge标签是用来减少视图层级结构的一种优化手段。就我目前的总结,较容易出现并且需要使用标签来优化的是以下两种情况:

  • 我们activity的contentView是一个FrameLayout节点。由于contentView的父节点往往是一个ID为android.R.id.content的FrameLayout节点,因此,我们自定义的contentView的FrameLayout节点就产生了冗余,可以合并到父节点中。

  • 另一种情况是我们自定义一个View,假设是MyRelativeLayout extends Relativelayout。在MyRelativeLayout中我们又去inflate一个根节点为RelativeLayout的布局文件,此时就产生了冗余,也可以用去优化它。

下面看看我们这个界面的布局层级(只截取了部分我认为有问题的地方):

blob.png

可以看到红框内的两个节点。 其中ProfileLabelPanel一个继承自RelativeLayout的自定义View,在它里面又inflate了一个根节点为RelativeLayout的layout文件。看来,我们碰到了上述的第二种情况,优化方案:

将layout文件中的根节点标签,即RelativeLayout修改成merge,修改后的层级如下

blob.png

第二个RelativeLayout已成功合并到父节点中。

2. 去掉window的默认背景

当我们使用了Android自带的一些主题时,我们的activity往往会被设置一个默认的背景,这个背景是被DecorView持有的。当我们的自定义布局有一个全屏的背景时,比如我们这个界面的全屏白色背景,DecorView的background此时对我们来说是无用的,但是它会产生一次Overdraw,带来绘制性能损耗。

这种绘制可以从上面提到的Trace for OpenGL ES工具看到,首先它绘制了一个黑色背景(手Q在application层级应用了黑色背景主题),接着绘制了一个白色背景覆盖在它上面。

去掉window的背景可以在onCreate中调用

getWindow().setBackgroundDrawable(null);

但是有一点一定要注意: 上述语句必须在setContentView之后执行,否则是无效的。原因从源码中就很容易看出来。 这两条语句最终都会调用到PhoneWindow类中的同名方法。

blob.png

setBackgroundDrawable其实设置的就是DecorView的background。

blob.png

Activity刚启动的时候,DecorView是为null的,此时setContentView方法会先去installDecor,然后才会去加载我们自己的layout。

这也就是为什么必须先调用setContentView的原因。
去掉window背景前后的具体性能测试请参考: Speed up your Android UI

3. 在自定义布局中去掉没必要的背景重叠(往往发生在根节点设置一个背景的时候)

在我们的编辑标签界面中,有个全屏的白色背景(在根节点中设置的),其中,上半部分的标签展示区域使用了这个默认的白色背景,而底部的标签编辑区域(就是上文提到的ProfileLabelPanel这个自定义View,一片红色那里)则使用了自己的背景,覆盖了底层的白色背景,从而产生了一次overdraw。

blob.png

优化方案:去掉根节点上的backgruond,将其加到需要的子节点中(即界面的上半部分节点中)。此时,红色区域的背景就不会绘制在原来的白色背景之上,而是属于第一层绘制的背景。

注意: 由于将根节点上的background移到了需要的子节点中,因此,若子节点和根节点之间有margin, 就会产生问题(没有bakground去覆盖)。因此,子节点的布局尽量使用match_parent, 原来的margin改成padding。比如,我这个例子中:

blob.png

将根节点上的background移到GridView这个子节点上之后,必须将之前的margin给成padding,否则就会和根节点产生空隙。

4. 善用9patch来做背景

这种情况经常发生在View需要两层背景,比如ImageView需要设置一个前景和一个背景(其中背景用来做边框)。如下图:

这是一个ImageView,设置了两层drawable,底下一层仅仅是为了作为图片的边框而已。但是两层drawable的重叠区域去绘制了两次,导致overdraw。

优化方案: 将背景drawable制作成9patch,并且将和前景重叠的部分设置为透明。由于Android的2D渲染器会优化9patch中的透明区域,从而优化了这次overDraw。

注意: 必须将图片制作成9patch才行,因为Android 2D渲染器只对9patch有这个优化,否则,一张普通的Png,就算你把中间的部分设置成透明,也不会减少这次overDraw,大家可以做个demo尝试一下。

★ 优化成果

blob.png

可以看到,背景的一层overdraw已经去掉,底部区域的大片红色已经优化成了蓝色,只有少许的绿色。

★ 结束语

在Android的开发过程中,overdraw是不可避免的,但是过多的Overdraw就会带来巨大的性能问题。这个时候,就需要我们使用一些方法和工具来优化,此文仅做抛砖引玉之用,欢迎拍砖探讨。

强烈推荐博文: Android Performance Case Study