前言

本文是一篇译文,原文Android Performance Case Study Follow-up的作者是大名鼎鼎的Romain Guy。本文讲述了Android性能优化的一些技巧、方法和工具。

译文正文

两年前,我发表了一篇名为Android Performance Case Study 的文章,来帮助Android开发者了解需要使用什么工具和技术手段来确定、追踪和优化性能问题。

那篇文章以一个Twitter客户端 Falcon Pro为典范,其开发人员为 Joaquim Vergès. Joaquim人不错,他允许我在我的文章中使用它的程序作为例子,并且快速处理了我发现的所有问题。一切都OK,直到Joaquim 从头开始开发Falcon Pro 3,前不久在他准备发布它的新应用的时候,他联系了我,因为他有一个和滚动相关的性能问题需要我来帮助他,这一次我依然没有源代码可以参考。

Joaquim使用了所有的工具来找出问题所在,他发现Overdraw不是问题的原因,他觉得是 ViewPager 的用法导致了这个问题。他给我发来了下面的截图:

Falcon Pro

Joaquim使用了系统内置的GPU profiling工具来发现掉帧现象, 左边的截图是在没有ViewPager 的情况下滑动时间线,右边的截图是有ViewPager的情况下滑动(他使用的是2014年的Moto x来截的图),问题看起来很明显。

我最先想到的是查看ViewPager是不是由于滥用硬件加速导致,这个性能问题看起来像是在滑动的过程中每一帧都使用了硬件加速。系统的 hardware layers updates debugging tool没有显示什么有用的信息。我反复使用HierarchyViewer 查看布局情况,令我满意的是ViewPager的表现很正确(相反,不太可能会出问题)

之后我打开了另一个强大的工具却很少用到的工具:Tracer for OpenGL 。我之前的那篇文章解释了如何使用工具获得更多细节。你首先需要知道的是这个工具收集了所有UI界面发给GPU的绘制命令。

Android 4.3 and up: Tracer has unfortunately become a little more difficult to use since Android 4.3 when we introducedreordering and merging of drawing commands. It’s an amazingly useful optimization but it prevents Tracer from grouping drawing commands by view. You can restore the old behavior by disabling display lists optimization using the following command (before you start your application)(意思是说Android4.3之后,这个工具不太好用了,因为有reordering and merging 机制的引进)

Reading OpenGL traces: Commands shown in blue are GL operations that draw pixels on screen. All other commands are used to transfer data or set state and can easily be ignored. Every time you click on one of the blue commands, Tracer will update the Details tab and show you the content of the current render target right after the command you clicked is executed. You can thus reconstruct a frame by clicking on each blue command one after another. It’s pretty much how I analyze performance issues with Tracer. Seeing how a frame is rendered gives a lot of insight on what the application is doing.(意思是说只蓝色的行是真正进行绘制的命令,点击可以看到绘制的这一帧的图像,其他的命令都是一些数据的转换)

滑动一段时间Falcon Pro应用后,我仔细查看Gl Trace收集到的数据,我很惊奇地发现很多SaveLayer/ComposeLayer阻塞命令。

Paste_Image.png

这些命令表明应用在生成一个临时的Hardware Layer。这些临时的Layer被不同的 Canvas.saveLayer())所创建,这些UI控件在下面的情况下使用Canvas.saveLayer()方法去绘制 alpha < 1 (seeView.setAlpha())的View(即半透明View):

我和Chet 在很多演示中解释过为什么你应该 use alpha with care,每次UI控件使用一个临时的Layer,绘制命令会发送不同的渲染目标,对GPU来说,切换渲染目标是很昂贵的操作,这对于使用tiling/deferred架构的GPU(ImaginationTech’s SGX, Qualcomm’s Adreno, etc)等是硬伤,直接渲染架构的GPU,比如 Nvidia,则会好一点。因为我和Joaquim 使用的是搭载高通处理器的Moto X 2014版本,所以使用多个临时硬件层是最有可能的性能问题的根源。

那么问题来了,是什么创建了这些临时的Layer呢?Tracer告诉我们了答案,如果你看了刚刚上面那张,你可以看到只有SaveLayer这个组中OpenGl命令绘制了一个小圆圈(图被工具放大了),我们来看一下应用截图:

Falcon Pro 3

你看到最上面的小圆圈了么?那是ViewPager的指示器,来显示当前的位置。Joaquim 使用了一个第三方库来绘制这些指示器,有趣的是这些库如何绘制指示器的:当前的Page用一个白色的圈指示,其他的页用类似灰色的圆圈来指示。我说类似灰色因为这个圆圈其实是半透明的白色圆圈。这个库使用 setAlpha()方法来给每个圆圈设置颜色。

有下面几种方法来解决这个问题:

  • Use a customizable “inactive” color instead of setting an opacity on the View( 使用动态的“inactive”颜色(即根据状态来设置View的颜色)而不是设置透明度。)
  • Return false from hasOverlappingRendering() and the framework will set the proper alpha on the Paint
    for you(使hasOverlappingRendering()返回false,这样系统会设置适当的alpha,关于这个的用法,这篇文章中有提到:同时Android提供了hasOverlappingRendering()接口,通过重写该接口可以告知系统当前View是否存在内容重叠的情况,帮助系统优化绘制流程,原理是这样的:对于有重叠内容的View,系统简单粗暴的使用 offscreen buffer来协助处理。当告知系统该View无重叠内容时,系统会分别使用合适的alpha值绘制每一层。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * Returns whether this View has content which overlaps. This function, intended to be
    * overridden by specific View types, is an optimization when alpha is set on a view. If
    * rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer
    * and then composited it into place, which can be expensive. If the view has no overlapping
    * rendering, the view can draw each primitive with the appropriate alpha value directly.
    * An example of overlapping rendering is a TextView with a background image, such as a
    * Button. An example of non-overlapping rendering is a TextView with no background, or
    * an ImageView with only the foreground image. The default implementation returns true;
    * subclasses should override if they have cases which can be optimized.
    *
    * @return true if the content in this view might overlap, false otherwise.
    */
    public boolean hasOverlappingRendering() {
    return true;
    }
  • Return true from onSetAlpha() and set an alpha on the Paint used to draw the “gray” circles(使onSetAlpha() 返回True并对Paint设置alpha来绘制“gray”圆圈)

    1
    2
    paint.setAlpha((int) alpha * 255);
    canvas.draw*(..., paint);

最简单的方法是使用第二种,但是他只能在API16以上使用,如果你要支持旧版本的Android,使用其他两个方法,我相信Joaquim 已经丢弃那个第三方库并使用自己的指示器了。

我希望这篇文章能让大家清楚如何从看似无辜的和无害的操作中寻找可能会出现性能问题。所以请记住:不要仅仅做出假设,要实际去验证、测量。

附录

更多关于Alpha的使用,可以参考这篇文章:
Android Tips: Best Practices for Using Alpha