(转)Android性能优化案例研究(下)

译者前言:在Android性能优化案例研究(上)中,作者Romain Guy将Falcon Pro这款应用作为例子,通过Android现有的工具追踪和分析了其隐藏的性能问题(重绘)。下篇作者将会带来如何解决此类问题的方法和思路。

去掉冗余的图层

为 了去掉重绘我们必须首先理解它从哪里产生的。这就轮到Hierarchy Viewer和Tracer for OpenGL大显身手的时候了。Hierarchy Viewer是ADT工具(或者monitor)的一部分,可以被用作对视图层级进行快速解读。在处理布局问题时特别有用,对于性能问题也很适用。

 

重要:

Hierarchy Viewer默认只能在非加密设备使用,例如工程机,工程平板或者模拟器。为了能够在任何手机上使用Hierarchy Viewer,你得在你的应用中添加ViewServer,这是一个开源库。

在 ADT(或者monitor)中打开Hierarchy Viewer的全景图,选择window标签。这个界面就会粗体高亮的显示当前设备运行的窗口,通常就是你想要研究的那个应用。选中它再点击工具栏的 Load按钮(它更像蓝色方块组成的树)。加载这棵树需要一段时间,所以请耐心等待。当这棵树加载完成你就可以看到如下图所示的画面。

 

 

现 在视图的层级已经加载到工具里,我们也可以将其转换为PhotoShop文档。只要点击工具栏的第二个按钮,工具提示说:“Capture the window layers [..]”。Adobe Photoshop本身不是必须的,因为生成的文档可以被其他工具兼容,例如Pixelmator, The GIMP等等。你们可以下载我所生成的PSD文件。

PhotoShop文档可以显示每个视图的每个图层。一个图层可以标记为可见或者不可见,这是取决于View.getVisibility()返回的结果。每一个图层命名在一个视图的后面,如果视图的android:id存在则使用android:id,或者使用它的类名。我曾经开始添加对于组(group)的支持用于视图树的重建…我其实应该早点把这个功能做完。

 

 

通过检查每个图层的列表,我们可以快速的辨别至少一种重绘的源头:多个全屏的背景。第一个就是第一个图层,叫做DecorView。这个view是由Android框架生成的,包含了皮肤主题指定的背景。这个默认的背景在应用中是不可见的,因此它可以被安全的去掉。

 

从DecorView向上滚动,你可以看到一个LinearLayout,它包含另一个全屏的背景。它和DecorView的背景是一回事,所以它也是不需要的。唯一可见且肯定存在的背景属于一个名叫id/tweet_list_container的view

 

去掉桌面背景:

定 义在你的主题皮肤里的背景通常是当系统启动你的应用时用来创建预览窗口的。千万不要设置它为空(null),除非你的应用是透明的。相反,设置它为你想要 的颜色或者图片,或者在onCreate()里调用getWindow().setBackgroundDrawable(null)来去掉它

 

进一步去掉重绘

用 Photoshop的文档图来理解应用是怎么创建的是很有用的。但是用来去掉小范围的重绘有点难度。现在我们就必须转向Tracer for OpenGL。同样在ADT(或者monitor)中打开它的视图,点击工具栏的箭头图标,输入你应用的包名和你主要的Activity的名字,然后选择 一个目的文件,点击Trace。

 

一句建议:

OpenGL traces抓取的数据量很大。为了让数据量较小,同时也利于更快速抓取。请去掉“all the Data Collection Options”选项。

 

Activity名字:

在应用启动时可以通过logcat获得包名和Activity名字。这就是为什么我可以知道在Tracer for OpenGL输入这些名字。

 

当启动并运行这个应用时,打开前两个选项:

  • Collect Framebuffer contents on eglSwapBuffers()

  • Collect Framebuffer contents on glDraw*()

 

第一个选项可以方便的快速找到你感兴趣的帧,第二个选项可以让我们看到每一帧是如何通过一步步绘图命令建立起来的。第二个选项就是解决重绘的关键。

随着这两个选项的开启,我开始滚动屏幕。抓取每一帧需要很长时间(也许要30秒),所以我推荐你可以先简单的下载我抓取的trace文件。你可以通过Tracer for OpenGL工具栏的第一个按钮打开这个文件。

trace 文件一旦加载完成,你就可以看到每一帧发生给GPU的每一个GL命令。如果你下载了我的文件,你跳到第21帧。当一帧被选中后,你就可以看到Frame Summary选项卡中呈现的模样。此外,你还可以点击高亮为蓝色的drawing命令,这样你就可以在Details选项卡中获得当前帧的状态细节。

相继的点击前三个绘图命令,你就可以看到在PhotoShop里面已经得到鉴定的问题:全屏的背景被画了三次。

通过深入研究这个trace文件,我们可以找到更多优化的地方。当去画一个消息内容条目时,ImageView被用来画头像。这个ImageView先画了一个背景然后再画头像:

如果你看得再仔细点你就会注意到背景只是用来作为图片的边框。这就意味着在位于头像的黑色方块产生了重绘。那块9格图(9-patch)完全被头像覆盖了。

解决这个问题的有一个很简单的方法就是让这块9格图设为透明。Android的2D渲染引擎已经在9格图上做了优化。这个简单的方法就可以去掉重绘。

有趣的是,同样的问题也发生在内嵌的媒体内容上。头像很小所以它们的重绘不是个大问题。但内嵌的媒体内容却可以占据屏幕的大片区域,这个问题就严重了。可以用同样的方法去解决它。

未来的优化:

我希望Android的2D渲染流水线能够自动的检测和修正重绘。我们已经有了一些想法但还不能做出承诺。

扁平化View的层级

现在重绘已经基本考虑过了。让我们重新回到Hierarchy Viewer吧。通过研究这棵UI树,我们可以尽量去鉴别哪些View不是必须的。去掉View,特别是去掉ViewGroups,不仅可以提供帧率,也可以节省内存,加快启动时间等等。

看一眼Falcon Pro的View的层级树就可以发现一些ViewGroups是在同一个子节点上。ViewGroups通常不是必须的,也很容易去掉。下面这个截图显示至少有两个节点是可以去掉的。

也 有一些冗余的View可以去掉。比如每一个消息条目都包一个叫做id/listElementBottom的RelativeLayout。这个布局包含 了作者的名字,推特消息,已经发布了多长时间和一个图标。名字和消息用了两个不同的TextView,其实只需要一个TextView用不同的风格来显示 就行了。时间和图标用了一个TextView和一个ImageView,其实两者可以用一个TextView,然后用可视化绑定到TextView上。

左边滑动的界面用了若干不同的LinearLayout+TextView+ImageView来显示标签和图标。他们都可以通过一个TextView来代替。

如何扁平化你的界面:

我在2009年的Google I/O大会上做了一篇题为优化你的UI的演讲,里面介绍了这其中的技术细节。

关于输入事件?

还记得我们在看systrace时找到一段处理很慢的触摸事件?现在可以看看这个问题。理解这个问题最佳的工具就是traceview。

traceview 是Dalvik性能解析工具,它可以测量一个应用在每个方法调用上花费的事件。在ADT或者monitor里打开DDMS,在设备选项卡里选择你应用所在 的进程,然后点击“start method profiling”按钮(三个箭头和一个红色的圈),你就可以使用traceview。

当启动了traceview后,我滚动应用的界面,然后点击那个按钮结束跟踪。你也可以下载我的跟踪文件。结果如下图所示。

点击条目21:ViewRootImpl.draw(),高亮它所花的时间。表的最后一列表明这个方法的和在它的子类里平均的调用时间。如果你仔细看看高亮的时间轴,你可以注意到帧与帧之间的差距。

用 一个简单的方法来查看差距里面到底发生了什么,可以放大他们开始的阶段,然后点击你找到的红色的块。你可以跟着调用链来找到你能认出的方法。在我的例子 里,我跟踪了一个大概占用了0.5毫秒的Pattern.compileImpl方法,一直到跟DBListAdapter.bindView。

很 显然这个应用将同一个正则表达式编译了好几次,每一次滚动屏幕都伴随着一个条目的绑定。TraceView显示bindView平均占用了38毫秒,而其 中56%的时间花在了解析HTML文本上。似乎可以将这个步骤放在后台运行而不去阻塞UI线程,而正则表达式不应该每次都需要重新编译。

现在轮到你了!

我保留了最后一个跟踪文件作为测验。这个应用有两个滑动的菜单,可以左右滑动时间轴。Show GPU overdraws高亮了滑动时大量的绘图。我用Tracer for OpenGL抓取了滑动时的若干帧。下载我的trace文件,然后看看你是否能找到重绘的原因(去看第34号帧)。

提示:

应 用应该调用View.setLayerType()来使用硬件图层(hardware layer)来简化绘图。大量的背景可以使用9格图来做优化。裁剪也很有效。最后,也许可以将一个颜色过滤器(colofilter)设置在画笔 (paint)上,然后传给setLayerType(),这样可以帮助去掉最后一个绘图命令。

我向你们展示了大量可以优化你们应用的工具。我其实还可以花费大量的时间来描述用这些工具处理这些问题的技术方法,但这样文章就会变成长篇大论。你们可以去参考Android开发者的的官方文档和所有Google I/O上Android的演讲(ppt和视频都是免费可取得)。

英文原文:Android Performance Case Study  编译:ImportNew - 孙立

译文地址:  http://www.importnew.com/4065.html

【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】


转自:http://www.importnew.com/4065.html

你可能感兴趣的:(Java/Android,性能)