近期读到《Speed up your app》一文。这是一篇关于Android APP性能分析、优化的文章。在这篇文章中,作者介绍他的APP分析优化规则、使用的工具和方法。我觉得值得大家借鉴。英文好的读者可读原文(链接:http://blog.udinic.com/2015/09/15/speed-up-your-app)。

1、作者的规则

作者每次着手处理或寻找性能问题时,遵循下列规则:

  • 时常检测

在更新APP前后,用测试工具软件多检测几次APP性能,可快速得到测试数据。这些数字是不会说谎的。而仅仅用眼睛观察APP性能是肯定不行的。如你在观察几次相同的动画效果后,你就想象它运行的够快了,从而忽略一下问题。

  • 使用低端设备

如今硬件性能在不断的提升,如果仅在最新设备运行APP,可能不能充分暴露出APP中存在的性能问题。另外,尽管用户设备换手率已经很高了,但仍然不是所有用户都是使用最新的和最强功能的设备。所以应该使用低端设备上,运行APP,这可以帮助你更有效地发现问题。

  • 权衡

性能优化是要综合各方面因素进行评判、权衡。因为优化一项性能可能是要以牺牲另一项性能为代价的。分析、查找、修复是要花费很多时间。你要准备好自我牺牲。

2、作者的分析方法及使用工具

作者采用自顶向下方法,从手机运行的概况开始,逐级深入分析:方法的性能、内存使用情况、GPU渲染效果、视图层次、图形叠加绘制、图像透明值;解释Honeycomb引入的硬件加速以及视图层。

2.1 Systrace

手机实际就是一台功能强大的计算机,同时间做很多事情。Systrace能够展示手机运行的概况。

作者在Systrace中选取一个Alert,做为例子,讲解分析发现问题的方法:

1)由Alert找到函数(如:long View#draw()),再展开“Inflation during ListView recycling”

2)可以查看到函数的耗时,更详细观察分析其中哪项花费时间较长。

3)选择一帧查看它花费多久时间。如有一帧绘制用时超过19ms。展开“Scheduling delay”

4)它的值(Wall duration和CPU duration之间的差异)表明有很长时间CPU没有安排这个线程。

这就需要查查在整个这段时间里CPU都做了什么?但是Systrace只能查看运行概况,还不能得到更深层次的分析。为了找到CPU运行繁忙的真正原因,作者使用另一个工具:Traceview。

2.2 Traceview

Traceview是性能分析工具,可以显示每个方法运行时间。可从Android Device Monitor中启动,也可从代码中启动。

作者以“滚屏”动作为例说明Traceview分析方法。在“滚屏”动作的跟踪记录中,找到getView()方法,发现它被调用12次,每次CPU用时约3ms。但是每次完成整个调用却用时162ms!这就是个问题!

作者就继续查看getView()的子方法,查看各个子方法所用时间在总时间中比例。他发现Thread.join()的Inclusive Real Time占用约98%。他顺藤摸瓜,找启动子方法的Thread.run()方法(它是创建一个新线程时所调用的方法),逐个跟着,直到找到“元凶”:BgService.doWork()方法。

另外,GC(Garbage Collector – 垃圾收集器)不定期运行清理不用的对象。GC的频繁运行也会使得APP运行慢下来。为此,作者的下一步就是针对内存进行分析。

2.3 内存分析(Memory Profiling)

Android Studio逐步在改善,有越来越多的工具可以帮助我们找到和分析性能问题。作者用其分析内存使用情况。

2.3.1 Heap dump

Heap Dump可以看到Heap中依据类名排序实例的直方图。每个都有分配对象的总数,实例的大小和留在内存中对象的大小。后者告诉我们这些实例释放后,能够释放多少内存。这可帮助我们识别出大的数据结构和对象关系。这些信息可以帮助我们构建更有效的数据结构,解开对象之间联系以减少内存驻留,最终尽可能的减少内存占用。

在分析中,可发现“内存泄漏”。解决方法就是要记得在活动即将被销毁时调用onDestory()方法删除引用。

内存泄漏和较大对象的heap空间占用,使得有效内存减少,频繁引发GC事件,试图释放更多的heap空间。这些GC事件占用CPU,降低了APP性能。如果有效的内存数量不足与满足APP,且heap空间不能在扩大,就引发 —— OutOfMemortException —— APP崩溃。

Eclipse MAT(Eclipse Memory Analyze Tool)也是不错的内存分析工具。

2.3.2 Allocation Tracker

Allocation Tracker可生成在跟踪期间内存分配给所有实例的情况报告,报告可按类分组或按方法分组。它以很好的可视化方式展示哪个实例所获得内存最多。

使用这些信息,可以找出分配大内存的方法,和可能频繁触发GC的事件。

2.3.3 General memory tips

作者给出一些技巧:

  • 枚举

一直是讨论性能的热门话题。枚举比普通常数占用更多的内存空间吗?是的。但这肯定是坏事吗?未必。如在编写代码库,需要强类型安全性,这就应该使用它。如果有一组可以归结在一起的常数,此时使用枚举也许不不合适。怎样决定,你需要权衡考虑。

  • Auto-boxing

是自动将原始数据类型转换为其对应的对象表示(如:int 到 Integer)。每次原始数据类型被“装箱”到对象,就会产生一个新的对象(我知道这令人震惊)。如果有许多这样的操作,那么GC就频繁地运行。由于将原始类型数据赋值到对象时,自动进行auto-boxing的,就很容易忽视它的数量。解决方案就是尽量使数据类型保持一致。如果要在整个APP中使用原始数据类型,就尽量避免在没有实际需要时进行Auto-boxing。使用内存分析工具可以找到许多对象是表示原始数据类型。也可以用Traceview寻找到Integer.valueOf(),Long.valueOf()等等。

  • HashMap与ArrayMap / Sparse*Array

在Auto-boxing相关问题中,使用HashMap时,就要求用对象作为键值。如果在APP中用原始int类型,那么在使用HashMap时就需要将int转化到Integer。这种情况也许就需要用SparesIntArray。如果在键值仍然需要对象类型的情况下,也可以改用ArrayMap类。它非常类似HashMap,只是在其内部工作方式不同,是以降低速度为代价,使用较少的内存。这两者占用内存都比HashMap小,但检索所花费的时间略高于HashMap。如果数据项少于1000,它们的运行时没有什么差别。

  • Context Awareness

Activity内存比较容易泄漏。由于它们保持UI的所有视图层次,占用大量的空间,所以它们的泄漏也是非常“昂贵的”。许多操作都要求Context对象,你发起Activity。假如引用被缓存,并且该对象的存活期要长于你的Activity,如果没有清理它的引用,你就产生了内存泄漏。

  • 避免非静态内部类(inner class)

创建一个非静态内部类,并实例化它,就创建对外部类的隐式引用。如果内部类实例需要的时间比外部类长,这外部类就要在内存中保留,即使它不再需要了。例如,在Activity类内部,创建一个扩展AsyncTask的非静态类,然后着手启动异步任务,在它运行时,销毁活动。该异步任务在其运行期间,都保持这一Activity运行。解决方案就是不要这样做。如果需要这样,就声明一个静态内部类。

2.4 GPU Profiling

Android Studio 1.4增加一项新功能:分析GPU渲染功能。作者详细讲解这一新功能的分析方法。

在GPU选项卡下,可以在屏幕上看到图形化显示的渲染每帧所花费的时间。图形中每条都表示被渲染的一帧。颜色表示进程的不同周期:

  • 绘画(蓝色)

表示View#onDraw()方法。那部分建立/更改DisplayList对象,然后转换成GPU能够理解的OpenGL命令。高的条形可能是视图复杂,而要求更多的时间绘制它们的显示列表,而许多视图在短时间内就失效了。

  • 准备(紫色)

在Lollipop中,加入另一个线程,以帮助UI线程渲染更快。这个线程叫:RenderThread。它的责任是转换显示列表为OpenGL命令,再发送给GPU。这样在渲染过程中,UI线程可以开始处理下一个帧。这时UI线程将所有资源传送给RenderThread。如果有许多资源要传递(如许多/繁重显示列表),这一步可能需要较长时间。

  • 处理(红色)

执行显示列表产生OpenGL命令。由于需要视图重绘,如果有许多/复杂显示列表要执行转换,这一步可能需要较长时间。当视图无效或是移动时,都要要重绘视图。

  • 执行(黄色)

发送OpenGL命令到GPU。由于CPU发送这些缓存的命令到GPU,并期待收回干净缓存,这就阻塞调用了。缓存数量有限,并且GPU也很忙 —— CPU会发现自己必须先等待缓存释放。因此,如果在这一步我们见高的条形,就可能意味着GPU在绘制UI时非常忙,这个绘制在短时间内太复杂了。

具体操作实例见原文。

2.5 Hierarchy Viewer

作者喜爱这个工具。他对许多开发者根本不使用这工具感到失望。

使用Hierarchy Viewer,可以完整地观察到屏幕视图层次和所有视图的属性。还可以导出主题(theme)数据,查看到每个样式的所有属性。但是,这只是在Hierarchy Viewer独立运行时,才能查看这些数据。而不可以从Android监控器中查看。

作者在设计布局和优化布局时使用这个工具。

作者认为有时间,可以对每张视图都测量以及它的所有子视图。颜色表示视图与树中其他视图的比较情况,很容易找出最薄弱的环节。由于我们可以预览视图,这样就可通过视图树,跟踪找出可删除的冗余步骤。这其中,对性能影响最大的,被称为Overdraw。

2.6 Overdraw

如果GPU需要在屏幕上绘制很多内容,绘制每帧都需要增加时间,这样执行周期就拉长了,在图形中以黄色表示。在一些图形上再叠加绘制,如在红色背景上绘制黄色按钮,这就发生Overdraw。这种情况下,GPU需要先绘制红色背景,再在其上绘制黄色按钮,Overdraw就不可避免了。如果有太多的Overdraw层,这就使得GPU超负荷运行,偏离16ms的目标。

设置“Debug GPU Overdraw”开发者选项,所有Overdraw的严重程度都以颜色表示出来。1~2倍的Overdraw算好的,甚至有些小的红色区也不坏。但是如果在屏幕上有许多红色,这就有问题了。但是都被红色覆盖。这就是问题了。作者建议这时仅用一种颜色设置背景来解决这个问题。

注意:默认主题声明一个全屏窗口背景颜色。如果有不透明布局的Activity覆盖在整个屏幕上,可以通过删除窗口的背景色消除这层Overdraw。

Hierarchy Viewer能够输出所有层次到PSD文件中,用Photoshop中打开。在Photoshop中研究不同的层就可展示布局中的所有Overdraw。删除冗余的Overdraw,努力性能提高到蓝色上。

2.7 Alpha

使用透明效果也会影响性能。为什么?

ImageView相互重叠。用setAlpha()设置alpha值,这将传递给所有的子视图,对帧缓冲区进行绘制。结果都重叠混在一起。幸好,OS有这个问题的解决方案。布局数据被复制到off-screen缓冲区,用alpha值对off-screen缓冲区进行处理后,再复制到帧缓冲区中。效果就好了些。但是,我们为此付出了代价:把“帧”缓冲区改为off-screen缓冲区,实际上增加了一个隐含的Overdraw层。OS就不知道处理了,所以默认情况下经常要进行复杂地操作。

不过还是有方法设置alpha值,避免off-screen缓冲区增加的复杂性:

  • TextView

用setTextColor()替代setAlpha()。使用文本颜色的alpha通道,就可直接用它来绘制的文本。

  • ImageView

用setImageAlpha()替代setAlpha()。理由同TextView。

  • Custom View

如果自定义视图不支持覆盖视图,这复杂行为是无关紧要的。可通过重写hasOverlappingRendering()方法,让其返回false,通知OS直接绘制自定义的视图。还可以通过重写onSetAlpha()方面,让其返回true,选择手动处理设置,各alpha值对应的操作。

2.8 Hardware Acceleration

在Honeycomb(蜂巢,Android 3.x)引入硬件加速后,在屏幕上渲染APP可以以新的绘制模型(http://developer.android.com/guide/topics/graphics/hardware-accel.html)进行。新模型引入DisplayList结构,记录视图渲染绘制命令。还有另一个很好的特性,时常被开发人员忽视或不正确地使用 — 视图层。

使用视图层,我们能够非屏幕缓冲区(如前面所见,应用alpha通道)渲染视图,并且能按照我们的意愿操控它。由于利用这一特性能够更快地绘制复杂动画视图,所以它主要用于动画。没有这些层次,在改变动画属性(如:x坐标、缩放、alpha值等等)后,动画视图将无效。对于复杂视图,这个无效效果都传递到所有子视图,且重绘的成本很高。在硬件支持下,使用视图层时,GPU会为视图创建纹理。有几个操作可以用于纹理,而不会破坏它,如:X / Y位置、旋转、alpha等等。所有都意味着在动画期间,可以在屏幕上绘制复杂动画视图,而完全不会破坏它。这使得动画更加顺畅。

提出在使用硬件层时需要记住几件事:

  • 清理视图。硬件层消耗有限存储元件的空间、GPU。所以仅在确实需要的时候使用(像动画),并在事后清理。
  • 如果在使用硬件层后,改变视图,硬件层无效,并在非屏幕缓冲区重绘视图。这会发生在改变那些无硬件层优化的属性上(迄今为止,仅优化:旋转、缩放、x/y、转换、轴移和alpha)。

3、其他资料

作者为说明性能分析、优化,准备很多代码来模拟情景。这些代码可以在Github代码库(https://github.com/Udinic/PerformanceDemo) 或是 Google Play(https://play.google.com/store/apps/details?id=com.udinic.perfdemo) 找到。他将不同的场景分别放到不同的Activity中,并它们编写文档,尽可能帮助理解使用这些Activity会遇到哪方面的问题。可以用工具和运行APP来阅读Activity的javadoc。

作者还推荐一些学习交流方法:

  1. 作者极力推荐大家观看YouTube上一组Android Performance Patterns视频(https://www.youtube.com/playlist?list=PLOU2XLYxmsIKEOXh5TwZEv89aofHzNCiu),其中许多短视频来自Google,讲解不同性能主题。
  2. 加入Android Performance Patterns Google+ community(https://plus.google.com/communities/116342551728637785407),这里可以与包括Google人在内的其他人一起讨论性能,分享想法、文章,和提出问题。
  3. 更有趣的链接:
    • 学习Android图形架构(http://source.android.com/devices/graphics/architecture.html)是怎样工作的。这里有你需要知道的Android怎样渲染UI的一切,讲解不同的系统元素,如:SurfaceFlinger,以及它们之间怎样相互交互的。这篇文档很长,但是值得一读。
    • 关于Google IO 2012谈话(https://www.youtube.com/watch?v=Q8m9sHdyXnE),展示绘图模型的工作原理,和如何/为什么在UI渲染时会有一个Jank。
    • Android性能研讨会(https://www.parleys.com/tutorial/part-2-android-performance-workshop), 从Devoxx 2013开始,展示Android 4.4中绘图模型的一些优化,演示不同的性能优化工具(Systrace,Overdraw等等)。
    • 关于预防性优化(https://medium.com/google-developers/the-truth-about-preventative-optimizations-ccebadfd3eb5)的长篇文章,说明这为什么不同于提早(premature)优化。由于许多开发者认为影响不明显,所以没有优化他们的部分代码。要记住一点,积少成多就是一个大问题了。如果你有机会优化一小部分代码,那怕它可能微乎其微,也不要放弃优化。
    • Android的内存管理(https://www.youtube.com/watch?v=_CruQY55HOk)这是Google IO 2011的旧视频。它仍然很有意义。它展示了Android怎样管理APP的内存的,以及怎样用工具(如Eclipse MAT)找出问题。
    • Google工程师Romain Guy所做的案例分析(http://www.curious-creature.com/docs/android-performance-case-study-1.html),怎样优化Twitter客户端。在这个案例分析中,Romain展示了他怎样在APP中找到性能问题,以及他建议如何修改它们。在一篇回帖中,展示了在重新设计同一APP后产生的其他问题。

作者希望大家已获得足够资料和更强的自信。从今天开始优化自己的APP!

作者关于性能优化的演讲视频在这里:http://www.youtube.com/embed/v3DlGOQAIbw?color=white&theme=light

Android APP性能分析方法及工具的更多相关文章

  1. Atitit.提升软件Web应用程序 app性能的方法原理 h5 js java c# php python android .net

    Atitit.提升软件Web应用程序 app性能的方法原理 h5 js java c# php python android .net 1. 提升单例有能力的1 2. 减少工作数量2 2.1. 减少距 ...

  2. Android APP 性能优化的一些思考

    说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才 ...

  3. Android app 性能优化的思考--性能卡顿不好的原因在哪?

    说到 Android 系统手机,大部分人的印象是用了一段时间就变得有点卡顿,有些程序在运行期间莫名其妙的出现崩溃,打开系统文件夹一看,发现多了很多文件,然后用手机管家 APP 不断地进行清理优化 ,才 ...

  4. 十大技巧优化Android App性能

    无论锤子还是茄子手机的不断冒出,Android系统的手机市场占有率目前来说还是最大的,因此基于Android开发的App数量也是很庞大的. 那么,如何能开发出更高性能的Android App?相信是软 ...

  5. 优化Android App性能?十大技巧必知!

    无论锤子还是茄子手机的不断冒出,Android系统的手机市场占有率目前来说还是最大的,因此基于Android开发的App数量也是很庞大的.那么,如何能开发出更高性能的Android App?相信是软件 ...

  6. 包建强的培训课程(9):Android App性能优化

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  7. android app性能优化大汇总

    这里根据网络上各位大神已经总结的知识内容做一个大汇总,作为记录,方便后续“温故知新”. 性能指标: (1)使用流畅度:  图片处理器每秒刷新的帧数(FPS),可用来指示页面是否平滑的渲染.高的帧率可以 ...

  8. APP 性能分析工作台——你的最佳桌面端性能分析助手

    目前 MARS-App 性能分析工作台版本为开发者提供Fastbot桌面版的服务. 旨在帮助开发者们更快.更便捷地开启智能测试之旅,成倍提升稳定性测试的效率. 作者:字节跳动终端技术--王凯 背景 F ...

  9. Qt qml调试,qml性能分析和优化工具

    QML语言为qt推出的用于界面编程的语言. 1)如何在qt creator中进行调试qml: 以Qt Creator 4.6.2为例: 在qt creator的debug模式下,可以直接在qml中打断 ...

随机推荐

  1. webBrowser 加载网页

    事件 webBrowser_DocumentCompleted private void webBrowser_DocumentCompleted(object sender, WebBrowserD ...

  2. 基于Fragment的百度地图框架的使用

    博客:http://blog.csdn.net/developer_jiangqq (一)基本介绍(Fragment和SupportMapFragment): Fragment的使用现在安卓APP开发 ...

  3. NFS Volume Provider(Part II) - 每天5分钟玩转 OpenStack(63)

    上一节我们将 NFS volume provider 配置就绪,本节将创建 volume. 创建 volume 创建 NFS volume 操作方法与 LVM volume 一样,唯一区别是在 vol ...

  4. Web APi 2.0优点和特点?在Web APi中如何启动Session状态?

    前言 曾几何时,微软基于Web服务技术给出最流行的基于XML且以扩展名为.asmx结尾的Web Service,此服务在.NET Framework中风靡一时同时也被.NET业界同仁所青睐,几年后在此 ...

  5. WebService中使用Aspose.Cells.dll

    首先,目前我是在Json里面使用的,然后关于HTML+WebService+Json怎么使用,可以看看Jsonp跨域的相关例子. 本次的实现原理是:通过HTML传送参数到WebService,然后在W ...

  6. 关于css中pointer-events属性的怪异行为

    在我的记忆中pointer-events就是用来进行事件穿透的,也就是说,如果给父元素设置了pointer-events:none,那么父元素不再监听鼠标事件事件(类似于touch,click这样的) ...

  7. 纯CSS打造忙碌光标

    我们经常会用到忙碌等待的情况,比如ajax等待回调,或者加载页面的时候,我们通常的做法就是放一张loading.gif图片,在那里转呀转. 以前是在pc上用,也没有觉得不妥,如今要在移动端用,问题就来 ...

  8. Icinga使用总结

    1. 在定义命令的时候,往往需要修改commands.cfg配置文件,如果使用NPRE插件监控远程服务器,其实,定义命令也没这么复杂 commands.cfg中有关check_nrpe的命令定义如下: ...

  9. EntityFramework 7 Left Join Where Select 奇怪问题

    这篇博文纪录一下:使用 EF7,当 Linq 查询中使用 "Left Join" 语法(DefaultIfEmpty),Where Select 不同条件语法实现,出现的不同问题. ...

  10. canvas学习(一)

    Canvas 学习之路 (一) canvas 是H5 里面神一样的东西,使得只是通过html和js就能做出非常棒的游戏和画面. 因为对前端无限的爱好,更加对canvas充满好奇,将我学习canvas的 ...