前提
最近有两周时间在忙其它的事情,还好事情已经忙完,可以再来继续写我的博客了。写博客这件事情一定要坚持,我坚信,当博客达到一定数量,不管是你的技术,还是你的表达都会有一个大的提升。好了,继续我的博客之旅。今天写的是有关Android
性能优化及使用一些常用工具来解决性能方面的问题。让程序更优化的技术-性能优化。
1.布局优化
布局是Android
中最重要也是最基础的部分,布局的优化是非常重要的,好的布局,能够使你的App
更加高效、顺畅的运行。布局优化主要从Include
、merge
、viewStub
关键布局的使用及减少视图树层级
四部分来进行优化。
1.1 Include优化
Include
的优化主要关注的是代码结构方面的优化,可能并不能给app
性能带来多大提升,但它可以使我们的代码更加精炼、简洁;达到代码复用的目的。
常用的地方就是我们每个页面都会有一个标题栏,标题栏上有返回按钮,每个页面都需要同样的显示。这个时候我们就可以抽出一个通用的标题栏,然后通过include
导致到具体的页面。达到让多个页面使用同一标题栏来减少重复编码的效果。
常见的形式:这里需要注意的地方有两个,第一个:如果include
这里设置了id
,那么这个id
就会代替我们layout
所引用的根布局的id
在代码中使用。第二个就是这里使用的是layout
关键字而不是android:layout
,而后面要讲的ViewStub
使用的是android:layout
。
Include原理Include
的原理非常简单,就是在系统解析布局xml文件时,解析到include
标签,就将include
包含的根布局直接添加到其父布局。我们系统里所有的xml布局都是通过LayoutInflate
的inflate
函数来进行解析的。而inflate()
又调用了rInflate()
函数来进行解析的。我们来看看rInflate
都做了些什么吧。在rInflate()
方法里,对include
标签进行了判断,然后对使用了include
的部分通过parseInclude()
方法来进行对include
内的布局进行解析。
1.2 Merge标签
merge
标签可以帮助我们减少布局中的视图树的层级,这是一个布局优化很好用的一个标签,通过减少视图树的层级来减少视图渲染所使用的时间,从而使app
更加流畅。
在Android构成的基石这篇文章中,我们了解到,我们Activity
其实是有一个根视图,如果我们不通过setContentView
来设置当前Activity
的布局,直接运行,也是能看到一个只包含标题的一个空Activity
显示的。我们的系统默认为Activity
设置了一个根视图,它的文件名是screen_title.xml
文件。看到这里,也就明白了,为什么说Activity
默认的布局是FrameLayout
了。
现在我给一个Activity
设置了一个简单的只包含一个按钮的布局。此时我们通过hierarchy viewer
来分析一下在分析之前我先说一名题外话,怎么mac上的hierarchy viewer
变成了这个鬼,还是因为studio
升级到2.1
的原因,回头我去查下资料。
从图上可以看出有两个重复的FrameLayout
布局。最重要的是,去掉一个布局似乎不影响布局的展示。这时我们的Merge
就派上用场了,Merge
就是为了解决这种重复布局的。此时把设置给Activity
的根布局设置成Merge
再来看看效果。从图上可以看出,显示结果是一样的,而我们的视图树里就直接少了一层了。视图层级越少,就意味着系统对布局视图的渲染更快。
Merge注意的地方Merge
一般使用在两个Layout
布局,有一个布局需要添加到另一个布局时,就将要被添加的子布局的根布局设置成Merge
(如果子view要插入父view的直接父布局和子view是一样的),而在同一布局中是不能使用的,同一布局里,子view和父view使用相同的标签,如果把子view的标签改成Merge
那么会改变其布局的展示。
1.3 ViewStub标签
ViewStub
也是我们在布局时,必不可少的一个用来优化布局的标签。 它的作用是将使用了ViewStub
标签的布局,在加载布局文件时,只是添加一个标记,而不会真正的加载viewStub
所引用的布局,直到被设置为可见或者调用inflate()
方法,才去加载那部分的视图,达到加快布局加载速度和节约系统资源的目的。
ViewStub
的使用方式和include
很相似,都是将某个独立的布局引用到当前布局,不同的是include
是通过layout
属性来引用布局的,而ViewStub
是通过android:layout
来引用布局的。我用一个小例子来说明一下。上面是我通过viewStub
设置的布局。为了验证当布局在加载过程中,被ViewStub
引用进来的子view有没有被加载?我在Activity
在没有设置ViewStub
显示或者inflate()
的时候去使用findViewById
去找我们的子view中的TextView
.然后为TextView
设置文本显示。上面是我们的设置,然后运行看看有什么结果。这里直接就报了NullPointException
,说明子View
里的TextView还没有被加载到内存,系统找不到此资源。也就论证了我们前面所说的,当ViewStub
没有被设置为可见或者inflate()
时,被ViewStub
引用到布局的资源是不会被加载到内存的。
个人觉得ViewStub
是对布局优化帮助很大的一个标签。对一些不需要一开始就加载的布局,使用ViewStub
,是非常棒的一个选择。
1.4减少视图树层级
因为每一个视图都要经过测量,布局,绘制的过程,如果视图树的层级越少,就能相应的减少布局的测量、布局和绘制,从而提高UI
的流畅度。就是在能够实现布局效果的前提下,尽量减少视图的层级。这里没什么好说的,有几点原则,大家注意一下吧。
1.尽量使用
RelativeLayout
2.在ListView
的item view
布局尽量不要使用LinearLayout
的weight
来进行布局。因为使用weight
都会导致view
被绘制两次。
3.将可以复用的布局进行抽取,通过来进行引用
4.使用来对一些不常显示的布局进行优化。
5.使用来减少不冗余的层级
2.内存优化
内存优化是App
开发中非常重要的一个环节,一般Android
系统给每个App
分配的内存大小是有限制的,一般限制为192M
,也就是一个应用程序的最大内存占用为192M
,如果超出这个大小,就会导致out of memory
内存溢出。所以做好内存优化是非常必要的。我们常用的内存优化有以下几点:
2.1 不要滥用service
如果App
内使用了Service
,除非它触发了一个任务,否则,其它时候它都是处于非运行状态,但由于系统对Service
的回收策略,导致没有在运行的Service
仍然占用着系统的内存资源,而且,当Service
停止失败会引起内存泄漏。除非是你有经常需要在后台运行的任务需要使用到Service
不然,不要轻易去使用它,一般推荐使用IntentService
来代替Service
来完成相应的任务。IntentService
会在任务执行完成后自动去结束自己,而让其占用的资源通过GC回收。
2.2 当UI隐藏时释放内存
这里说的是一个进程级别的概念,并不是单个App
某个页面的隐藏。当我们在多个App
之间切换时,运行在后台的App
,在Activity
里覆写onTrimMemory
方法,并在这个方法里监听来自TRIM_MEMORY_UI_HIDDEN
级别的回调。在这个级别的回调里可以做一些回收资源的操作。当App
处于后台之后,每个覆写了onTrimMemory
的方法都做被回调。这里的UI隐藏,不同于Activity
里的onStop
方法,onStop
是当前页面级别的回调,也就是这个app还处于前台运行,而要在当前页面打开一个新的页面,使得当前当前页面不可见,而被调用此方法;而onTrimMemory
是App
级别的回调,也就是只有当当前App
处于后台运行时,才会被调用此方法。
2.3 当内存紧张时释放部分内存
这里所说的内存也是系统内存,而不是单个App
被限制的内存上限。同样的,这里也是要在Activity
里来监听onTrimMemory
方法,来根据不同的级别来进行相应App
资源的回收,以保证系统的正常稳定运行。这里的提示级别有以下几种:
1.TRIM_MEMORY_COMPLETE:系统内存不足,并且该进程在后台进程列表最后一个,也就是马上会被GC清理。
2.TRIM_MEMORY_MODERATE:系统内存不足,并且该进程在后台进程列表的中部位置
3.TRIM_MEMORY_BACKGROUND:系统内存不足,并且该进程是后台进程
4.TRIM_MEMORY_UI_HIDDEN:代表当前进程的UI已经被隐藏,也就是当前App
已经处于后台进程。
5.TRIM_MEMORY_RUNNING_CRITICAL:你的app仍在运行,但是系统已经把LRU Cache中的大多数进程已经杀死,因此,必须立即释放非必须的资源,如果系统不能回收到足够的RAM大小,那么就会将LRU Cache
列表里的所有进程杀死。
6.TRIM_MEMORY_RUNNING_LOW:你的app正在运行且没有被列为可杀死的。但是,设备正运行更在低内存的状态下,你应该释放一些资源。
7.TRIM_MEMORY_RUNNING_MODERATE:你的app正在运行且没有被列为杀死的。但是,设备正在运行在低内存状态下,系统开始触发LRU Cache
中的Process
回收机制。
2.4 检查你应用使用的内存
每个系统版本或者每个厂商都会根据memory的大小对App
的内存大小限制进行设置,每个设备对单个App
的大小限制是不太一样的。好像在6.0之前,我试过的几部手机的内存大小限制都是192M
,而在三星S6
上测试的内存大小限制为256M
。可能通过getMemoryClass
来获得当前设备所分配的单个App
内存大小的限制。当前为了满足有些大内存应用的操作,Android
还是开了后门的,可以在Mainfest
文件设置此属性,可以让应用程序冲破内存大小的限制。具体为多大,可以通过getLargeMemoryClass
来获得。三星S6
的为512M
,也就是最大内存分配比内存大小限制大了一倍。当然,除非必要,不然,不要轻易设置此参数,而应该把心思花到内存优化上。
2.5 避免Bitmap的浪费
Bitmap
占用内存的计算方法为:图片长度 图片宽度 单位像素占用的字节数。比如一张图片的分辨率为1110x568,那么它所占用的内存大小为1110x568x4,单位字节B,总共为2521920B.约2.4M。如果图片的分辨率越高所占内存越大。当图片的分辨率大于设备的分辨率时,此时,就需要对图片进行缩放处理,这里可以使用BitmapFactory.Options
的inSampleSize
来进行比例的缩放操作。当然在使用inSampleSize
之前,要先使用inJustDecodeBounds=true
来先加载图片的信息,得到相应长和宽后,再和设备的分辨率来作一个比较,最后得到比例,将比例设置给inSampleSize
,之后,再使用inJustDecodeBounds=false
让BitmapFactory
去加载完整的图片。这样就成功的把图片进行了缩放,减少了图片占用内存的大小。
2.6 使用优化的数据容器
像我们经常使用的HashMap
是我们Java
下的Api
,而它是比较消耗内存的,对此,在Android FrameWork
提供了一些优化后的容器,比如SpareArray
和ArrayMap
就是Android
下优化过的HashMap
,使用它们可以减少存储时的内存消耗,更节省资源。
2.7 请注意内存的开销
这里主要说的就是在开发过程中关注一下使用的库的开销。比如:使用Enums
的开销是使用static contants
的2倍。每一个类(包括匿名内部类)都会使用大概500bytes。每一个类的实例产生的开销是12-16Bytes。往HashMap
添加一个entry
,除了数据本身的内存占用外,还会多出一个额外占用32bytes的entry对象。
2.8 请注意代码的抽象
这里说的是,虽然抽象能够提升代码的灵活性与可维护性,但抽象也会带来一个显著的开销。通常需要同等量的代码被Mapping
到内存中。如果你在代码里使用的抽象没有显著的提高效率,请慎用。
2.9 为序列化数据使用nano protobufs
这里也没有什么好说的,nano protobufs
是一个库,它提供了对数据序列化和反序列化的支持。Protobuf Nano
内部封装了Parcelable
接口,也就是使用Nano
处理过的对象是已经实现了Parcelable
的,可以直接在Intent和进程间传递这些对象。
2.10 尽量避免使用依赖注入框架
这里所说的就是注解框架的实现是比较消耗内存的,为了搜寻代码中的注解,通常都需要花费很长的初始化过程,在这个过程中可能会把一些用不到的对象都加载到内存中,而这些用不到的对象会一直占用着内存,可能需要很长的时间才会的释放。
2.11 谨慎使用外部库
尽量使用一些网上比较流行的库,因为流行的库一般都是那个时候各方面优化都做得比较到位的库,或者是更新速度会比较快的,即使有问题,也就很快得到解决。还有就是不要为了一个小的功能实现去导入一个很大库,你可以选择将库里的一部分抽取出来用于功能实现或者干脆自己实现。
2.12 优化整体性能
这里主要推荐Google
官方的两篇关于优化的文章 Best Practices for Performance 和optimizing your UI
2.13 使用ProGuard来剔除不需要的代码
ProGuard
能够通过移除不需要的代码,重命名类,域和方法等来对代码压缩、优化、和混淆。需要系统学习一下ProGuard
的一些规则和方法。
2.14 对最终的APK使用zipalign
Android SDK中包含一个zipalign
的工具,它能对打包的应用程序进行优化。我们知道APK其实就是一个Zip
压缩文件,通过Zipalign
优化后可以使你的app运行更快。从原理上来讲就是通过格式化Zip
文件夹中的二进制文件的序列,达到提升系统解析速度。就像我们在阅读代码的过程中先格式化一遍代码,会让我们更容易理解其含义一样。ZipAlign
在sdk/tool/build-tools/xxx(这里任意版本都可)/zipalign(windows 为zipalign.exe).
使用方法
1.先通过命令行工具进入到
zipalign
所在目录
2.zipalign -v4 需要优化的apk绝对路径 优化后的apk绝对路径
3.优化完成后,检查优化是否成功?zipalign -c -v4 优化后的apk绝对路径
2.15 使用多进程
如果合适的话,有一个更高级的技术可以帮助你的app管理内存使用:通过把app组件切分成多个组件,运行在不同的进程中。当然这个技术必须慎用,大多数应用都不需要运行在多进程中。因为如果使用不当,会增加内存的使用,而不是减少。当你的应用程序需要在后台运行与前台一样的大量任务时,可以使用此技术。比较典型的例子就是音乐播放器。
好了,到这里有关内存优化的部分就完成了,这里的优化方式,都是Google
官方推荐的优化方式,这里只是进行一个cp然后手打而已。
3.内存泄露
内存泄露就是已经执行完不需要的对象还存在于内存中,GC没有办法进行回收,造成内存被无用的对象所占用,而导致的内存浪费。这里要简单的说一下GC的回收机制。
GC认定可回收对象原理
gc认定对象是否可回收,有两种方式。在Java
早期版本中,使用的是计数器法来判定对象是否可回收,当对象被引用一次,计数器+1,而iqvv对象引用被移除相应的计数器-1,如果计数器为0,说明此对象没有被引用,就会判定为垃圾对象,标记后让gc执行回收。而这种方式有个孤岛问题
,也就是垃圾对象之间的相互引用,这种情况gc会认为这些对象还有被使用,因为还存在被其它对象的引用。而其实这些对象只是相互被引用。
由于这个问题,后期的Java
版本已经进行了修复,使用了根搜索法来判定对象是否可回收。根探索法的原理是从GC Roots(每种具体的实现对GC Roots的定义不同)作为起点,向下搜索它们的引用对象,可以生成一颗树,树的节点表示可达对象,反之视为不可达。不可达的就会被gc判定为垃圾对象,然后被GC回收。
3.1 Memory Monitor
Android Studio
提供的内存监控工具。如果此处为disable的话,需要开启adb Integration
或者重新插拔数据线。
图上灰色表示已分配未使用内存,而蓝色表示已分配已使用内存。这里说明一下,每个app虽说有内存大小的限制,但是并不是一次系统就把所有的内存分配给了app,而是在不断的变化中动态的去增加内存的分配,直到达到单个app内存大小的限制。
调试方法
花一些时间与app进行交互,并关注内存的变化情况。free
未使用部分的内存会慢慢变小,直到没有内存可用,这时就会触发GC,回收一些无用的对象,如果gc触发之后还是不够用,这时,就会为app增加内存。当你在短时间内发现内存的使用快速增长或者gc变得非常频繁,此时,就需要加倍小心了,很有可能已经发生了内存泄露。当然这个时候也需要有你的判断,也就是当前页面是否需要这么大的一个内存。
如果看到内存的占用不断增加,这时系统会为app不断的分配新的内存,直到达到单个app内存大小限制而oom。如果碰到这种情况,说明你的app在内存分配使用情况上犯了严重的错误。
除了Memory Monitor
之外,在DDMS
工具集里还有个Heap
的工具也可以用来观察内存的开销变化。通过在app的操作以及手动的调用Cause GC
来回收资源,从而来观察其相应页面的内存回收情况,以此判定是否存在内存泄露。
2.2 Leak Canary
这个工具,我相信大部分人都听过了的,这是一个非常好使的检查内存泄露的工具。使用起来非常简单,但是非常有用。此工具是由Android
下的开源巨头square
公司开源的。
作用LeakCanary
会自动完成内存追踪、检测、分析、输出结果,甚至会在OOM
发生之前,就把内存泄露日志报告给你,避免了你手动分析内存的工作。通过会通过通知栏,或者logcat来通知用户相关的内存泄漏信息,相应的引用,哪里造成的泄露都一清二楚。
使用方法
先通过gradle
导入依赖库然后,再通过在全局Application
的onCreate
方法里来进行初始化工作。此时就可以开始监听app内部的内存泄露了,如果出现内存泄露,会通过通知栏和logcat来通知你。但是这里需要注意的是,默认的它只会监听Activity
的内存泄露。如果需要观察其它对象,需要拿到RefWatcher
对象后,来对特定对象进行监听。
比如我想要监听下面的info
对象是否会发生内存泄露
LeakCanary原理LeakCanary
是通过API 14后在全局Application
里增加了对Activity
生命周期的回调来进行对Activity
生命周期的监控的。 这是一个全局的回调,App
里所有的Activity
的相应生命周期的执行都会回调到这里。在Application
里可以监听这个回调。默认情况下LeakCanary
会监控所有Activitys
的生命周期。并且在Activity
调用onDestory
后将此Activity
对象添加到内存泄漏监控队列。也就是在RefWatcher.watch()
中创建一个KeyedWeakRefrence
到被监控的对象。接下来,在后台线程中观察这个弱引用是否被清除。将Heap
内存dump
到一个.hprof
文件里并存储到手机内存里。HeapAnalyzerService
在另外一个进程中启动,使用HeapAnalyzer
来解析内存。通过HAHA这个项目来HeapAnalayzer
计算出到GC Roots
最短强引用来判断是否存在泄露,然后建立导致泄露的引用链。结果被回传到应用程序进程的DisplayLeakService
中,然后输出log并发出通知。
如果是API 14也就是4.0之前,是没有ActivityLifecleCallbacks
回调的,那么用户必须像前面监控info
对象一相,来对Activity
来进行手动监控。
4.性能优化
4.1过度绘制
1.问题描述
设计出漂亮的界面只是开发过程中的一部分,还需要确保用户界面可以流畅的运行。一个常见的问题就是用户界面常常会卡顿,这里有一个可能的原因为OverDraw
(过度绘制)。过度绘制屏幕上的某个像素在同一幀的时间内被绘制了多次。
比如:一个具有蓝色的背景TextView
,它不仅会蓝色绘制用户可见的区域,而是绘制整个蓝色的背景以及TextView
上的文字。这就导致了一些像素被会被绘制两次,这就是过度绘制。
当然,像上面的过度绘制是不可避免的,但是还有很多的情况是可以进行优化的。当你的App
性能出现问题时,通常可以先从过度绘制来进行分析。
2.检测过度绘制工具
这里我们使用的是Android
系统自带的开发工具调度GPU过度渲染
将其打开,选择显示过度渲染区域
。 打开之后,就可以看到App
的每个地方都有用不同的颜色来做标识。这时的颜色分为四种,分别代表重绘的次数。
1.没颜色:代表没有过度绘制
2.浅紫色:代表过度绘制一次。也就是同一个像素点被绘制了两次。
3.绿色:代表过度绘制二次,也就是同一个像素点被绘制了三次。通常,你需要集中精力优化过度绘制大于等于2次的情况。
4.浅红色:代表过度绘制三次,也就是同一个像素点被绘制了四次。这取决于你的应用,小范围的3次重绘可能是不可避免的,但如果大面积的出现,那说明布局设置肯定出现了问题,应该检查一下布局是否合理。
5.深红色:代表过度绘制四次或者更多,也就是同一像素点被绘制了五次甚至更多。出现这种问题你一定要找出原因,进行优化。
3.最小化过度绘制
也就是说当出现过度绘制的问题时,最直接的解决方法就是打开xml文件找到过度绘制的区域,特别是不可见的drawable
或者其它控件上的背景设置,以此来降低这些地方的过度绘制。也应该找出那些自己背景设置成白色,而父类也设置了背景为白色的情况。
4.2 Android图形渲染
为了渲染每个视图,Android
都会经历三个阶段:测量(onMeasure)、布局(onLayout)、绘制(onDraw)。花在这三个阶段的时间会View
层级中的数量成正比,这也就意味着层级越深所花在渲染的时间就越长。
在视图渲染期间,每个View都要向它的父视图提供它自己的尺寸。而如果父视图发现任意一个尺寸有问题,那么它会强制所有的子视图重新进行测量。
即使没有发生尺寸有问题的情况,重新测量也会出现。比如:为了正确的进行布局,RelativeLayout
通常会对它的子视图进行两次测量,而使用weight
属性的LinearLayout
也会对它的子视图进行两次测量。
测量和重新测量的代价是昂贵的,它们严重影响你的app的渲染速度。确保你的用户界面运行流畅的关键在于去掉不必要的view以及减少view的层级。Android
为我们提供了分析view层级的工具——Hierarchy Viewer
Hierarchy Viewer的使用
通过两种方式可以打开Hierarchy Viewer
。 或者使用打开之后,见到的就是这个界面当然,有时候进去不是并不是上面看到的界面,可以通过下面的方式找到好了,我们先来说说上面三个部分都是显示什么的吧
1.Tree View:视图层级窗口,这里显示是的最全的信息,每一个节点代表一个view
2.Tree OverView:整个视图层级的缩略图
3.Layout View:当前视图层级的轮廓,也就是布局长什么样子
当你不确定一个view是否在UI界面中是必须元素,最简单的方法就是在Tree View
里点击这个节点,然后在Layout View
里看看它是怎么显示的,正常的显示是下面这个样子的。这样子代表它在view上是有显示的。
而这种显示说明当前view在视图里是没有显示的,是多余的,当然也并不是说这个视图完全没有用,只是说明当前这个页面显示时,它是没有起作用的。
我们可以通过Hierarchy Viewer
来剖析每个view在不同的渲染阶段的耗时。Hierarchy Viewer
默认并不会显示渲染时间。需要到Tree View
的窗口上进行设置设置方法是先点击需要查看时间的父view,点击选中后,再点击三个颜色的按钮,如果没有报错的话,当前view的所有子view都会显示三个小圆点。每个小圆点可以显示出不同的颜色,分别代表不同的等级。从左到右依次代表测量、布局、绘制的相应时间开销。每个小圆点都有几种颜色表示。
1.绿色代表该
View
的渲染速度至少要快于一半以上的其它参与测试的节点。
2.黄色代表该view慢于50%以上的其他节点。
3.红色代表该View的渲染速度比其他所有参与测试的节点都慢。
真机运行Hierarchy Viewer调试不起的解决方法
报错为Unable to debug device
当然还有其它的错误提示,总之,就是不能正常打开调试。我的测试机为S6
,一直都调不出来view页面。这里你需要导入一个库 ,这个库只有一个文件,解决此问题,github
网址:点我,将此库应用到项目后,就可以正常调试了。
4.3 数据采集与分析工具——TraceView
TraceView
是Android平台提供的一个专门用来数据采集和分析的工具。TraceView
可以搭配Debug
类或者使用DDMS
工具来使用。
使用Debug
类的方法为开发人员可以在需要调试代码的开始处使用Debug.startMethodTracing()
来开启数据采集。在代码结束处使用Debug.stopMethodTracing()
来停止对数据的采集。并将采集到的文件保存到/mnt/sdcard/目录下,文件名需要在startMethodTracing()
的方法参数中进行配置。完整的使用见下图:
另外一种是使用DDMS
工具来生成采集数据文件。打开DDMS
工具后,app执行到需要采集数据的点,点击带小红点的按钮开始捕获数据,再次点击后结束捕获。此时TraceView会自动打开Trace
文件。TraceView
面板分为上下两个面板,依次为Timeline Panel
时间线面板和Profile Panel
函数分析面板。而上面板又分为左面板和右面板,左面板为测试数据中所采集到的线程信息;右面板为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包含函数名、函数调用时间等。TraceView
的函数分析面板的参数比较复杂,了解每一列的参数的意义是很关键的。
1.
Incl Cpu Time %
表示某函数占用的CPU执行时间(包括该函数调用其它函数的执行时间)占总执行时间的百分比
2.Incl Cpu Time
表示某函数占用的CPU执行时间(包括该函数调用其它函数所执行的时间)
3.Excl Cpu Time %
表示某函数占用的CPU执行时间(不含该函数调用其它函数的执行时间)占总执行时间的百分比。
4.Excl Cpu Time
表示某函数占用的CPU执行时间(不含该函数调用其它函数的执行时间)
5.Incl Real Time
表示某函数执行的真实时间,包括调用其它函数的执行时间,包含了CPU的等待、切换时间,因此要比Incl Cpu Time
要大。
6.Excl Real Time
表示某函数执行的真实时间,不含该函数调用的其它函数的执行时间
7.Calls+Recur Calls/Total
表示某函数被调用的次数,递归调用次数、总调用次数
8.Cpu Time/Call
表示某函数的CPU执行时间与调用次数的比,相当于该函数的平均执行时间,这里没有包含函数内部调用其它函数的执行时间
9.Real Time/Call
表示某函数的CPU执行时间与调用次数的比,相当于该函数的平均执行时间,这个时间中包含了内部调用的其他函数的执行时间。
清楚了每列参数代表的意思之后,我们上一个例子来简单分析一下。假设我们收集的数据总共执行时间为100秒,下面的request()
函数执行了10秒,在request()
里有两个函数,getParams()
和excute()
分别执行时间为4秒、5秒。由于request
的耗时为10秒,假设request
被调用了一次,而我们收集数据的执行时间为100秒。那么request
的Incl Cpu Time
为10秒,Incl Cpu Time %
就是10%。Excl Cpu Time
是不包含函数调用其它函数的执行时间,在request
里除去getParams()
5秒、excute()
4秒后,为1秒。也就是说request
本身耗时较少,真正耗时的是它内部调用的函数。
接下来来一个更加具体的例子来进一步说明我们使用getView
来模拟一个耗时的操作。因为我们调试的是ListView
,而ListView
的性能瓶颈一般会在Adapter
的getView
里。所以我直接在Trace
文件中找到getView
方法来进行分析。incl Cpu Time
表示getView
的执行时间,包括调用其它函数的执行时间,但是这里不包含CPU等待,切换所花费的时间,这里为135.6毫秒。Incl Real Time
表示当前函数执行时间,包括调用其它函数的执行时间和CPU切换、等待的执行时间,这里为1485毫秒。因为我们这里主要是使用Sleep
来进行耗时操作的,所以能看到Incl Cpu Time
耗时比较少,而Incl Real Time
耗时比较大。我们再来看看在getView
内部调用的各函数所花费的时间。doSomeStr
下的Incl Real Time
为1383毫秒,占真实耗时的93%之多,而Call + Recul Calls/Total
的值为75,也就是被调用了75次,其Real Time/Call
为18.444秒,刚好就是我们使用Thread.Sleep
所耗费的时间18毫秒,再加上一点调用相应函数所耗费的时间0.444毫秒。我们再来看看doSomeStr
里调用的相应函数的耗时情况。从图上可以看出,Incl Real Time
的值分别为310、310、762,而我们在函数里让CPU的睡眠时间分别为4、4、10,比例差不多就是一样的,这里加上一丁点的函数本身执行所耗费的时间。这样,我们就找到了在getView
里真正耗时的地方了。找到了地方就可以开始着手进行相关的优化了。
好了,今天的博客终于是写完了,内容有点多,话说花了我两天的时间才写完的。写得不对的地方,还请大神多多指教!拜了个拜~