欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

overdraw优化

程序员文章站 2024-03-17 17:45:16
...

预备知识

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

  • 什么是overdraw?

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

  • 如何识别overdraw?

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

overdraw优化

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

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

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

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

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

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

下图展示了优化前的overdraw:

overdraw优化

从上图中可以看到,底下的标签选择区域基本呈现暗红,上面的标签展示区域的文本也是浅红色。甚至是在绘制内容之前,就有一个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是如何绘制每一帧的,在调试绘制性能问题时无比强大。如下图:

overdraw优化

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

优化过程

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

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

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

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

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

overdraw优化

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

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

overdraw优化

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

去掉window的默认背景

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

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

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

getWindow().setBackgroundDrawable(null);

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

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

overdraw优化

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

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

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

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

overdraw优化

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

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

overdraw优化

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

善用9patch来做背景

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

overdraw优化

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

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

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

优化成果

overdraw优化

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

结束语

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

强烈推荐博文: Android Performance Case Study