Skip to content

Latest commit

 

History

History
339 lines (235 loc) · 24.4 KB

性能优化相关工具.md

File metadata and controls

339 lines (235 loc) · 24.4 KB

性能优化相关工具 有关性能优化的文章请参考性能优化和布局优化

DDMS(百宝箱) 查看进程的内存使用 DDMS可以让你查看到一个进程使用的堆内存。

在Device了表选择你想查看的进程。 点击Update Heap按钮来开启查看进程堆内存信息。 在Heap页面,点击Cause GC来执行垃圾回收。 在列表中点击一个对象类型来查看它所分配的内存大小。 追踪对象的内存分配情况 在Device了表选择你想查看的进程。 在Allocation Tracker页面,点击Start Tracking按钮来开始追踪。 点击Get Allocations来查看从你点击Start Tracking之后分配内存的对象列表。 你可以再次点击Get Allocations来添加新分配内存的对象列表。 停止追踪或者清除数据可以点击Stop Tracking按钮。 在列表中单独点击一行来查看详细的信息。 使用文件系统 DDMS提供了一个文件浏览器来供你查看、拷贝、删除文件。

在Device页面,选中你想要查看的设备。 从设备中拷贝文件,用文件浏览器选择文件后点击Pull file按钮。 拷贝文件到设备中,点击Push file按钮。 检查线程信息 Thread页面可以显示指定进程中当前正在运行的线程。

在Device页面,选择想要查看的进程。 点击Update Threads按钮。 在Thread页面,你可以查看对应的线程信息。 启动方法分析 方法分析可以追踪一个方法的特定信息,例如调用数量,执行时间、耗时等。如果想要更精确的控制想要收集的数据,可以使用startMethodTracing()和stopMethodTracing()方法。

在你开始使用方法分析之前请知晓如下限制:

在Android 2.1及以前的设备必须有SD卡,并且你的应用必须有写入SD卡的权限。 在Android 2.2及以后的设备中不需要SD卡,trace log文件会直接被导入到开发机上。 开启方法分析:

在Device页面,选择想要开启方法分析的进程。 点击Start Method Profiling按钮。 在Android 4.4及以后,可以选择trace_based profiling或者基本的sample-based profiling选项。在之前的版本智能使用trace-based profiling。 在应用中操作界面来执行你想要分析的方法。 点击Stop Method Profiling按钮。DDMS会停止分析并且将在Start Method Profiling和Stop Method Profiling中间手机的方法分析数据使用Traceview打开。 使用网络流量工具 从Android 4.0开始,DDMS包含了一个网络使用情况的页面来支持跟踪应用的网络请求。

如图:

image。

通过检测数据交互的频繁性以及在每次连接中数据的传递量,你可以知道应用中具体可以做的更好更高效的地方。 想要更好的查看引起数据交互的地方,可以使用TrafficsStats这个API,它也可以使用setThreadStatsTag()方法来设置数据使用情况的tag,

首先要讲到的是Systrace,之前在淘宝面试的时候被问到,如有查找UI绘制卡顿的问题,我没答上来。早点知道Systrace就好了(当然只知道工具是不够的,也要懂得里面的原理)。

Systrace Systrace有什么用呢?官方文档中有一片文章的标题是:使用Systrace进行UI性能分析。

在应用程序开发的过程中,你需要检查用户交互是否平滑流畅,运行在稳定的60fps。如果中间出了些问题,导致某一帧延迟,我们想要解决该问题的第一步就是理解系统如何操作的。

Systrace工具可以让你来手机和观察整个android设备的时间信息,也称为trace。它会显示每个时间点上的CPU消耗图,显示每个线程在显示的内容以及每个进程当时在做的操作。

在使用Systracv来分析应用之前,你需要手机应用的trace log信息。生成的trace能够让你清晰的观察系统在该时间内所做的任何事情。

生成Trace 为了能创建一个trace,你必须要执行如下几个操作。 首先,你要有一个Android 4.1及以上的设备。将该设备设置为debugging,连接你的设备并且安装应用。有些类型的信息,特别是一些硬盘的活动和内核工作队列,需要设备获取root权限才可以。 然而,大多数的Systrace log的数据只需要设备开启开发者debugging就可以了。

Systrace可以通过命令行或者图形化界面的方式来运行,在Studio中打开Android Device Monitor然后选择Systracv图标image。

下面以命令行的方式为例,这里只讲解一下Android 4.3及以上的使用方式:

确保设备已经通过USB连接并且开启了debugging模式。 设置一些参数并执行trace命令,例如: $ cd android-sdk/platform-tools/systrace $ python systrace.py --time=10 -o mynewtrace.html sched gfx view wm 执行完上面的命令后就会在sdk/platform-tools/systrace/mynewtrace.html生成对应的html文件。

在设备上执行一些你想要trace的过程。 悲剧了,生成了对应的html文件,但是我死活打不开。打开是白屏,折腾了我半天,最后终于找到了解决方法:

Firstly, if anyone is using Chrome v50.0+ on OS X, just try this please.

open chrome browser and go to "chrome://tracing" in the tracing page, click load and select the systrace generated html file. Secondly, I think it's a bug which is confirmed by Google.

OK了。

image。

看不懂!!!

分析Trace 用浏览器打开上面生成的mynewtrace.html。 下面为了能明显的说明,我就用官网提供的例子来说明了。

查看Frames 从打开的文件中我们能看到每个应用都有一行frame的圆圈来标示渲染的帧,通常都是绿色的。黄色或者红的圆圈标示超过了我们对保障60fps所需的16毫秒绘制时间。。 可以在文件上面按w键来放大文件以便能更好的观看查找。

***提示:***在文件上面按?键可以查看对应的快捷键。

image。

在Android 5.0以上的设备上,显示工作呗分为UI Thread和Render Thread。在之前的版本,所有工作都是在UI Thread进行的。 点击某个单独的frame图标来查看它们所需的时间。

观看Alerts Systracv会自动分析trace过程的时间,并且通过alerts提示该展现问题,以及建议如何处理。

image

如上图所示,在你点击一个比较慢的frame时,下面就会显示一个alert。在这种情况下,它直说可能是ListView服用以及重复渲染的问题。 如果你发现UI Thread做了太多的工作,你可以使用TraceView进行代码分析,来找到具体哪些操作导致了消耗时间。

如下,你也可以通过点击窗口右边的Alerts来查看当前所有的alert。这样会直接展开Alert窗口。

image

下面我们以另外一个例子来分析一下它的Frame:

我们点击某一红色frame来查看这一阵的一些相关警告: image 可以看到这里说耗时32毫秒,这已经远远超过了16毫秒的绘制时间。 我们可以通过Description来查看描述内容。 下面是另外一个渲染过慢的例子:

image 从上图,我们发现了Scheduling delay的警告,加起来能看到总的绘制时间大约是19毫秒。 Scheduling delay的意思是调度延迟,也就是说一个县城在处理这部分操作时,在很长时间内没有被分配到CPU上面进行运算,这样就导致了很长时间内没有完成操作。 我们选择这一帧中最长的一块,来观察下: image

我们在上图看到了Wall duration,他代表着这一块开始到结束的总耗时。而在CPU Duration这里显示了CPU在处理该部分消耗的时间。 很明显,真个区域用了18毫秒,但是CPU实际处理只用了4毫秒,也就是说剩下的14毫秒就可能有问题了。 image

可以看到,4个线程都比较忙。

选择其中一个线程来查看是哪个应用在使用它,这里看到了一个包名为com.udinic.keepbusyapp的应用。也就是说由于另外一个应用占用了CPU,,导致了我们的应用未能获取到足够的CPU资源。

追踪代码 在Android 4.3以上你可以使用Trace类来在代码中添加(很熟悉有木有,在看源码的时候经常看到)。这样能查看到此时你的应用线程都做了哪些操作。 下面的代码展示了如何使用Trace类来追踪两个代码块部分:

public void ProcessPeople() { Trace.beginSection("ProcessPeople"); try { Trace.beginSection("Processing Jane"); try { // code for Jane task... } finally { Trace.endSection(); // ends "Processing Jane" }

    Trace.beginSection("Processing John");
    try {
        // code for John task...
    } finally {
        Trace.endSection(); // ends "Processing John"
    }
} finally {
    Trace.endSection(); // ends "ProcessPeople"
}

} Traceview Traceview是一个性能测试工具,展示了所有方法的运行时间。

Traceview is a graphical viewer for execution logs that you create by using the Debug class to log tracing information in your code. Traceview can help you debug your application and profile its performance.

创建Trace文件 想要使用Traceview你需要创建一个包含你想分析部分的trace信息的log文件。 有两种方式来生成trace logs:

在你测试的类中添加startMethodTracing()和stopMethodTracing()的代码,来指定开始和结束获取trace信息。这种方式是非常精确的,因为你可以在代码中指定开始和结束的位置。

使用DDMS中的方法来生成。这种方式就不太精确。虽然这种方式不能精确的指定起始和结束位置,但是如果在你无法修改源代码或者不需要精确时间的时候是非常有用的。

在开始生成trace log信息时,你需要知道如下的限制条件:

如果你在测试代码中使用,你的应用必须要用WRITE_EXTERNAL_STORAGE的权限。

如果你使用DDMS生成:

Android 2.1之前必须有SD卡,并且你的应用也要有写入SD卡的权限。 Android 2.2之后不需要SD卡,trace log文件会直接生成到你的开发机上。 在测试代码中调用startMethodTracing()方法的时候,你可以指定系统生成trace文件的名字。结束的时候调用stopMethodTracing()方法。这些方法开始和结束时贯穿整个虚拟中的。例如你可以在你activity的onCreate()方法中调用startMethodTracing()方法,然后在activity的onDestroy()方法中调用stopMethodTracing()方法。

// start tracing to "/sdcard/calc.trace"
Debug.startMethodTracing("calc");
// ...
// stop tracing
Debug.stopMethodTracing();

在调用startMethodTracing()方法的时候,系统创建一个名为.trace的文件。它包含方法的二进制trace数据和一个线程与方法名的对应集合。 然后系统就开始生成trace数据,直到调用stopMethodTracing()方法。如果在你调用stopMethodTracing()方法之前系统已经达到了最大的缓冲大小,系统就会停止trace并且在控制台发出一个通知。

拷贝Trace文件到电脑上 在模拟器或者机器上生成.trace文件后,你需要拷贝他们到你的电脑上,你可以使用adb pull命令来拷贝: adb pull /sdcard/calc.trace /tmp

在Traceview中查看trace文件 运行Traceview并且查看trace文件:

打开Android Device Monitor。 在Android Device Monitor的状态栏中点击DDMS并且选择一个进程。 点击Start Method Profiling图标开始查看。 在查看完后点击Stop Method Profiling图标来显示traceview。 大体的样子如下:

image

Traceview Layout 如果你有一个trace log文件(通过添加tracing代码或者用DDMS生成),你可以把该文件加载到Traceview中,这将会把log数据显示为两部分:

timeline panel-展示了每个线程和方法的起始和结束 profile panel-提供了一个方法中的执行内容的简述 Timeline Panel 每个线程都会以时间从左往右递增的方式在单独的一行中显示它的执行情况,

image

Profile Panel 显示了一个方法所消耗的时间的概要情况。在表格中会同时显示inclusive和exclusive的时间。Exclusive的时间是该方法所消耗的时间。InClusive的时间是该方法消耗的时间加上任何调的方法所消耗的时间。我们简单的将调用的方法叫做parents,被调用的方法叫做children。如果一个方法被调用,它会显示对应的parents和children。parents会显示一个紫色的背景,children会显示一个黄色的背景。最后一列显示了该方法所调用的总数的调用数。在下面的图中我们能看到一共有14个地方调用了LoadListener.nativeFinished() .

image

Name 方法名 Inclusive CPU Time, CPU在处理该方法以及所有子方法(被它调用的所有方法)所消耗的时间。 Exlusive CPU Time, CPU在处理该方法的耗时。 Inclusive/Exclusive Real Time, 从方法开始执行到执行结束的耗时。 Cal+Rec, 这个方法被调用的次数,以及递归被调用的次数。 CPU/Real time per Call, 在处理这个方法时的CPU耗时的平均值。 image

上图中getView方法被调用了12次,每次CPU消耗2.8秒,但是每次调用的总耗时是162秒,这里肯定有问题。 而看看这个方法的children,我们可以看到这其中的每个方法在耗时方面是如何分布的。Thread.join()方法战局了98%的inclusive real time。这个方法在等待另一个线程结束的时候被调用。在Children中另外一个方法就是Tread.start()方法,而之所以整个方法耗时很长,我猜测是因为在getView()方法中启动了线程并且在等待它的结束。

但是这个线程在哪儿?

我们在getView()方法中并不能看到这个线程做了什么,因为这段逻辑不在getView()方法之中。于是我找到了Thread.run()方法,就是在线程被创建出来时候所运行的方法。而跟随这个方法一路向下,我找到了问题的元凶。

image

我发现了BgService.doWork()方法的每次调用花费了将近14毫秒,并且有四十个这东西!而且getView()中还有可能调用多次这个方法,这就解释了为什么getView()方法执行时间如此之长。这个方法让CPU长时间的保持在了繁忙状态。而看看Exclusive CPU time,我们可以看到他占据了80%的CPU时间!此外,根据Exclusive CPU time排序,可以帮我们更好的定位那些耗时很长的方法,而他们很有可能就是造成性能问题的罪魁祸首。

关注这些耗时方法,例如getView(),View#onDraw()等方法,可以很好的帮助我们寻找为什么应用运行缓慢的原因。但有些时候,还会有一些其他的东西来占用宝贵的CPU资源,而这些资源如果被运用在UI的绘制上,也许我们的应用会更加流畅。Garbage Collector垃圾回收机制会不时的运行,回收那些没用的对象,通常来讲这不会影响我们在前台运行的程序。但如果GC被运行的过于频繁,他同样可以影响我们应用的执行效率。而我们该如何知道回收的是否过于频繁了呢…

Monitors 在Android Studio中下方的Android Monitor中可以看到Monitors工具栏,它能不断的去检测内存、网络、CPU的消耗情况。

image

我们可以直接点击Dump Java Heap或者Call GC等按钮,以便更好的去观察内存的使用情况。点击后会生成一个.hprof的文件。生成后直接使用Studio打开.hprof文件即可。

说到这里插一嘴,有关Java垃圾回收机制请参考

image 在左边能看到所有堆内存中的实例。后面会显示他所占用的内存大小。

对于内存泄漏的分析可以使用MAT或者LeakCanary来进行。这里就不仔细说了。

不同虚拟机的内存管理 Android Monitor使用的Virtual Machine(VM):

Android 4.3(API Level 18)及以前的版本使用Dalvik VM . Android 4.4(API Level 19)默认使用Dalvik VM,Android RunTime(ART)是可选的。 Android 4.3(API Level 18)及更高的版本使用ART VM. 虚拟之执行垃圾回收。Dalvik虚拟机使用一个标记和清除垃圾收集方案。ART虚拟机使用分代收集算法与标记和清楚手机算法相结合的方式。 Logcat会显示一些垃圾回收相关的信息。

GPU使用情况 上面也显示了GPU的使用情况,这里要说一句,如果想要显示它,必须要在手机的开发者中心中开启GPU显示配置文件选项,将其设置为显示与adb shell dumpsys gfxinfo。然后再点击Studio中的按钮重新开始就可以看到了。 每一条线意味着一帧被绘制出来了。而线的颜色又代表不同的阶段:

Draw(蓝色)代表着View.onDraw()方法。如果这个值很高就说明可能是该View比较复杂。 在这个环节会创建/刷新DisplayList中的对象,这些对象在后面会被转换成GPU可以明白的OpenGL命令。而这个值比较高可能是因为view比较复杂,需要更多的时间去创建他们的display list,或者是因为有太多的view在很短的时间内被创建。

Prepare(紫色),从Android 6.0开始,一个新的线程被引进来帮助UI线程进行绘制。 这个线程叫做Render Thread。它负责转换它负责转换display list到OpenGL命令并且送至GPU。在这过程中,UI线程可以继续开始处理后面的帧。而在UI线程将所有资源传递给RenderThread过程中所消耗的时间,就是紫色阶段所消耗的时间。如果在这过程中有很多的资源都要进行传递,display list会变得过多过于沉重,从而导致在这一阶段过长的耗时。

Process(红色) 执行Display list中的内容并创建OpenGL命令。如果有过多或者过于复杂的display list需要执行的话,那么这阶段会消耗较长的时间,因为这样的话会有很多的view被重绘。而重绘往往发生在界面的刷新或是被移动出了被覆盖的区域。

Execute (黄色) – 发送OpenGL命令到GPU。这个阶段是一个阻塞调用,因为CPU在这里只会发送一个含有一些OpenGL命令的缓冲区给GPU,并且等待GPU返回空的缓冲区以便再次传递下一帧的OpenGL命令。而这些缓冲区的总量是一定的,如果GPU太过于繁忙,那么CPU则会去等待下一个空缓冲区。所以,如果我们看到这一阶段耗时比较长,那可能是因为GPU过于繁忙的绘制UI,而造成这个的原因则可能是在短时间内绘制了过于复杂的view。

Measure/Layout(绿色) 代表Measure和Layout的时间。

Hierarchy Viewer 布局分析工具,非常常用。

在Android Device Monitor中打开Hierarchy Viewer即可。

连接你的手机或者模拟器。 出于安全性考虑,Hierarchy Viewer只能连接开发者版的系统的手机。 运行程序,并且让界面显示出来。 启动hierarchy view工具,接着就能看到左边栏显示出了对应的设备,展开后可以看到一些组件的名称。这个页面包含了应用的界面以及系统的界面。选择你的应用中想要查看的界面直接双击就可以了。 image 如果你的界面显示的不是这个样子,少一些东西的话,你可以使用Window>Reset Perspective来重置样式。

但是我并打不开。 如果你的手机是Android 4.1及以上版本你必须要在你的电脑上设置一个ANDROID_HVPROTODE 环境变量才可以。

Windows 增加一个名为ANDROID_HVPROTO值为ddm的环境变量就可以了。 Mac 打开.bash_profile touch .bash_profile创建 open -e .bash_profile打开 添加 #Hierarchy Viewer Variable
export ANDROID_HVPROTO=ddm source .bash_profile 如果配置完成后仍然不能用的话,你可以:

关闭Android Studio. 执行add kill-server,然后adb start-server. 通过命令行开始hierarchyviewer. 好了,我们直接打开自己的页面进行查看布局吧(有点慢)。 它是介个样子滴 : image

我们可以看到所有结构,点击对一个的节点,能直接看到该界面的UI效果,并展示该布局包含多少个View,以及该布局measure,draw,layout所消耗的时间。选中Show Extras选项可以看到在右下角看到界面效果。

选中要查看的节点后点击Profile Node选项,可以看到如下界面:

image

可以看到每个布局都出现了三个点。有不同的颜色,从左到右这三个点分别表示:

左边的点代表Draw阶段。 中间的点代表了Layout阶段。 右边的点代表了Execute阶段。 这三个点有分别对应了pipeline中的不同阶段,如下图: image

不同的颜色代表不同的性能:

绿色代表渲染的非常快,至少是其他View的一半。 黄色代表View渲染比最后那一半的View快。 红色代表View渲染是几乎是在最慢的那部分中间。 Hierarchy Viewer测量的是相对的表现能力,所以总会有一个红色的节点,它并不意味者该View一定是绘制的太慢。

过度绘制 在开发者选项中将调试GPU过度绘制设置为显示过度绘制区域,就能看到程序的绘制情况。 过度绘制往往发生在我们需要在一个东西上面绘制另外一个东西,例如在一个红色的背景上画一个黄色的按钮。那么GPU就需要先画出红色背景,再在他上面绘制黄色按钮,此时过度绘制就是不可避免的了。如果我们有太多层需要绘制,那么则会过度的占用GPU导致我们每帧消耗的时间超过16毫秒。 这些过度绘制可能发生在我们给Activity或Fragment设置了全屏的背景,同时又给ListView以及ListView的条目设置了背景色。而通过只设置一次背景色即可解决这样的问题。

注意:默认的主题会为你指定一个默认的全屏背景色,如果你的activity又一个不透平的背景盖住了默认的背景色,那么你可以移除主题默认的背景色,这样也会移除一层的过度绘制。这可以通过配置主题配置或是通过代码的方法,在onCreate()方法中调用getWindow().setBackgroundDrawable(null)方法来实现。 image

Hardware Acceleration 在Honeycomb版本中引入了硬件加速(Hardware Accleration)后,我们的应用在绘制的时候就有了全新的绘制模型。它引入了DisplayList结构,用来记录View的绘制命令,以便更快的进行渲染。但还有一些很好的功能开发者们往往会忽略或者使用不当——View layers。

使用View layers(硬件层),我们可以将view渲染入一个非屏幕区域缓冲区(off-screen buffer,前面透明度部分提到过),并且根据我们的需求来操控它。这个功能主要是针对动画,因为它能让复杂的动画效果更加的流畅。而不使用硬件层的话,View会在动画属性(例如coordinate, scale, alpha值等)改变之后进行一次刷新。而对于相对复杂的view,这一次刷新又会连带它所有的子view进行刷新,并各自重新绘制,相当的耗费性能。使用View layers,通过调用硬件层,GPU直接为我们的view创建一个结构,并且不会造成view的刷新。而我们可以在避免刷新的情况下对这个结构进行进行很多种的操作,例如x/y位置变换,旋转,透明度等等。总之,这意味着我们可以对一个让一个复杂view执行动画的同时,又不会刷新!这会让动画看起来更加的流畅。 在阅读了ViewPager的源码后,我发现了在滑动的时候会自动为左右两页启动一个硬件层,并且在滑动结束后移除掉。

在两页间滑动的时候创建硬件层也是可以理解的,但对我来说小有不幸。通常来讲加入硬件层是为了让ViewPager的滑动更加流畅,毕竟它们相对复杂。

是的,但是再使用硬件layers的时候还是有几点要牢记在心:

回收 – 硬件层会占用GPU中的一块内存。只在必要的时候使用他们,比如动画,并且事后注意回收。例如在上面ObjectAnimator的例子中,我们增加了一个动画结束监听以便在动画结束后可以移除硬件层。而在Property animator的例子中,我们使用了withLayers(),这会在动画开始时候自动创建硬件层并且在结束的时候自动移除。 如果你在调用了硬件View layers后改变了View,那么会造成硬件硬件层的刷新并且再次重头渲染一遍view到非屏幕区域缓存中。这种情况通常发生在我们使用了硬件层暂时还不支持的属性(目前为止,硬件层只针对以下几种属性做了优化:otation、scale、x/y、translation、pivot和alpha)。例如,如果你另一个view执行动画,并且使用硬件层,在屏幕滑动他们的同时改变他的背景颜色,这就会造成硬件层的持续刷新。而以硬件层的持续刷新所造成的性能消耗来说,可能让它在这里的使用变得并不那么值。 有关如何使用Hardware Layer请参考之前写的文章:通过Hardware Layer提高动画性能

邮箱 :charon.chui@gmail.com Good Luck!