片头声明:
1、本片是据Romain Guy剧本编写Android Performance Case Study衍生的电影,某些部分可能由于个人英语水平有限及理解原因,可能有别于原作者的原意。如有发现,请指正。以利于我们共同学习,共同进步。
2、本片是继Android性能优化案例研究(上) by孙立出的下版。狗尾续貂,望大家海涵。
剧情介绍:孙立翻译的上半部分是如何发现性能问题,我这的下半部分是如何使用工具确定这些问题并给与了部分问题的解决方案。对于上部,就不再这里转载了,可以直接点击上面链接进行阅读学习。也可能过几天会转载过来
各位看官,下面就接上部开播:
移除无用的图层:为了减少重绘,我们首先必须知道,什么会导致重绘。这也是Hierarchy Viewer和Tracer for OpenGl之前的用处所在。
Hierarchy Viewer(图层查看器)是ADT的一部分,可以用于检查View Hierarchy(视图层级)的快照。它在解除布局问题时尤其有用,但也可以方便的检查工作性能。
【重要:默认情况下Hierarchy Viewer只能工作在非安全模式的设备上,比如工程机、平板或者虚拟机。要在所有手机上使用Hierarchy Viewer,需要添加一个叫ViewServer的开源库项目到你的应用中。
https://github.com/romainguy/ViewServer】
在ADT(或者监视器)中打开Hierarchy Viewer视图,然后选择Windows tab。(粗体高亮现实的窗口就是手持终端的foreground设备,一般也就是你要检测的那个界面)。点击高亮显示的条目,然后点击在工具栏中的Load按钮。(它看起来想一个蓝色方块树。)然后耐心等待载入整个树。当这该视图树载入完毕,你将看到如下图相似的画面。【译者:最好使用Monitor,译者多次使用ADT总是出错。】
现在,View Hierarchy已经在工具中载入成功,我们可以将其作为一个图片文档导出。只要点击工具栏中的第二个按钮(工具提示为:Capture the window layers捕获窗口图层)。Adobe Photoshop不是必须的软件,可以使用与其兼容的工具,比如Pixelmator,GIMP等等。可以下载我生成的PSD文件:
这个图像文件展示了应用中在一个Layer(图层)的每一个View。每一个layer(图层)基View.getVisibility返回的数据,分别被标记为可见或者不可见的。每一个layer(图层)使用View 可用android:id或者它的类名来命名。我开始添加支持组件来重建视图树,我应该完成这个功能。
通过检查这一系列图层,我们可以快速认出至少一个资源的overdraw,多个全屏背景。第一个就是这第一个layer。称为DecorView。这个视图被Android生成并包含了在主题中的特殊背景。在应用中这个默认梯度(默认透明度)是不可见的。因此可以安全的将其移除。
滚动DecorView你可以看见一个LinearLayout包含了另外一个全屏梯度(透明)背景。它与DecorView的背景是完全一样的,因此,它也是不需要的。唯一可见背景必须保持,它就是命名为id/tweet_list_container的视图。
【移除窗口背景:定义在你主题中的背景 被系统用来在启动的你应用之前作为预显示窗口。千万不要将其设置为null除非你的应用是透明的,相反,应该设置一个你认为好的颜色或者图片,或者在onCreater()方法中调用getWindow().setBackgroundDrawable(null)来将其去掉】。
进一步的减少overdraw:
通过图像文件,我们可以很容易明白应用是怎么生成的。但是它对于移除小区域的overdraw就显得十分吃力。那我们就需要打开Tracer for OpenGL。在ADT或者Monitor中名字为Tracer for OpenGL的视图,在工具栏中点击箭头那个图标。输入你应用的包名和主Activity的名字,然后选择存放位置(destination File)点击trace按钮。
【建议:OpenGL检测可能很大并且可以真正减速获取图像。 为了让它更小,获取图像更快,不要复选Data Collection Options boxes框。】
【Activity名字:当你打开应用后,logcat将显示包名和Activity名字。根据这个你就可以晓得在Tracer for OpenGL中要输入什么东东了。】
当应用已经打开并运行,使可用前两个选项:
* Collection Framebuffer contents on eglSwapBuffers()
* Collection Framebuffer contents on glDraw*()
第一个选项对于快速找到你感兴趣的frame十分有用,然而第二个选项则允许我们通过绘制命令查看每个frame生成使用的命令。第二个选项是解决overdraw 问题的关键所在。
开启这两个选项之后,我开始滑动主界面的时间轴。它将花费相当长的时间来获取每一个frame(不出意外的话,需要30秒)。因此我建议你下载我获取的文件(http://goo.gl/yPjB5)。你可以在Tracer for OpenGL中点击第一个按钮打开这个文件。
加载完毕后,视图展示你每一个发送到GPU的GL命令为一个frame快照。如果你下载我的文件。跳到21Frame处。当一个frame被选中之后,你可以在Frame Sunmmary tab 页中查看它是什么样子的。另外,你可以点击绘制命令,将其蓝色高亮,在Details tab页中查看这个frame的当前状态。
【总结:GL 命令被通过View分组。他们重新创建你在HierarchyViewer或者xml Layout文件夹可以看到的树。这使它很容易就能理解什么View产生了一个什么样的特定操作。】
通过连续点击前三个绘制命令,你可以看出已经在Photoshop中确定的问题,一个全屏的背景被绘制了三次。
我们通过查看下载的Tracer,可以发现更多可以优化的地方。当一个tweet(listitem)被绘制,一个ImageView被用于绘制头像(原文是:avatar)。ImageView第一次绘制了一个背景然后绘制头像(avatar)自身。
如果你仔细看,你将会发现背景只是作为图片的一个边界。这意味着,这意味着在头像图片中间的这黑色部分被
overdraw了。这片9path已经被头像图片(avatar)完全覆盖了。
一个对于这个问题的简单修复办法是使这中间可拉伸的9-patch图片透明。Android的2d渲染器优化9patches的透明部分。这个简单的改变将会移除一小部分的overdraw。
有趣的是,同样的问题也出现在了内联媒体(inline media)中。头像图片(Avatars)图片十分小,所以他们overdraw不会导致大的消耗。但是内嵌媒体(inline media)可占据屏幕相当大的一部分区域。问题的修复与上面的方法一致。
【深度优化:我更希望Android的2d渲染管道有能力自动正确的为你overdraw。我们已经有了些想法,但是我还不能做出任何关于这方面的承诺。正像加入GPU优化,这只能作用于不透明的元素。】
扁平化视图(
Flattening the view hierarchy):
好的,现在我们已经对overdraw(大部分都是)关心过了,现在让我们回过头来再看看Hierarchy viewer。通过检查图 像树,我们能试图确定不需要的Views。移除Views特别是ViewGroup,这不仅可以提高帧速率,而且还可以减少内存消耗 ,启动时间等等。
快速查看Falcon Pro视图层级,你可以确定有几个ViewGroup和一个单独的子视图。这些ViewGroup一般都是不需要并且 很容易移除掉。下面展示的图片中至少最后面的两个节点是可以移除的。
也有一些另外的视图可以从这个树中移除。比如,每一个tweet都包含了一个命名为listElementBottom的 RelativeLayout。这个Layout包含了作者的名字,他的Twitter,从tweet发送出来之后已经过去的时间。名字和handle是 两个分离的TextView代替了单独一个TextView加入多种类型不同的spans,时间和icon使用一个TextView和一个 ImageView可以被结合称一个单独的TextView,查看TextView's compound drawables
左边的菜单使用了多个LinearLayout+TextView+ImageView的组合来展示标签和图标。其实每个可以使用单独一个 TextView就可以代替了。
【如何扁平化你的UI:我解释了很多关于这方面的技术。你可以去看我在2009 Google I/O Talk
】
关于“输入”事件:记不记得当我们查看systrace并且发现,当处理触摸事件的时候会有些卡顿?现在,是时候来追踪这个 问题了,traceview是我们去明晰系统正在做什么的最好工具。
Traceview是一个记录应用在调用某个方法花费多少时间的虚拟机分析器。可以在ADT的DDMS视图或者monitor中启动他, 在Devices tab中选择你应用进程,然后点击Start method profiling按钮。红色环绕的三个箭头。
在开启追踪之后,我来回滑动主时间轴,并且重新点击按钮来结束trace。你也可以下载我的追踪记录:
结果如下图截屏所示。
点击第#21,ViewRootImpl.draw().高亮绘制时间。表格中最后一行给你一个这个方法的和在它的子类里平均的调用时间 。如你使用高亮来仔细看这时间轴,你将会注意到在连续frames之间的间隙。
一个最简单找出这些在这些间隙中到底发生了什么方法就是(选择他们其中一个的开始部分进行放大,然后点击你能发 现的最大色块)你追踪父子链(parent chain)直到找到你可以辨识出来的东西。在我的例子中,我追踪一个花费了平 均时间的0.5ms的叫Patter.compileImpl,直到所有都指向了DBListAdapter.bindView。
显然每次一旦有新的item被绑定或者滑动主时间轴时,应用都会重新一遍一遍的编译这相同的正则表达式。Traceview展 示了绑定一个View平均花费38ms并且有56%的时间花费在解析HTML文本上。这看起来某些东西可以被可以在后台实现而不 是阻塞UI线程,正则表达式不应该每次都要重新编译。
接下来看你的了!
我留下最后一个trace作为练习。这个应用在两边有两个菜单,可以通过敲击时间线的左边或者右边来显示。(在显示这 菜单时GPU overdraws 高亮了过度绘制的部分,我已经使用Tracer for OpenGL来获取了这个问题的几帧。下载我的 trace然后看看你是否能找到什么导致了overdraw(作者提示go to frame #34)。
提示就不翻译了: Hints: the application should use hardware layers by calling View.setLayerType() to simplify drawing. There are also extraneous backgrounds that can be optimized away with clever use of 9-patches. Clipping could also be very helpful. Finally, maybe a ColorFilter set on a Paint passed to setLayerType() could help remove the last drawing command.
我已经展示了多种你可以用于优化你的应用的工具。我已经花费了大量的时间来描述在这些工具的帮助下,使用什么技术去解决已辨别的问题。但是这个文章将会转到一本书中。检出文档这官方文档( Android developers web site)引用和所有GoogleI/O Android talks.
---Romain Guy
【后语:最好使用Monitor,鄙人在使用ADT的时候老出错。
补Traceview操作图:
】
来源:oschina
链接:https://my.oschina.net/u/591636/blog/129598