Android 性能个案研究

移动开发 Android
对于Android的开发,相信很多开发者在Android性能优化上花费了很大功夫,的确,这也是项目开发的一个关键所在,下面,就将所有的Android性能优化个案奉献给大家。

Falcon Pro

我最近在我的Nexus 4上安装了Falcon Pro,一个新的Twitter客户端。我真的很喜欢使用这个应用程序,但我在使用它时注意到了某些地方存在一些问题,看起来滚动主时间轴并没有得到非常稳定的帧率。我深钻了一下我每天工作所用工具和技术中的一些,我能很快地找到一些Falcon Pro 并不如它本可以表现得那么好的原因。

Falcon Pro

我在这篇文章里的目的是告诉你如何追踪和修复应用程序中的性能问题,即使你没有它的源代码。所有你需要的只是一份最新的Android 4.2 SDK –新的ADT bundle使设置变得简易。我极力推荐你下载这个应用程序来亲自应用这里描述的技术。对你来说不幸的是Falcon Pro是款付费应用,因此我将给您提供你可以下载的各种文件的链接来跟随我的分析。

关于性能

Android 4.1把重点放在Butter项目的性能上,它带来了新的性能分析工具,如systrace。Android 4.2不提供象systrace那么显著的工具,只是提供了对你的工具箱的一些有益补充。在本文的后面你会发现这些新工具中的一个。

性能分析往往是一个复杂的任务,需要大量的经验和对某些工具、硬件、API的深入的知识等等。经验使我能只用几分钟就进行在这里展示的分析——你可以在我十二月一日的Twitter stream上看到它“实时”发生。你可能要多试几次才会感觉这种工作很容易。

证实我的怀疑

关于性能操作,牢记最重要的一件事就是始终用测试去验证你的行为。即使Falcon Pro 在Nexus4上运行帧率下降看起来很明显,我还是需要确认一下。因此,我将应用安装到一部提供不同于Nexus性能概况并且比Nexus4更强大的Nexus7上。Nexus 7提供了一个有趣的更有优势的性能分析工具,我们以后再谈。

在Nexus 7安装应用程序并没有什么区别,我仍能看到帧率下降。应用甚至显得稍差。为了描述这个问题,我决定用4.1以后引入的“GPU呈现模式分析”工具。你可以在应用设置下的“开发者选项”中找到它。

如果“开发者选项”在你的android4.2设备上不能使用,找到“关于手机”或者“关于平板”勾选底部的7的倍数构建选项。

Turn on Profile GPU rendering

当选项打开后, 系统会保持跟踪每个窗口绘制最后128帧所耗费的时间。使用这个工具你必须首先结束掉应用– android将来的版本将摆脱这个限制。

方法说明:除非另有规定,本分析每个测量是通过每次缓慢滚动主时间轴上下几点,显示最多有一个额外的列表项。

在运行应用,主时间轴开始滚动时候,我在终端执行了如下命令:

$ adb shell dumpsys gfxinfo com.jv.falcon.pro

在产生的日志中,你会发现一个标题为: Profile data in ms. 这一节包含为每个窗口所属应用产生的3列表格。 为了使用这些数据, 简单的复制表格到你喜欢的电子表格软件中就会生成一个堆叠柱状图表。下面的图是我的测量结果 (原始表格数据 可以在线查看.)

每列给出渲染每帧大概需要多长时间:
  1. Draw是消耗在构建java显示列表的时间。 它显示出运行方法用的时间诸如View.onDraw(Canvas).
  2. Process是消耗在Android的2D渲染器执行显示列表的时间。你的视图层次越多,要执行的绘图命令就越多。
  3. Execute是消耗在排列每个发送过来的帧的顺序的时间.这部分的图通常是很小的。

注意:使顺利在60帧,每帧必须小于16毫秒完成。

关于Execute:如果执行耗费了过长的时间,这意味着你是跑在前面的图形管线。 android在运行时可以有3个缓冲区.如果你需要另一个应用程序将阻塞直到其中的一个缓冲区释放出来。两个原因会发生这种情况。第一,你的应用在Dalvik中快速绘制但在GPU显示列表时候消耗了大量时间。第二,你的应用程序花了很长的时间来执行第几帧;一旦管线满了他将无法赶上,除非动画完成。我们希望Android在未来版本中改进。

该图显然证实了我的怀疑:通常应用运行良好,不过有时候会运行帧率下降。

进一步观察

尽管我们收集的数据显示,应用有时候花费太久去渲染,但是这并不是全部的事实。帧刷新率也能够被没有调度或者错误调度的帧所影响。例如,一个应用总是以少于16ms的时间来画图,但是有时候在帧之间展现更长的任务,有时他将失去一个帧。

Systrace 是用于检查,一个Falcon Pro 是否在遭受这个问题最简单的工具。这个工具是一个具有非常低开销的系统分析工具。它的时域分析相当精确,并且给你展示了整个系统在做什么,包括你的应用。

为了让systrace起作用,进入Developer options并且选择Enable traces。一个对话框出现,并且让你选择你想要分析什么类型的事件。我们只关心Graphics 和 View。

注意:不要忘记关掉分析GPU rendering。

Enable systrace

#p#

为了使用systrace,打开一个终端,并从Android SDK的tools/systrace下运行systrace.py:

默认该工具将捕获5秒钟的事件。我只是上下滚动主时间轴。跟踪结果是一个独立的HTML文件。

建议:为了在systrack中能够导航,可以使用WASD键平移和缩放。W将会放大鼠标光标处的内容。

一个systrack文件展示了很多有趣的信息。比如,它表明你是否有一个进程计划在CUP。如果你放大到名为10440: m.jv.falcon.pro的最后一行,你能看到应用做了些什么。如果你查看performTraversals中的一块,你能看到应用绘制一帧消耗了多长时间。

虽然大多数的performtraversals低于16毫秒的临界值,但是有些需要更多的时间,这也证实了先前获得的测量结果(放大在935 MS标记看到这样一块。)

更有意思的是,你可以看到应用有时候会丢失一帧,因为它没有安排一个绘制操作。放大到标记为270毫秒的地方,找到deliverInputEvent 块,消耗25毫秒。这些块表示应用消耗25毫秒来响应用户触摸事件。由于应用程序是使用ListView,这可能是由于在适配器的一个问题,我们随后会回过头来讨论。

Systrace十分有用,不仅是它能检查应用消耗过多的是在绘制上,而且也能帮助我们找到其他的性能瓶颈。尽管它很有用,但是也存在自身的局限性。它只提供了高层次的数据,我们需要利用其他的工具才能明白它真正发生了什么。

可视化的透支

绘制性能问题可能有很多根本原因,但是其中一个常见的是透支。透支发生在应用每次向系统请求在其他物体上绘制内容。想像一个最简单的应用问题:一个白色背景的窗口,在它的上面一个按钮。当系统绘制按钮时,要绘制已存在的白色背景上。这就是透支。

透支是不可避免的,但是过多的透支就会产生问题。设备具有有限的内存带宽,如果透支导致你的应用请求资源超过了可用带宽,就是造成性能下降。不同设备可以明确负担透支的数量是变化的。

一个好的经验法则是针对最大透支的2倍;这意味着你可以绘制屏幕一次,再画上画两次,每个像素总量的3倍。

透支的存在也通常表示其他问题:太多的视图,层次结构复杂,通胀时间较长,等

Android提供了三个工具来识别和修复过度绘制: Hierarchy Viewer, Tracer for OpenGL 和 Show GPU overdraw。前两个可以在ADT或者独立的监控工具中找到。最后一个是开发这选项的一部分。

Show GPU overdraw 使用不用的颜色来绘制屏幕,来指示过度绘制在哪里发生以及程度如何。打开此选项然后别忘了关闭你自己的应用 – 未来的Android版本中将不再需要这样做。

Enable overdraw debugging

在看Falcon Pro之前,让我们看一下设置Show GPU overdraw选项的页面长什么样。

Settings with overdraw debugging

如果你记得每种颜色代表的含义,这些结果就很容易解释:

  • 没有颜色意味着没有透支。像素只画了一次。在这个例子中,你可以看到背景颜色没有变化。
  • 蓝色 意味着透支1倍。像素绘制了两次。大片的蓝色还是可以接受的。 (如果整个窗口是蓝色的,你可以摆脱一层。)
  • 绿色 意味着透支2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你也应该尝试优化,减少他们。
  • 浅红 意味着透支3倍。像素绘制了四次。小范围内可以接受。
  • 暗红 意味着透支4倍。像素绘制了五次或者更多。这是错误的,要修复他们。

基于此信息你可以看到设置是一个好的应用程序,不需要任何额外的工作。有一点红色在转换部分,但是并不需要过于纠结。

透明像素:仔细看看以前的截图。每个图标是蓝色的。你可以看到位图的透明像素加剧了透支。透明像素必须被GPU处理并且代价是昂贵的。Android使用层和9-pathches作为最优方案去避免绘制透明像素,所有你只用考虑位图就行。

透支和GPU: 在移动设备上有两种GPU架构。第一个使用了延迟渲染, 例如:ImaginationTech的SGX系列。 这种架构允许GPU检测和修复透支的具体情况 (如果你是混合透明或半透明的像素,它将不起作用)第二结构采用直接绘制,可以在NVIDIA的Tegra GPU的找到。这种体系结构不能优化你的透支,这就是为什么我喜欢在Nexus 7试验。两种架构都是各种所长,各有所短,但是具体的内容已经超出本文范围。只要知道两种方式都很好的运行。

现在让我们看一下 Falcon Pro…

Overdraw in Falcon Pro

截图中有大片的红色! 然而有趣的是列表的背景是绿色的。这表明该应用甚至在绘制正文之前,就有了一个2倍的过度绘制。这里我们看到的问题很可能跟多重全屏背景有关。要修复它通常比较容易。

删除附加层

为了减少透支,我们必须了解它是怎么来的。这是比层次视图、跟踪OpenGL更有用。层次视图是ADT(或者监听器)的一部分,并可以生成层次视图快照。特别是调试布局问题,很有帮助,对性能工作更是得心应手。

重要提示:层次视图只能工作在非安全设备上,比如工程机、平板或者模拟器。在某些设备上使用层次视图需要在你的应用上安装ViewServer(一个开源库)。

在ADT(或监视器)中打开 层次查看视图 ,然后选择 Windows 选项卡。斜体高亮的窗口是设备的前台窗口,也就是通常情况下你要查看的那个。点击它然后 点击工具条中的 Load 按钮 (它看起来像一个由蓝方框组成的的树形结构)。加载这棵树可能需要一会儿所以要有耐心。加载好后,可以看到类似下图的样子。

Hierarchy Viewer

现在层次的视图已经展现在工具中,我们可以查看它就像浏览一个Photoshop文档那样。要进行查看可以点击工具条中的第二个按钮 – 该按钮的提示是“捕获窗口层次[…]”。Adobe Photoshop不是必须的,因为这里生成的文档也可以由诸如Pixelmator和GIMP之类的工具来打开。我生成的PSD 文件已经可以从网上下载。

该Photoshop文档以每一个视图一个层次的方式展示了该应用。每一层都被标记为可见或不可见,可以通过View.getVisibility()的返回值来查看。每一层都以它所属的视图来命名,或者是android:id 或者是类名。我曾经尝试添加分组支持来重建视图树…我真应当好好完成该功能。

Photoshop as an IDE

通过检查层级列表,我们可以快速的确认至少一个过度绘制的原因:多重全屏背景。第一个背景是第一层视图,称为DecorView。该视图是Android系统创建的,包含了主题中定义的背景。系统中渐变的默认设置是不可见的,所以可以安全的把它去除掉。

从DecorView向上滚动,你可以看到一个包含另一全屏梯度背景的LinearLayout。这是与DecorView完全相同的背景,因此是不必要的。唯一可见的背景,必须保持属于名为id/tweet_list_container的视图。

删除窗口背景:当你的应用程序启动时,在你的主题中定义的背景是被系统使用创建预览窗口的。绝不要把它设置为null,除非您的应用程序是透明的。相反,要把它设置为你想要的颜色或图片,或者通过调用getWindow().setBackgroundDrawable(null)从onCreate() 中摆脱它。

#p#

进一步减少过度绘制

Photoshop文档有助于理解应用是如何构建的,但是用它来消除小区域的过度绘制时就有些困难了。现在我们必须转向Tracer for OpenGL。打开ADT (或监视器)中的同名视图,然后点击工具条中的箭头图标。输入你app的包名和主Activity的名称,然后选择一个目标文件并点击Trace。

建议:获取OpenGL跟踪是大任务并且很耗时。要使该任务更轻便更快,请勿勾选所有的Data Collection Ooptions 复选框。

Starting an OpenGL trace

Activity 名称:当你运行一个应用时,logcat会展示包和Activity的名称。这就是我怎么知道在Tracer for OpenGL中该敲入些什么的原因。

当应用启动起来,打开前两个设置。

  • Collect Framebuffer contents on eglSwapBuffers()
  • Collect Framebuffer contents on glDraw*()

第一个有用的选项能帮助你快速的找到感兴趣的帧。第二个选择让我们看到每个帧由绘图命令绘制命令。第二个选项是解决透支问题的关键。

Enable data collections

这两个选项使我开始滚动主时间轴。它将消耗很长时间去捕捉每一帧(不出意外是30秒)所有我建议你直接下载我的捕获跟踪.你可以通过点击工具栏上的第一个按钮在Tracer for OpenGL打开文件。

一旦加载完成。跟踪显示你每GL命令发送到GPU的每个捕获的帧。 如果你下载了我的跟踪文件,跳转到21帧。当某一帧被选中你可以看到类似Frame Summary 选项卡情形. 另外,你可以点击蓝色高亮绘制的命令,在Details 选项卡中查看当前帧的状态。

组织:GL命令通过view分组。他们重新创建相同的树,在你的Hierarchy Viewer或XML布局文件可以查看。这使得了解视图生成特定的操作很容易。

Analyzing a frame in Tracer for OpenGL

通过点击先后在前三个绘图命令,你可以看到在PS已经确认的问题;一个全屏背景绘制3次。

通过向下追溯查找,我们可以进一步地找到更多有待优化的。当一个tweet(列表项目)被绘制的时候,一个ImageView控件用来绘制图像。控件首先绘制图像本身的背景:

Tweet item

Avatar background

Avatar

如果你凑近一点看,你会发现背景只是作为图片的边框。这意味着在图像背景中间黑色的部分过度绘制了。9-patch部分都被图像覆盖了。

这个问题的简单解决方法是把9-patch中间部分设置为透明。 Android的 2D渲染器总是把9-patch优化为透明。这个简单的改变将会去掉很多的过度绘制。

有趣的是, 相同确切的问题发生与内联元素。头像小不是个大问题,但内联元素却能够占据屏幕的绝大部分区域。解决方法是完全一样的。

Inline media background

Inline media

更多的选择: 我想Android的2D渲染管道,能够自动检测和纠正你过度绘制。我有一些想法但我不能作出任何承诺,就像使用内置GPU的优化,这只会与不完全透明图元。

扁平化(译注:用“缩减”更直观)视图层级

现在(其实是大部分时候)透支是要注意的事情,让我们回到层级查看器。通过检查层级树,我们可以尝试识别不必要的视图。删除视图,尤其是视图组,不仅可以帮助提高帧速率,而且可以减少内存消耗和启动时间等等。

快速浏览一下Falcon Pro的视图层级,足以识别几个只有一个单独子视图的视图组。这些视图组通常是不必要的,很容易去除。至少下面截图中显示的节点中的两个应该被删除。

Unnecessary views

有许多其他的视图可以从这棵树中移除。例如,每个包含名为 id/listElementBottom的 RelativeLayout的tweet(译者注:在Twitter上发布的消息)。此布局包含了作者的名字,他的Twitter地址,这条tweet发布过后的时间,和一个图标。作者名称和Twitter地址是两个单独的 TextView而不是仅用一个,是为了使用不同风格。时间和图标使用一个 TextView 和一个 ImageView,可以使用 TextView的复合画板功能合并成一个 TextView。

左边的滑入式菜单使用了几组 LinearLayout+TextView+ImageView 来显示带图标的标签。每一组都可以用一个单独的TextView来替换。

如何摆平你的UI:我在2009年的谷歌I/O上谈论了题为激发你的UI的文章,在其中更详细地解释了这些技术。

输入事件处理

还记得当我们在看systrace的时候发现触摸事件响应处理有一些延迟吗?现在就是解决这个问题的时候啦。traceview就是处置这种问题和了解应用正在做什么的最好的工具。

 

Traceview是一个测量应用的方法调用所耗时间的Dalvik分析器。怎样使用它呢?在ADT或者监视器中打开DDMS视图,在Devices选项卡中选择您的应用的进程,然后点击“start method profiling”按钮(就是一个红色圈圈和三个箭头的那个按钮)。

开启追踪之后,在主时间轴上选取开始和结束时间,再次点击按钮来完成跟踪。您也可以下载 我的程序调试信息。结果如下。

Traceview

点击第21行,ViewRootImpl.draw(),该方法调用时间将会高亮。表的最后一行将会给出这个方法及其子方法的平均调用时间。如果你仔细看时间轴上的高亮处,你会发现连续帧之间的差别。

一个弄清楚这些差别之间是怎么回事的简易方法就是在差别开始出现的地方逐渐放大,然后点击其中最大的彩色块。跟踪父链直到找到你的问题所在。在这个案例中,我跟踪了Pattern.compileImpl方法的调用(平均调用时间为0.5ms),找到了DBListAdapter.bindView方法.

显然,应用程序将相同的表达式被一遍又一遍的重复编译,每次调用时候势必在主时间轴产生一次调用时间花费。Traceview表明bindview方法的平均调用时间为38ms,56%的时间都花在了HTML文本解析上。这个花费应该在后台默默的运行而不应该阻塞UI主线程。当然正则表达式也不应该被一次一次的重复编译。

试试吧,少年!

最后一个调试给大家留作练习。这是一个包含两个菜单可以向左或向右滑动切换的应用。但是我在使用openGL调试工具调试时发现在菜单滑动切换的时候产生了大量的画图消耗。下载我的调试,看看是什么原因造成了这种现象吧(去到第34帧。)

提示:

1、应用程序应该通过调用 View.setLayerType()使用硬件层来简化绘图。

2、多余的背景也可以通过9-patches来巧妙的优化。

3、裁剪同样也是很有用的优化方式。

4、通过设置ColorFilter传递给setLayerType()的画笔的方式也许可以移除最后的绘图指令哦。

我们一起学习了多种优化应用的工具。虽然我可以花大量的时间来讲解选用何种技术来解决解决特定问题,但是那样的话这篇文章就会显得太臃肿了。android开发者官网上的提供文档和Google I/O大会有关于android的讨论(网上有免费的幻灯片和视频)也许能够帮助你。

责任编辑:张叶青 来源: 开源社区
相关推荐

2014-03-21 10:31:51

NSArray枚举

2013-12-06 14:52:49

性能评价模型分析WEB系统

2022-11-09 11:50:21

2021-08-27 07:01:06

用户体验设计师产品设计

2021-09-27 09:52:41

FacebookBOLTLinux

2011-06-01 10:58:54

Android Service

2017-01-11 19:15:55

Android着色器Tint

2010-03-02 15:10:27

Android系统

2014-06-06 10:47:52

Android视图大小测量案例

2012-06-14 10:08:18

2021-08-23 15:45:55

5GCPE终端网络

2013-09-16 15:16:20

Android性能优化

2010-03-03 16:51:13

Android版本

2017-02-21 10:30:17

Android单元测试研究与实践

2011-04-25 09:12:47

LinuxIO数据库

2014-11-11 16:28:21

浪潮高性能计算煤炭研究

2014-06-17 09:28:06

浪潮高性能计算重离子加速器

2024-01-16 12:19:08

MySQL重要机制高并发

2011-08-19 10:13:05

iPhone开发

2010-02-04 10:27:33

Android DDM
点赞
收藏

51CTO技术栈公众号