Android 应用和系统优化V1.2

一年多年写了一篇简单的软件优化教程,给公司的同事使用。现在应该还不算过时,在过去一年里,在国家脱虚向实运动倡导下,一个个高科技企业如雨后春笋般诞生,对软件的优化和重构的需求也越来越多。早期的 android 开发者曾经十分羡慕C文开发者,C的调试工具是如此之多和丰富,内存,堆栈,CPU,GPU,断点,现在android的系统分析和优化工具也相当多,借助系统自带的开发者工具,android系统也变得日趋成熟和完美。无论是功耗(耗电),还是CPU,还是GPU,还是内存,抑或者是UI显示都有了相应的分析工具做定量的分析,不会处于我的软件似乎变快了,似乎省电了,但又肉眼看不出来的结果。


Android 应用和系统优化V1.2

作者:贾治国

目录

1应用UI卡顿常见原因
2.常用工具
3.工具用法
Android应用开发性能优化完全分析
给 App 提速:Android 性能优化总结
Android系统性能调优工具介绍
正确使用Android性能分析工具——TraceView
为什么微博的app在iPhone比Android上流畅
被忽略的UI检视利器:Hierarchy Viewer
使用Systrace分析UI性能
正确使用Android性能分析工具——TraceView
内存分析工具 MAT 的使用
使用Android studio分析内存泄露
Android 编程下的 TraceView 简介及其案例实战
Google 发布 Android 性能优化典范
Android MemInfo 各项的意义(转) 
Android 内存分析工具 - LogCat GC
Android使用procrank和dumpsysmeminfo分析内存占用情况
LeakCanary——直白的展现Android中的内存泄露
LeakCanary 中文使用说明
如何使用eclipse单独调试android系统的app
Android上oprofile使用说明
谁动了我的cpu——oprofile使用札记
Android性能优化案例研究(上)
Android性能优化案例研究(下)
Android代码性能优化












应用UI卡顿常见原因
我们在使用App时会发现有些界面启动卡顿、动画不流畅、列表等滑动时也会卡顿,究其原因,很多都是丢帧导致的;通过上面卡顿原理的简单说明我们从应用开发的角度往回推理可以得出常见卡顿原因,如下:
人为在UI线程中做轻微耗时操作,导致UI线程卡顿;(traceview)
布局Layout过于复杂,无法在16ms内完成渲染;(HierarchyViewer)
同一时间动画执行的次数过多,导致CPU或GPU负载过重;(traceview)
View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;使用GPU过度绘制分析UI性能(开发者模式)
View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;(HierarchyViewer)
内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;(Memory监测)
冗余资源及逻辑等导致加载和执行缓慢; (HierarchyViewer)
臭名昭著的ANR; (adb logcat | grep AndroidRuntime)
影响动画的因素,时间曲线,帧率,触屏响应(systrace)
大量的数学运算和矩阵变化阻塞了动画线程,优化数学方法,少使用浮点(代码分析)
使用大量滤镜特效处理图像导致动画阻塞(代码分析)













常用分析工具
使用GPU过度绘制分析UI性能(开发者模式,布局优化)
硬件加速,打开 “Show hardware layers updates” 选项。(开发者模式,变绿未加速)
使用GPU呈现模式图及FPS考核UI性能(开发者模式,超过十六毫秒优化)
一些android自带的命令行工具procrank,meminfo分析系统和应用内存
使用Lint进行资源及冗余UI布局等优化(android studio OR eclipse工具对代码优化)
android studio device monitor或者DDMS使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析(android studio device monitor OR eclipse ddms分析)
使用HierarchyViewer(层级观察器)分析UI性能(命令行,android studio device monitor OR eclipse ddms启动)
使用Traceview和dmtracedump进行分析优化,系统函数耗时分析(android studio device monitor OR eclipse ddms )
使用Systrace进行分析优化,动画帧超时分析,提供性能警告信息分析(android studio device monitor OR eclipse ddms)
使用traces.txt文件进行ANR分析优化(分析阻塞)
LeakCanary(查找概率性内存泄漏,需要注入APP测试)
Oprofile,linux下使用,可以分析优化系统底层的文件和函数
MAT(查找内存泄漏)
Adb logcat | findstr GC 或者 adb logcat grep GC


















常用工具及用法(标题前网址为原文地址)

http://blog.csdn.net/yanbober/article/details/48394201
Android应用开发性能优化完全分析
1 背景
其实有点不想写这篇文章的,但是又想写,有些矛盾。不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结、我一总结的都说到了很多优化 注意事项,但是看过这些文章后大多数存在一个问题就是只给出啥啥啥不能用,啥啥啥该咋用等,却很少有较为系统的进行真正性能案例分析的,大多数都是嘴上喊 喊或者死记住规则而已(当然了,这话我自己听着都有些刺耳,实在不好意思,其实关于性能优化的优质博文网上也还是有很多的,譬如Google官方都已经推 出了优化专题,我这里只是总结下自的感悟而已,若有得罪欢迎拍砖,我愿挨打,因为我之前工作的一半时间都是负责性能优化)。
当然了,本文不会就此编辑这么一次,因为技术在发展,工具在强大(写着写着Android Studio 1.4版本都推送了),自己的经验也在增加,所以本文自然不会覆盖所有性能优化及分析;解决的办法就是该文章会长期维护更新,同时在评论区欢迎你关于性能 优化点子的探讨。
Android应用的性能问题其实可以划分为几个大的模块的,而且都具有相对不错的优化调试技巧,下面我们就会依据一个项目常规开发的大类型来进行一些分析讲解。
PS:之前呆过一家初创医疗互联网公司,别提性能优化了,老板立完新项目后一个月就要求见到上线成品,这种压迫下谈何性能优化,纯属扯蛋,所以不到三个月时间我主动选择撤了,这种现象后来我一打听发现在很多初创公司都很严重,都想速成却忽略了体验。
PPPS:本文只是达到抛砖引玉的作用,很多东西细究下去都是值得深入研究的,再加上性能优化本来就是一个需要综合考量的任务,不是说会了本文哪一点就能做性能分析了,需要面面俱到才可高效定位问题原因。
2 应用UI性能问题分析
UI可谓是一个应用的脸,所以每一款应用在开发阶段我们的交互、视觉、动画工程师都拼命的想让它变得自然大方美丽,可是现实总是不尽人意,动画和交 互总会觉得开发做出来的应用用上去感觉不自然,没有达到他们心目中的自然流畅细节;这种情况之下就更别提发布给终端用户使用了,用户要是能够感觉出来,少 则影响心情,多则卸载应用;所以一个应用的UI显示性能问题就不得不被开发人员重视。
2-1 应用UI卡顿原理
人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这种流畅的帧率规定为60fps。
有了上面的背景,我们开发App的帧率性能目标就是保持在60fps,也就是说我们在进行App性能优化时心中要有如下准则:
换算关系:60帧/秒-----------16ms/帧;

准则:尽量保证每次在16ms内处理完所有的CPU与GPU计算、绘制、渲染等操作,否则会造成丢帧卡顿问题。
1
2
3
从上面可以看出来,所谓的卡顿其实是可以量化的,每次是否能够成功渲染是非常重要的问题,16ms能否完整的做完一次操作直接决定了卡顿性能问题。
当然了,针对Android系统的设计我们还需要知道另一个常识;虚拟机在执行GC垃圾回收操作时所有线程(包括UI线程)都需要暂停,当GC垃圾 回收完成之后所有线程才能够继续执行(这个细节下面小节会有详细介绍)。也就是说当在16ms内进行渲染等操作时如果刚好遇上大量GC操作则会导致渲染时 间明显不足,也就从而导致了丢帧卡顿问题。
有了上面这两个简单的理论基础之后我们下面就会探讨一些UI卡顿的原因分析及解决方案。
2-2 应用UI卡顿常见原因
我们在使用App时会发现有些界面启动卡顿、动画不流畅、列表等滑动时也会卡顿,究其原因,很多都是丢帧导致的;通过上面卡顿原理的简单说明我们从应用开发的角度往回推理可以得出常见卡顿原因,如下:
人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
布局Layout过于复杂,无法在16ms内完成渲染;
同一时间动画执行的次数过多,导致CPU或GPU负载过重;
View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;
View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
冗余资源及逻辑等导致加载和执行缓慢;
臭名昭著的ANR;
可以看见,上面这些导致卡顿的原因都是我们平时开发中非常常见的。有些人可能会觉得自己的应用用着还蛮OK的,其实那是因为你没进行一些瞬时测试和压力测试,一旦在这种环境下运行你的App你就会发现很多性能问题。
2-3 应用UI卡顿分析解决方法
分析UI卡顿我们一般都借助工具,通过工具一般都可以直观的分析出问题原因,从而反推寻求优化方案,具体如下细说各种强大的工具。
2-3-1 使用HierarchyViewer分析UI性能
我们可以通过SDK提供的工具HierarchyViewer来进行UI布局复杂程度及冗余等分析,如下:
xxx@ThinkPad:~$ hierarchyviewer   //通过命令启动HierarchyViewer
1
选中一个Window界面item,然后点击右上方Hierarchy window或者Pixel Perfect window(这里不介绍,主要用来检查像素属性的)即可操作。
先看下Hierarchy window,如下:
 
一个Activity的View树,通过这个树可以分析出View嵌套的冗余层级,左下角可以输入View的id直接自动跳转到中间显示;Save as PNG用来把左侧树保存为一张图片;Capture Layers用来保存psd的PhotoShop分层素材;右侧剧中显示选中View的当前属性状态;右下角显示当前View在Activity中的位置 等;左下角三个进行切换;Load View Hierarchy用来手动刷新变化(不会自动刷新的)。当我们选择一个View后会如下图所示:
 
类似上图可以很方便的查看到当前View的许多信息;上图最底那三个彩色原点代表了当前View的性能指标,从左到右依次代表测量、布局、绘制的渲 染时间,红色和黄色的点代表速度渲染较慢的View(当然了,有些时候较慢不代表有问题,譬如ViewGroup子节点越多、结构越复杂,性能就越差)。
当然了,在自定义View的性能调试时,HierarchyViewer上面的invalidate Layout和requestLayout按钮的功能更加强大,它可以帮助我们debug自定义View执行invalidate()和 requestLayout()过程,我们只需要在代码的相关地方打上断点就行了,接下来通过它观察绘制即可。
可以发现,有了HierarchyViewer调试工具,我们的UI性能分析变得十分容易,这个工具也是我们开发中调试UI的利器,在平时写代码时会时常伴随我们左右。
2-3-2 使用GPU过度绘制分析UI性能
我们对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析):
 
可以发现,开启后在我们想要调试的应用界面中可以看到各种颜色的区域,具体含义如下:
 
颜色    含义       
无色    WebView等的渲染区域       
蓝色    1x过度绘制       
绿色    2x过度绘制       
淡红色    3x过度绘制       
红色    4x(+)过度绘制    

由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会被绘制两次,一次背景一次文本;这里需要强调的是 Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(当然这在很多绚丽的界面是不现实的,所以 大家有个度即可,我们的开发性能优化标准要求最极端界面下红色区域不能长期持续超过屏幕三分之一,可见还是比较宽松的规定),因此我们需要依据此颜色分布 进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设 置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。
2-3-3 使用GPU呈现模式图及FPS考核UI性能
Android界面流畅度除过视觉感知以外是可以考核的(测试妹子专用),常见的方法就是通过GPU呈现模式图或者实时FPS显示进行考核,这里我 们主要针对GPU呈现模式图进行下说明,因为FPS考核测试方法有很多(譬如自己写代码实现、第三方App测试、固件支持等),所以不做统一说明。
通过开发者选项中GPU呈现模式图工具来进行流畅度考量的流程是(注意:如果是在开启应用后才开启此功能,记得先把应用结束后重新启动)在设置 ->开发者选项->GPU呈现模式(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面上下滑动列表后 的图表):
 
当然,也可以在执行完UI滑动操作后在命令行输入如下命令查看命令行打印的GPU渲染数据(分析依据:Draw + Process + Execute = 完整的显示一帧时间 < 16ms):
adb shell dumpsys gfxinfo [应用包名]
1
打开上图可视化工具后,我们可以在手机画面上看到丰富的GPU绘制图形信息,分别展示了StatusBar、NavgationBar、 Activity区域等的GPU渲染时间信息,随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上 都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算啥问 题的)。
可以发现,这个工具是有局限性的,他虽然能够看出来有帧耗时超过基准线导致了丢帧卡顿,但却分析不到造成丢帧的具体原因。所以说为了配合解决分析UI丢帧卡顿问题我们还需要借助traceview和systrace来进行原因追踪,下面我们会介绍这两种工具的。
2-3-4 使用Lint进行资源及冗余UI布局等优化
上面说了,冗余资源及逻辑等也可能会导致加载和执行缓慢,所以我们就来看看Lint这个工具是如何发现优化这些问题的(当然了,Lint实际的功能是非常强大的,我们开发中也是经常使用它来发现一些问题的,这里主要有点针对UI性能的说明了,其他的雷同)。
在Android Studio 1.4版本中使用Lint最简单的办法就是将鼠标放在代码区点击右键->Analyze->Inspect Code–>界面选择你要检测的模块->点击确认开始检测,等待一下后会发现如下结果:
 
可以看见,Lint检测完后给了我们很多建议的,我们重点看一个关于UI性能的检测结果;上图中高亮的那一行明确说明了存在冗余的UI层级嵌套,所以我们是可以点击跳进去进行优化处理掉的。
当然了,Lint还有很多功能,大家可以自行探索发挥,这里只是达到抛砖引玉的作用。
2-3-5 使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析
关于Android的内存管理机制下面的一节会详细介绍,这里我们主要针对GC导致的UI卡顿问题进行详细说明。
Android系统会依据内存中不同的内存数据类型分别执行不同的GC操作,常见应用开发中导致GC频繁执行的原因主要可能是因为短时间内有大量频 繁的对象创建与释放操作,也就是俗称的内存抖动现象,或者短时间内已经存在大量内存暂用介于阈值边缘,接着每当有新对象创建时都会导致超越阈值触发GC操 作。
如下是我工作中一个项目的一次经历(我将代码回退特意抓取的),出现这个问题的场景是一次压力测试导致整个系统卡顿,瞬间杀掉应用就OK了,究其原 因最终查到是一个API的调运位置写错了方式,导致一直被狂调,当普通使用时不会有问题,压力测试必现卡顿。具体内存参考图如下:
 
与此抖动图对应的LogCat抓取如下:
//截取其中比较密集一段LogCat,与上图Memory检测到的抖动图对应,其中xxx为应用包名
......
10-06 00:59:45.619 xxx I/art: Explicit concurrent mark sweep GC freed 72515(3MB) AllocSpace objects, 65(2028KB) LOS objects, 80% free, 17MB/89MB, paused 3.505ms total 60.958ms
10-06 00:59:45.749 xxx I/art: Explicit concurrent mark sweep GC freed 5396(193KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.079ms total 100.522ms
......
10-06 00:59:48.059 xxx I/art: Explicit concurrent mark sweep GC freed 4693(172KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.227ms total 101.692ms
......
1
2
3
4
5
6
7
我们知道,类似上面logcat打印一样,触发垃圾回收的主要原因有以下几种:
GC_MALLOC——内存分配失败时触发;
GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;
GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;
GC_EXTERNAL_ALLOC——外部内存分配失败时触发;
可以看见,这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿,所以我们要避免此类问题的出现,具体的常见优化方式如下:
检查代码,尽量避免有些频繁触发的逻辑方法中存在大量对象分配;
尽量避免在多次for循环中频繁分配对象;
避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等);
对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理。
当然了,有了上面说明GC导致的性能后我们就该定位分析问题了,可以通过运行DDMS->Allocation Tracker标签打开一个新窗口,然后点击Start Tracing按钮,接着运行你想分析的代码,运行完毕后点击Get Allocations按钮就能够看见一个已分配对象的列表,如下:
 
点击上面第一个表格中的任何一项就能够在第二个表格中看见导致该内存分配的栈信息,通过这个工具我们可以很方便的知道代码分配了哪类对象、在哪个线 程、哪个类、哪个文件的哪一行。譬如我们可以通过Allocation Tracker分别做一次Paint对象实例化在onDraw与构造方法的一个自定义View的内存跟踪,然后你就明白这个工具的强大了。
PS一句,Android Studio新版本除过DDMS以外在Memory视图的左侧已经集成了Allocation Tracker功能,只是用起来还是没有DDMS的方便实用,如下图:
 
2-3-6 使用Traceview和dmtracedump进行分析优化
关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,他是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS 然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个 按钮,稍等片刻即可出现下图,如下:
 
花花绿绿的一幅图我们怎么分析呢?下面我们解释下如何通过该工具定位问题:
整个界面包括上下两部分,上面是你测试的进程中每个线程运行的时间线,下面是每个方法(包含parent及child)执行的各个指标的值。通过上 图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面的性能面板很 复杂,其实这才是TraceView的核心图表,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡 顿的核心关注点,所以我们先看几个重要的属性说明,如下:
 
属性名    含义       
name    线程中调运的方法名;       
Incl CPU Time    当前方法(包含内部调运的子方法)执行占用的CPU时间;       
Excl CPU Time    当前方法(不包含内部调运的子方法)执行占用的CPU时间;       
Incl Real Time    当前方法(包含内部调运的子方法)执行的真实时间,ms单位;       
Excl Real Time    当前方法(不包含内部调运的子方法)执行的真实时间,ms单位;       
Calls+Recur Calls/Total    当前方法被调运的次数及递归调运占总调运次数百分比;       
CPU Time/Call    当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间;       
Real Time/Call    当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注)    

有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题:
方法调运一次需要耗费很长时间导致卡顿;
方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。
譬如我们来举个实例,有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我 们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可;当然了,我们也可以通过该工具来 trace我们自定义View的一些方法来权衡性能问题,这里不再一一列举喽。
可以看见,Traceview能够帮助我们分析程序性能,已经很方便了,然而Traceview家族还有一个更加直观强大的小工具,那就是可以通过dmtracedump生成方法调用图。具体做法如下:
dmtracedump -g result.png target.trace  //结果png文件  目标trace文件
1
通过这个生成的方法调运图我们可以更加直观的发现一些方法的调运异常现象。不过本人优化到现在还没怎么用到它,每次用到Traceview分析就已经搞定问题了,所以说dmtracedump自己酌情使用吧。
PS一句,Android Studio新版本除过DDMS以外在CPU视图的左侧已经集成了Traceview(start Method Tracing)功能,只是用起来还是没有DDMS的方便实用(这里有一篇AS MT个人觉得不错的分析文章(引用自网络,链接属于原作者功劳)),如下图:
 
2-3-7 使用Systrace进行分析优化
Systrace其实有些类似Traceview,它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、 WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查/sys/kernel /debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下。
我们在分析UI性能时一般只关注图形性能(所以必须选择Graphics和View,其他随意),同时一般对于卡顿的抓取都是5s,最多10s。启动Systrace进行数据抓取可以通过两种方式,命令行方式如下:
python systrace.py --time=10 -o mynewtrace.html sched gfx view wm
1
图形模式:
打开DDMS->Capture system wide trace using Android systrace->设置时间与选项点击OK就开始了抓取,接着操作APP,完事生成一个trace.html文件,用Chrome打开即可如下图:
 
在Chrome中浏览分析该文件我们可以通过键盘的W-A-S-D键来搞定,由于上面我们在进行trace时选择了一些选项,所以上图生成了左上方相关的 CPU频率、负载、状态等信息,其中的CPU N代表了CPU核数,每个CPU行的柱状图表代表了当前时间段当前核上的运行信息;下面我们再来看看SurfaceFlinger的解释,如下:
 
可以看见上面左边栏的SurfaceFlinger其实就是负责绘制Android程序UI的服务,所以SurfaceFlinger能反应出整体 绘制情况,可以关注上图VSYNC-app一行可以发现前5s多基本都能够达到16ms刷新间隔,5s多开始到7s多大于了15ms,说明此时存在绘制丢 帧卡顿;同时可以发现surfaceflinger一行明显存在类似不规律间隔,这是因为有的地方是不需要重新渲染UI,所以有大范围不规律,有的是因为 阻塞导致不规律,明显可以发现0到4s间大多是不需要渲染,而5s以后大多是阻塞导致;对应这个时间点我们放大可以看到每个部分所使用的时间和正在执行的 任务,具体如下:
 
可以发现具体的执行明显存在超时性能卡顿(原点不是绿色的基本都代表存在一定问题,下面和右侧都会提示你选择的帧相关详细信息或者alert信 息),但是遗憾的是通过Systrace只能大体上发现是否存在性能问题,具体问题还需要通过Traceview或者代码中嵌入Trace工具类等去继续 详细分析,总之很蛋疼。
PS:如果你想使用Systrace很轻松的分析定位所有问题,看明白所有的行含义,你还需要具备非常扎实的Android系统框架的原理才可以将该工具使用的得心应手。
2-3-8 使用traces.txt文件进行ANR分析优化
ANR(Application Not Responding)是Android中AMS与WMS监测应用响应超时的表现;之所以把臭名昭著的ANR单独作为UI性能卡顿的分析来说明是因为 ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免他的出现,当然了,万一出现了那就用下面介绍的方法来分析吧。
我们应用开发中常见的ANR主要有如下几类:
按键触摸事件派发超时ANR,一般阈值为5s(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);
广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);
服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);
当ANR发生时除过logcat可以看见的log以外我们还可以在系统指定目录下找到traces文件或dropbox文件进行分析,发生ANR后我们可以通过如下命令得到ANR trace文件:
adb pull /data/anr/traces.txt ./
1
然后我们用txt编辑器打开可以发现如下结构分析:
//显示进程id、ANR发生时间点、ANR发生进程包名
----- pid 19073 at 2015-10-08 17:24:38 -----
Cmd line: com.example.yanbo.myapplication
//一些GC等object信息,通常可以忽略
......
//ANR方法堆栈打印信息!重点!
DALVIK THREADS (18):
"main" prio=5 tid=1 Sleeping
  | group="main" sCount=1 dsCount=0 obj=0x7497dfb8 self=0x7f9d09a000
  | sysTid=19073 nice=0 cgrp=default sched=0/0 handle=0x7fa106c0a8
  | state=S schedstat=( 125271779 68162762 280 ) utm=11 stm=1 core=0 HZ=100
  | stack=0x7fe90d3000-0x7fe90d5000 stackSize=8MB
  | held mutexes=
  at java.lang.Thread.sleep!(Native method)
  - sleeping on <0x0a2ae345> (a java.lang.Object)
  at java.lang.Thread.sleep(Thread.java:1031)
  - locked <0x0a2ae345> (a java.lang.Object)
//真正导致ANR的问题点,可以发现是onClick中有sleep导致。我们平时可以类比分析即可,这里不详细说明。
  at java.lang.Thread.sleep(Thread.java:985)
  at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:21)
  at android.view.View.performClick(View.java:4908)
  at android.view.View$PerformClick.run(View.java:20389)
  at android.os.Handler.handleCallback(Handler.java:815)
  at android.os.Handler.dispatchMessage(Handler.java:104)
  at android.os.Looper.loop(Looper.java:194)
  at android.app.ActivityThread.main(ActivityThread.java:5743)
  at java.lang.reflect.Method.invoke!(Native method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
......
//省略一些不常关注堆栈打印
......
至此常见的应用开发中ANR分析定位就可以解决了。
2-4 应用UI性能分析解决总结
可以看见,关于Android UI卡顿的性能分析还是有很多工具的,上面只是介绍了应用开发中我们经常使用的一些而已,还有一些其他的,譬如Oprofile等工具不怎么常用,这里就不再详细介绍。
通过上面UI性能的原理、原因、工具分析总结可以发现,我们在开发应用时一定要时刻重视性能问题,如若真的没留意出现了性能问题,不妨使用上面的一 些案例方式进行分析。但是那终归是补救措施,在我们知道上面UI卡顿原理之后我们应该尽量从项目代码架构搭建及编写时就避免一些UI性能问题,具体项目中 常见的注意事项如下:
布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽 量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自 定义Item View来取代,减少measure与layout次数等。
列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。
背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。
自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。
避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等。
当然了,上面只是列出了我们项目中常见的一些UI性能注意事项而已,相信还有很多其他的情况这里没有说到,欢迎补充。还有一点就是我们上面所谓的 UI性能优化分析总结等都是建议性的,因为性能这个问题是一个涉及面很广很泛的问题,有些优化不是必需的,有些优化是必需的,有些优化掉以后又是得不偿失 的,所以我们一般着手解决那些必须的就可以了。
3 应用开发Memory内存性能分析优化
说完了应用开发中的UI性能问题后我们就该来关注应用开发中的另一个重要、严重、非常重要的性能问题了,那就是内存性能优化分析。Android其 实就是嵌入式设备,嵌入式设备核心关注点之一就是内存资源;有人说现在的设备都在堆硬件配置(譬如国产某米的某兔跑分手机、盒子等),所以内存不会再像以 前那么紧张了,其实这句话听着没错,但为啥再牛逼配置的Android设备上有些应用还是越用系统越卡呢?这里面的原因有很多,不过相信有了这一章下面的 内容分析,作为一个移动开发者的你就有能力打理好自己应用的那一亩三分地内存了,能做到这样就足以了。关于Android内存优化,这里有一篇 Google的官方指导文档,但是本文为自己项目摸索,会有很多不一样的地方。
3-1 Android内存管理原理
系统级内存管理:
Android系统内核是基于Linux,所以说Android的内存管理其实也是Linux的升级版而已。Linux在进程停止后就结束该进程, 而Android把这些停止的进程都保留在内存中,直到系统需要更多内存时才选择性的释放一些,保留在内存中的进程默认(不包含后台service与 Thread等单独UI线程的进程)不会影响整体系统的性能(速度与电量等)且当再次启动这些保留在内存的进程时可以明显提高启动速度,不需要再去加载。
再直白点就是说Android系统级内存管理机制其实类似于Java的垃圾回收机制,这下明白了吧;在Android系统中框架会定义如下几类进程、在系统内存达到规定的不同level阈值时触发清空不同level的进程类型。
 
可以看见,所谓的我们的Service在后台跑着跑着挂了,或者盒子上有些大型游戏启动起来就挂(之前我在上家公司做盒子时遇见过),有一个直接的 原因就是这个阈值定义的太大,导致系统一直认为已经达到阈值,所以进行优先清除了符合类型的进程。所以说,该阈值的设定是有一些讲究的,额,扯多了,我们 主要是针对应用层内存分析的,系统级内存回收了解这些就基本够解释我们应用在设备上的一些表现特征了。
应用级内存管理:
在说应用级别内存管理原理时大家先想一个问题,假设有一个内存为1G的Android设备,上面运行了一个非常非常吃内存的应用,如果没有任何机制的情况下是不是用着用着整个设备会因为我们这个应用把1G内存吃光然后整个系统运行瘫痪呢?
哈哈,其实Google的工程师才不会这么傻的把系统设计这么差劲。为了使系统不存在我们上面假想情况且能安全快速的运行,Android的框架使 得每个应用程序都运行在单独的进程中(这些应用进程都是由Zygote进程孵化出来的,每个应用进程都对应自己唯一的虚拟机实例);如果应用在运行时再存 在上面假想的情况,那么瘫痪的只会是自己的进程,不会直接影响系统运行及其他进程运行。
既然每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般 都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等;它的配置是在Android的属性系统的/system /build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize则表示初始申 请大小),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。
接着我们运行的App在自己的虚拟机中内存管理基本就是遵循Java的内存管理机制了,系统在特定的情况下主动进行垃圾回收。但是要注意的一点就是 在Android系统中执行垃圾回收(GC)操作时所有线程(包含UI线程)都必须暂停,等垃圾回收操作完成之后其他线程才能继续运行。这些GC垃圾回收 一般都会有明显的log打印出回收类型,常见的如下:
GC_MALLOC——内存分配失败时触发;
GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;
GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;
GC_EXTERNAL_ALLOC——外部内存分配失败时触发;
通过上面这几点的分析可以发现,应用的内存管理其实就是一个萝卜一个坑,坑都一般大,你在开发应用时要保证的是内存使用同一时刻不能超过坑的大小,否则就装不下了。
3-2 Android内存泄露性能分析
有了关于Android的一些内存认识,接着我们来看看关于Android应用开发中常出现的一种内存问题—-内存泄露。
3-2-1 Android应用内存泄露概念
众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个 对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。如下就是一个最常见简单的泄露例 子(其它的泄露不再一一列举了):
public final class MainActivity extends Activity {
    private DbManager mDbManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        //DbManager是一个单例模式类,这样就持有了MainActivity引用,导致泄露
        mDbManager = DbManager.getInstance(this);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
可以看见,上面例子中我们让一个单例模式的对象持有了当前Activity的强引用,那在当前Acvitivy执行完onDestroy()后,这个Activity就无法得到垃圾回收,也就造成了内存泄露。
内存泄露可以引发很多的问题,常见的内存泄露导致问题如下:
应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC);
应用被从后台进程干为空进程(上面系统内存原理有介绍,也就是超过了阈值);
应用莫名的崩溃(上面应用内存原理有介绍,也就是超过了阈值OOM);
造成内存泄露泄露的最核心原理就是一个对象持有了超过自己生命周期以外的对象强引用导致该对象无法被正常垃圾回收;可以发现,应用内存泄露是个相当棘手重要的问题,我们必须重视。
3-2-2 Android应用内存泄露察觉手段
知道了内存泄露的概念之后肯定就是想办法来确认自己的项目是否存在内存泄露了,那该如何察觉自己项目是否存在内存泄露呢?如下提供了几种常用的方式:
 
察觉方式    场景       
AS的Memory窗口    平时用来直观了解自己应用的全局内存情况,大的泄露才能有感知。       
DDMS-Heap内存监测工具    同上,大的泄露才能有感知。       
dumpsys meminfo命令    常用方式,可以很直观的察觉一些泄露,但不全面且常规足够用。       
leakcanary神器    比较强大,可以感知泄露且定位泄露;实质是MAT原理,只是更加自动化了,当现有代码量已经庞大成型,且无法很快察觉掌控全局代码时极力推荐;或者是偶现泄露的情况下极力推荐。    
AS的Memory窗口如下,详细的说明这里就不解释了,很简单很直观(使用频率高):
 
DDMS-Heap内存监测工具窗口如下,详细的说明这里就不解释了,很简单(使用频率不高):
 
dumpsys meminfo命令如下(使用频率非常高,非常高效,我的最爱之一,平时一般关注几个重要的Object个数即可判断一般的泄露;当然了,adb shell dumpsys meminfo不跟参数直接展示系统所有内存状态):
 
leakcanary神器使用这里先不说,下文会专题介绍,你会震撼的一B。有了这些工具的定位我们就能很方便的察觉我们App的内存泄露问题,察觉到以后该怎么定位分析呢,继续往下看。
3-2-3 Android应用内存泄露leakcanary工具定位分析
leakcanary是一个开源项目,一个内存泄露自动检测工具,是著名的GitHub开源组织Square贡献的,它的主要优势就在于自动化过早 的发觉内存泄露、配置简单、抓取贴心,缺点在于还存在一些bug,不过正常使用百分之九十情况是OK的,其核心原理与MAT工具类似。
关于leakcanary工具的配置使用方式这里不再详细介绍,因为真的很简单,详情点我参考官方教程学习使用即可。
PS:之前在优化性能时发现我们有一个应用有两个界面退出后Activity没有被回收(dumpsys meminfo发现一直在加),所以就怀疑可能存在内存泄露。但是问题来了,这两个Activity的逻辑十分复杂,代码也不是我写的,相关联的代码量也 十分庞大,更加郁闷的是很难判断是哪个版本修改导致的,这时候只知道有泄露,却无法定位具体原因,使用MAT分析解决掉了一个可疑泄露后发现泄露又变成了 概率性的。可以发现,对于这种概率性的泄露用MAT去主动抓取肯定是很耗时耗力的,所以决定直接引入leakcanary神器来检测项目,后来很快就彻底 解决了项目中所有必现的、偶现的内存泄露。
总之一点,工具再强大也只是帮我们定位可能的泄露点,而最核心的GC ROOT泄露信息推导出泄露问题及如何解决还是需要你把住代码逻辑及泄露核心概念去推理解决。
3-2-4 Android应用内存泄露MAT工具定位分析
Eclipse Memory Analysis Tools(点我下载)是一个专门分析Java堆数据内存引用的工具,我们可以使用它方便的定位内存泄露原因,核心任务就是找到GC ROOT位置即可,哎呀,关于这个工具的使用我是真的不想说了,自己搜索吧,实在简单、传统的不行了。
PS:这是开发中使用频率非常高的一个工具之一,麻烦务必掌握其核心使用技巧,虽然Android Studio已经实现了部分功能,但是真的很难用,遇到问题目前还是使用Eclipse Memory Analysis Tools吧。
原谅我该小节的放荡不羁!!!!(其实我是困了,呜呜!)
3-2-5 Android应用开发规避内存泄露建议
有了上面的原理及案例处理其实还不够,因为上面这些处理办法是补救的措施,我们正确的做法应该是在开发过程中就养成良好的习惯和敏锐的嗅觉才对,所以下面给出一些应用开发中常见的规避内存泄露建议:
Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用(譬如上面概念部分给出的示例)。尽量在一切可以使用应用ApplicationContext代替Context的 地方进行替换(原理我前面有一篇关于Context的文章有解释)。
非静态内部类的静态实例容易造成内存泄漏;即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。
警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity 时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周 期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。
对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。
创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。
不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。
避免代码设计模式的错误造成内存泄露;譬如循环引用,A持有B,B持有C,C持有A,这样的设计谁都得不到释放。
关于规避内存泄露上面我只是列出了我在项目中经常遇见的一些情况而已,肯定不全面,欢迎拍砖!当然了,只有我们做到好的规避加上强有力的判断嗅觉泄露才能让我们的应用驾驭好自己的一亩三分地。
3-3 Android内存溢出OOM性能分析
上面谈论了Android应用开发的内存泄露,下面谈谈内存溢出(OOM);其实可以认为内存溢出与内存泄露是交集关系,具体如下图:
 
下面我们就来看看内存溢出(OOM)相关的东东吧。
3-3-1 Android应用内存溢出OOM概念
上面我们探讨了Android内存管理和应用开发中的内存泄露问题,可以知道内存泄露一般影响就是导致应用卡顿,但是极端的影响是使应用挂掉。前面 也提到过应用的内存分配是有一个阈值的,超过阈值就会出问题,这里我们就来看看这个问题—–内存溢出(OOM–OutOfMemoryError)。
内存溢出的主要导致原因有如下几类:
应用代码存在内存泄露,长时间积累无法释放导致OOM;
应用的某些逻辑操作疯狂的消耗掉大量内存(譬如加载一张不经过处理的超大超高清图片等)导致超过阈值OOM;
可以发现,无论哪种类型,导致内存溢出(OutOfMemoryError)的核心原因就是应用的内存超过阈值了。
3-3-2 Android应用内存溢出OOM性能分析
通过上面的OOM概念和那幅交集图可以发现,要想分析OOM原因和避免OOM需要分两种情况考虑,泄露导致的OOM,申请过大导致的OOM。
内存泄露导致的OOM分析:
这种OOM一旦发生后会在logcat中打印相关OutOfMemoryError的异常栈信息,不过你别高兴太早,这种情况下导致的OOM打印异常信息是没有太大作用,因为这种OOM的导致一般都如下图情况(图示为了说明问题数据和场景有夸张,请忽略):
 
从图片可以看见,这种OOM我们有时也遇到,第一反应是去分析OOM异常打印栈,可是后来发现打印栈打印的地方没有啥问题,没有可优化的余地了,于是就郁闷了。其实这时候你留心观察几个现象即可,如下:
留意你执行触发OOM操作前的界面是否有卡顿或者比较密集的GC打印;
使用命令查看下当前应用占用内存情况;
确认了以上这些现象你基本可以断定该OOM的log真的没用,真正导致问题的原因是内存泄露,所以我们应该按照上节介绍的方式去着手排查内存泄露问题,解决掉内存泄露后红色空间都能得到释放,再去显示一张0.8M的优化图片就不会再报OOM异常了。
不珍惜内存导致的OOM分析:
上面说了内存泄露导致的OOM异常,下面我们再来看一幅图(数据和场景描述有夸张,请忽略),如下:
 
可见,这种类型的OOM就很好定位原因了,一般都可以从OOM后的log中得出分析定位。
如下例子,我们在Activity中的ImageView放置一张未优化的特大的(30多M)高清图片,运行直接崩溃如下:
//抛出OOM异常
10-10 09:01:04.873 11703-11703/? E/art: Throwing OutOfMemoryError "Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"
10-10 09:01:04.940 11703-11703/? E/art: Throwing OutOfMemoryError "Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM"
//堆栈打印
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: FATAL EXCEPTION: main
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: Process: com.example.application, PID: 11703
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.application/com.example.myapplication.MainActivity}: android.view.InflateException: Binary XML file line #21: Error inflating class <unknown>
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2610)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2684)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.access$800(ActivityThread.java:177)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1542)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:111)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:194)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5743)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
//出错地点,原因是21行的ImageView设置的src是一张未优化的31M的高清图片
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:  Caused by: android.view.InflateException: Binary XML file line #21: Error inflating class <unknown>
10-10 09:01:04.958 11703-11703/? E/AndroidRuntime:     at android.view.LayoutInflater.createView(LayoutInflater.java:633)
通过上面的log可以很方便的看出来问题原因所在地,那接下来的做法就是优化呗,降低图片的相关规格即可(譬如使用BitmapFactory的Option类操作等)。
PS:提醒一句的是记得应用所属的内存是区分Java堆和native堆的!
3-3-3 Android应用规避内存溢出OOM建议
还是那句话,等待OOM发生是为时已晚的事,我们应该将其扼杀于萌芽之中,至于如何在开发中规避OOM,如下给出一些我们应用开发中的常用的策略建议:
时刻记得不要加载过大的Bitmap对象;譬如对于类似图片加载我们要通过BitmapFactory.Options设置图片的一些采样比率和复用等,具体做法点我参考官方文档,不过过我们一般都用fresco或Glide开源库进行加载。
优化界面交互过程中频繁的内存使用;譬如在列表等操作中只加载可见区域的Bitmap、滑动时不加载、停止滑动后再开始加载。
有些地方避免使用强引用,替换为弱引用等操作。
避免各种内存泄露的存在导致OOM。
对批量加载等操作进行缓存设计,譬如列表图片显示,Adapter的convertView缓存等。
尽可能的复用资源;譬如系统本身有很多字符串、颜色、图片、动画、样式以及简单布局等资源可供我们直接使用,我们自己也要尽量复用style等资源达到节约内存。
对于有缓存等存在的应用尽量实现onLowMemory()和onTrimMemory()方法。
尽量使用线程池替代多线程操作,这样可以节约内存及CPU占用率。
尽量管理好自己的Service、Thread等后台的生命周期,不要浪费内存占用。
尽可能的不要使用依赖注入,中看不中用。
尽量在做一些大内存分配等可疑内存操作时进行try catch操作,避免不必要的应用闪退。
尽量的优化自己的代码,减少冗余,进行编译打包等优化对齐处理,避免类加载时浪费内存。
可以发现,上面只是列出了我们开发中常见的导致OOM异常的一些规避原则,还有很多相信还没有列出来,大家可以自行追加参考即可。
3-4 Android内存性能优化总结
无论是什么电子设备的开发,内存问题永远都是一个很深奥、无底洞的话题,上面的这些内存分析建议也单单只是Android应用开发中一些常见的场景而已,真正的达到合理的优化还是需要很多知识和功底的。
合理的应用架构设计、设计风格选择、开源Lib选择、代码逻辑规范等都会决定到应用的内存性能,我们必须时刻头脑清醒的意识到这些问题潜在的风险与优劣,因为内存优化必须要有一个度,不能一味的优化,亦不能置之不理。
4 Android应用API使用及代码逻辑性能分析
在我们开发中除过常规的那些经典UI、内存性能问题外其实还存在很多潜在的性能优化、这种优化不是十分明显,但是在某些场景下却是非常有必要的,所以我们简单列举一些常见的其他潜在性能优化技巧,具体如下探讨。
4-1 Android应用String/StringBuilder/StringBuffer优化建议
字符串操作在Android应用开发中是十分常见的操作,也就是这个最简单的字符串操作却也暗藏很多潜在的性能问题,下面我们实例来说说。
先看下面这个关于String和StringBuffer的对比例子:
//性能差的实现
String str1 = "Name:";
String str2 = "GJRS";
String Str = str1 + str2;
//性能好的实现
String str1 = "Name:";
String str2 = "GJRS";
StringBuffer str = new StringBuilder().append(str1).append(str2);
通过这个例子可以看出来,String对象(记得是对象,不是常量)和StringBuffer对象的主要性能区别在于String对象是不可变 的,所以每次对String对象做改变操作(譬如“+”操作)时其实都生成了新的String对象实例,所以会导致内存消耗性能问题;而 StringBuffer对象做改变操作每次都会对自己进行操作,所以不需要消耗额外的内存空间。
我们再看一个关于String和StringBuffer的对比例子:
//性能差的实现
StringBuffer str = new StringBuilder().append("Name:").append("GJRS");
//性能好的实现
String Str = "Name:" + "GJRS";
1
2
3
4
在这种情况下你会发现StringBuffer的性能反而没有String的好,原因是在JVM解释时认为
String Str = "Name:" + "GJRS";就是String Str = "Name:GJRS";,所以自然比StringBuffer快了。
可以发现,如果我们拼接的是字符串常量则String效率比StringBuffer高,如果拼接的是字符串对象,则StringBuffer比 String效率高,我们在开发中要酌情选择。当然,除过注意StringBuffer和String的效率问题,我们还应该注意另一个问题,那就是 StringBuffer和StringBuilder的区别,其实StringBuffer和StringBuilder都继承自同一个父类,只是 StringBuffer是线程安全的,也就是说在不考虑多线程情况下StringBuilder的性能又比StringBuffer高。
PS:如果想追究清楚他们之间具体细节差异,麻烦自己查看实现源码即可。
4-2 Android应用OnTrimMemory()实现性能建议
OnTrimMemory是Android 4.0之后加入的一个回调方法,作用是通知应用在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验(冷启动速度是热启动的 2~3倍)。系统会根据当前不同等级的内存使用情况调用这个方法,并且传入当前内存等级,这个等级有很多种,我们可以依据情况实现不同的等级,这里不详细 介绍,但是要说的是我们应用应该至少实现如下等级:
TRIM_MEMORY_BACKGROUND
内存已经很低了,系统准备开始根据LRU缓存来清理进程。这时候如果我们手动释放一些不重要的缓存资源,则当用户返回我们应用时会感觉到很顺畅,而不是重新启动应用。
可以实现OnTrimMemory方法的系统组件有Application、Activity、Fragement、
Service、ContentProvider;关于OnTrimMemory释放哪些内存其实在架构阶段就要考虑清楚哪些对象是要常驻内存的,哪些是伴随组件周期存在的,一般需要释放的都是缓存。
如下给出一个我们项目中常用的例子:
@Override
public void onTrimMemory(int level) {
   if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
       clearCache();
   }
}
通常在我们代码实现了onTrimMemory后很难复显这种内存消耗场景,但是你又怕引入新Bug,想想办法测试。好在我们有一个快捷的方式来模拟触发该水平内存释放,如下命令:
adb shell dumpsys gfxinfo packagename -cmd trim value
1
packagename为包名或者进程id,value为ComponentCallbacks2.java里面定义的值,可以为80、60、40、20、5等,我们模拟触发其中的等级即可。
4-3 Android应用HashMap与ArrayMap及SparseArray优化建议
在Android开发中涉及到数据逻辑部分大部分用的都是Java的API(譬如HashMap),但是对于Android设备来说有些Java的 API并不适合,可能会导致系统性能下降,好在Google团队已经意识到这些问题,所以他们针对Android设备对Java的一些API进行了优化, 优化最多就是使用了ArrayMap及SparseArray替代HashMap来获得性能提升。
HashMap:
HashMap内部使用一个默认容量为16的数组来存储数据,数组中每一个元素存放一个链表的头结点,其实整个HashMap内部结构就是一个哈希 表的拉链结构。HashMap默认实现的扩容是以2倍增加,且获取一个节点采用了遍历法,所以相对来说无论从内存消耗还是节点查找上都是十分昂贵的。
SparseArray:
SparseArray比HashMap省内存是因为它避免了对Key进行自动装箱(int转Integer),它内部是用两个数组来进行数据存储 的(一个存Key,一个存Value),它内部对数据采用了压缩方式来表示稀疏数组数据,从而节约内存空间,而且其查找节点的实现采用了二分法,很明显可 以看见性能的提升。
ArrayMap:
ArrayMap内部使用两个数组进行数据存储,一个记录Key的Hash值,一个记录Value值,它和SparseArray类似,也会在查找时对Key采用二分法。
有了上面的基本了解我们可以得出结论供开发时参考,当数据量不大(千位级内)且Key为int类型时使用SparseArray替换HashMap 效率高;当数据量不大(千位级内)且数据类型为Map类型时使用ArrayMap替换HashMap效率高;其他情况下HashMap效率相对高于二者。
4-4 Android应用ContentProviderOperation优化建议
ContentProvider是Android应用开发的核心组件之一,有时候在开发中需要使用ContentProvider对多行数据进行操 作,我们的做法一般是多次调运相关操作方法,殊不知这种实现方式是非常低性能的,取而代之的做法应该是使用批量操作,具体为了使批量更新、插入、删除数据 操作更加方便官方提供了ContentProviderOperation工具类。所以在我们开发中遇到类似情景时请务必使用批量操作,具体的优势如下:
所有的操作都在一个事务中执行,可以保证数据的完整性。
批量操作在一个事务中执行,所以只用打开、关闭一个事务。
减轻应用程序与ContentProvider间的多次频繁交互,提升性能。
可以看见,这对于数据库操作来说是一个非常有用的优化措施,烦请务必重视(我们项目优化过,的确有很大提升)。
4-5 Android应用其他逻辑优化建议
关于API及逻辑性能优化其实有多知识点的,这里无法一一列出,只能给出一些重要的知识点,下面再给出一些常见的优化建议:
避免在Android中使用Java的枚举类型,因为编译后不但占空间,加载也费时,完全没有static final的变量好用、高效。
Handler发送消息时尽量使用obtain去获取已经存在的Message对象进行复用,而不是新new Message对象,这样可以减轻内存压力。
在使用后台Service时尽量将能够替换为IntentService的地方替换为此,这样可以减轻系统压力、省电、省内存、省CPU占用率。
在当前类内部尽量不要通过自己的getXXX、setXXX对自己内部成员进行操作,而是直接使用,这样可以提高代码执行效率。
不要一味的为了设计模式而过分的抽象代码,因为代码抽象系数与代码加载执行时间成正比。
尽量减少锁个数、减小锁范围,避免造成性能问题。
合理的选择使用for循环与增强型for循环,譬如不要在ArrayList上使用增强型for循环等。
哎呀,类似的小优化技巧有很多,这里不一一列举了,自行发挥留意即可。
5 Android应用移动设备电池耗电性能分析
有了UI性能优化、内存性能优化、代码编写优化之后我们在来说说应用开发中很重要的一个优化模块—–电量优化。
5-1 Android应用耗电量概念
在盒子等开发时可能电量优化不是特别重视(视盒子待机真假待机模式而定),但是在移动设备开发中耗电量是一个非常重要的指标,如果用户一旦发现我们的应用非常耗电,不好意思,他们大多会选择卸载来解决此类问题,所以耗电量是一个十分重要的问题。
关于我们应用的耗电量情况我们可以进行定长时间测试,至于具体的耗电量统计等请参考此文,同时我们还可以直接通过Battery Historian Tool来查看详细的应用电量消耗情况。最简单常用办法是通过命令直接查看,如下:
adb shell dumpsys batterystats
1
其实我们一款应用耗电量最大的部分不是UI绘制显示等,常见耗电量最大原因基本都是因为网络数据交互、GPS定位、大量内存性能问题、冗余的后台线程和Service等造成。
5-2 Android应用耗电量优化建议
优化电量使用情况我们不仅可以使用系统提供的一些API去处理,还可以在平时编写代码时就养成好的习惯。具体的一些建议如下:
在需要网络的应用中,执行某些操作前尽量先进行网络状态判断。
在网络应用传输中使用高效率的数据格式和解析方法,譬如JSON等。
在传输用户反馈或者下载OTA升级包等不是十分紧急的操作时尽量采用压缩数据进行传输且延迟到设备充电和WIFI状态时进行。
在有必要的情况下尽量通过PowerManager.WakeLock和JobScheduler来控制一些逻辑操作达到省电优化。
对定位要求不太高的场景尽量使用网络定位,而不是GPS定位。
对于定时任务尽量使用AlarmManager,而不是sleep或者Timer进行管理。
尽可能的减少网络请求次数和减小网络请求时间间隔。
后台任务要尽可能少的唤醒CPU,譬如IM通信的长连接心跳时间间隔、一些应用的后台定时唤醒时间间隔等要设计合理。
特殊耗电业务情况可以进行弹窗等友好的交互设计提醒用户该操作会耗用过多电量。
可以看见,上面只是一些常见的电量消耗优化建议。总之,作为应用开发者的我们要意识到电量损耗对于用户来说是非常敏感的,只有我们做到合理的电量优化才能赢得用户的芳心。
6 Android应用开发性能优化总结
性能优化是一个很大的话题,上面我们谈到的只是应用开发中常见的性能问题,也是应用开发中性能问题的冰山一角,更多的性能优化技巧和能力不是靠看出 来,而是靠经验和实战结果总结出来的,所以说性能优化是一个涉及面非常广的话题,如果你想对你的应用进行性能你必须对你应用的整个框架有一个非常清晰的认 识。
当然了,如果在我们开发中只是一味的追求各种极致的优化也是不对的。因为优化本来就是存在风险的,甚至有些过度的优化会直接导致项目的臃肿,所以不要因为极致的性能优化而破坏掉了你项目的合理架构。
总之一句话,性能优化适可而止,请酌情优化。
PS:附上Google关于Android开发的一些专题建议视频链接,不过在天朝需要自备梯子哦。








































http://android.jobbole.com/81944/
给 App 提速:Android 性能优化总结
我在几周前的 Droidcon NYC 会议上,做了一个关于 Android 性能优化的报告。
我花了很多时间准备这个报告,因为我想要展示实际例子中的性能问题,以及如何使用适合的工具去确认它们 。但由于没有足够时间来展示所有的一切,我不得不将幻灯片的内容减半。在本文中,将总结所有我谈到的东西,并展示那些我没有时间讨论的例子。
你可以在这里观看报告视频。
幻灯片在这里可以看到。
现在,让我们仔细查看一些我之前谈过的重要内容 ,但愿我可以非常深入地解释一切。那就先从我在优化时遵循的基本原则开始:
我的原则
每当处理或者排查性能问题的时候,我都遵循这些原则:
持续测量: 用你的眼睛做优化从来就不是一个好主意。同一个动画看了几遍之后,你会开始想像它运行地越来越快。数字从来都不说谎。使用我们即将讨论的工具,在你做改动的前后多次测量应用程序的性能。
使用慢速设备:如果你真的想让所有的薄弱环节都暴露出来,慢速设备会给你更多帮助。有的性能问题也许不会出现在更新更强大的设备上,但不是所有的用户都会使用最新和最好的设备。
权衡利弊 :性能优化完全是权衡的问题。你优化了一个东西 —— 往往是以损害另一个东西为代价的。很多情况下,损害的另一个东西可能是查找和修复问题的时间,也可能是位图的质量,或者是应该在一个特定数据结构中存储的大量数据。你要随时做好取舍的准备。
Systrace
Systrace 是一个你可能没有用过的好工具。因为开发者不知道要如何利用它提供的信息。
Systrace 告诉我们当前大致有哪些程序运行在手机上。这个工具提醒我们,手中的电话实际上是一个功能强大的计算机,它可以同时做很多事情。在SDK 工具的最近的更新中,这个工具增强了从数据生成波形图的功能,这个功能可以帮助我们找到问题。让我们观察一下,一个记录文件长什么样子:
 
你可以用 Android Device Monitor 工具或者用命令行方式产生一个记录文件。在这里可以找到更多的信息。
我在视频中解释了不同的部分。其中最有意思的就是警报(Alert)和帧(Frame),展示了对搜集数据的分析。让我们观察一个采集到的记录文件,在顶部选择一个警报:
 
这个警报报告了有一个 View#draw() 调用费时较多。我们得到关于告警的描述,其中包含了关于这个主题的文档链接甚至是视频链接。检查帧下面那一行,我们看到绘制的每一帧都有一个标识,被标成 为绿色、黄色或者红色。如果标识是红色,就说明这帧在绘制时有一个性能问题。让我们选取一个红色的帧:
 
我们在底部看到所有这帧相关的警报。一共有三个,其中之一是我们之前看到的。让我们放大这个帧并在底部把 “Inflation during ListView recycling” 这个警报报展开:
 
我们看到这部分一共耗时 32 毫秒,超出了每分钟 60 帧的要求,这种情况下绘制每一帧的时间不能超过 16 毫秒。帧中 ListView 的每一项都有更多的时间信息 —— 每一项耗时 6 毫秒,我们一共有 5 项。其中的描述帮助我们理解这个问题,甚至还提供了一个解决方案。从上面的图中,我们看到所有内容都是可视化的,甚至可以放大“扩展” (“inflate”)片,来观察布局中的哪个视图(“View”)扩展时花了更久的时间。
另一个帧绘制较慢的例子:
 
选择一帧后,我们可以按下“m” 键来高亮并观察这部分花了多久。上图中,我们观察到绘制这帧花费了 19 毫秒。展开这帧对应的唯一警报,它告诉我们有一个“调度延迟”。
调度延迟说明这个处理特定时间片的线程有很长时间没有被 CPU 调度。因此这个线程花了很长时间才完成。选择帧中最长的时间片以便获取更多的详细信息:
 
墙上时间(Wall Duration)是指从时间片开始到结束所花费的时间。它被称为“墙上时间”,这是因为线程启动后就像观察一个挂钟(去记录这个时间)。
CPU时间是 CPU 处理这个时间片所花费的实际时间。
值得注意的是这两个时间有很大的不同。完成这个时间片花了 18 毫秒,但是 CPU 却只花费了 4 毫秒。这有点奇怪,现在是个好机会来抬头看看这整段时间里 CPU 都做了什么:
 
CPU 的 4 个核心都相当忙碌。
选择一个com.udinic.keepbusyapp 应用程序中的线程。这个例子中,一个不同的应用程序让 CPU 更加忙碌,而不是为我们的应用程序贡献资源。
这种特殊场景通常是暂时的,因为其它的应用程序不会总是在后台独占 CPU(对吗?)。
Traceview
Traceview 是一个性能分析工具,告诉我们每一个方法执行了多长时间。让我们看一个跟踪文件:
 
这个工具可以通过 Android Device Monitor 或者从代码中启动。更多信息请参考这里。
让我们仔细查看这些不同的列:
名称:此方法的名字,上图中用不同的颜色加以标识。
CPU非独占时间:此方法及其子方法所占用的 CPU 时间(即所有调用到的方法)。
CPU独占时间:此方法单独占用 CPU 的时间。
非独占和独占的实际时间 :此方法从启动那一刻直到完成的时间。和 Systrace 中的“墙上时间”一样。
调用和递归:此方法被调用的次数以及递归调用的数量。
每次调用的 CPU 时间和实际时间 :平均每次调用此方法的 CPU 时间和实际时间。另一个时间字段显示了所有调用这个方法的时间总和。
我打开一个滑动不流畅的应用程序。我启动追踪,滑动了一会然后关掉追踪。找到 getView() 这个方法然后把它展开,我看到下面的结果:
 
此方法被调用了 12 次,每次调用 CPU 花费的时间是 3 毫秒,但每次调用实际花费的时间是 162 毫秒!这一定有问题……
查 看了这个方法的子方法,可以看到总体时间都花费在哪些方法上。Thread.join() 占了 98% 左右的非独占实际时间。此方法用在等待其他线程结束。另一个子方法是 Thread.start(),我猜想 getView() 方法启动了一个线程然后等着它执行结束。
但这个线程在哪里呢?
因为 getView() 不直接做这件事情,所以 getView() 没有这样的子线程。为找到它 ,我查找一个 Thread.run() 方法,这是生成一个新线程所调用的方法。我追踪这个方法直至找到元凶:
 
我发现每次调用 BgService.doWork() 方法大约花费 14 毫秒,一共调用了 40 次 。每次 getView() 都有可能不止一次调用它,这就可以解释为什么每次调用 getView() 需要花费这么长时间。此方法让 CPU 长时间处于忙碌状态。再查看一下 CPU 独占 时间,我们看到它                      

http://blog.csdn.net/innost/article/details/9008691

Android系统性能调优工具介绍
Android系统性能调优工具介绍
在软件开发过程中,想必很多读者都遇到过系统性能问题。而解决系统性能问题的几个主要步骤是:
测评:对系统进行大量有针对性的测试,以得到合适的测试数据。
分析系统瓶颈:分析测试数据,找到其中的hotspot(热点,即bottleneck)。
性能优化:对hotspot相关的代码进行优化。
由上述步骤可知,性能优化的目标对象是hotspot。如果找到的hotspot并非真正的热点,则性能优化的结果必然是事倍功半甚至竹篮打水一场空。所以,作为Android性能调优相关知识的第一部分,本篇首先将向读者介绍Android平台中三个重要的性能测试工具,它们能很好得帮助开发者找到hotspot。
 
一Traceview介绍
1.1  Traceview简介
Traceview是Android平台特有的数据采集和分析工具,它主要用于分析Android中应用程序的hotspot。Traceview本身只是一个数据分析工具,而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下:
开 发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数,并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过 程中将采集运行时间内该应用所有线程(注意,只能是Java线程)的函数执行情况,并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然 后需要利用SDK中的Traceview工具来分析这些数据。
借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如图1-1所示。
 
图1-1  DDMS中Traceview使用示意图
点击图1-1中所示按钮即可以采集目标进程的数据。当停止采集时,DDMS会自动触发Traceview工具来浏览采集数据。
下面,我们通过一个示例程序向读者介绍Debug类以及Traceview的使用。
1.2  Traceview示例分析
示例程序运行时的界面如图1-2所示:
 
图1-2  示例界面图
图1-2中:
SystraceDemoStringAAA等字样是TraceviewDemo程序启动时ListView最初显示的字符串。
当用户点击ListView中的某一项时,Traceview将计算对应项当前显示的字符串的MD5值40次,然后用计算得到的MD5字符串替换该项之前显示的内容。其效果如图1-2中的“MD5值“箭头所示。
该示例的关键代码如图1-3所示:
 
图1-3示例代码
由图1-3可知:
左图中,Debug类的startMethodTracing和stopMethodTracing分别在MainAcvtivity的构造方法和onDestroy函数中调用。
onCreate函数中我们设置了第一个hotspot,即getStringToShow函数。它将解析一个XML文件,并将解析后的字符串保存到mListItem中以作为ListView的显示内容。
右图中,当用户点击ListView中的某个Item时,程序在onListItem中将计算MD5值40次,然后用计算结果做为被点击项的新字符串显示。generateMD5中的函数是本示例的第二个hotspot。
现在,我们用Traceview工具将测试结果文件TraceviewDemo.trace打开。
Traceview界面比较复杂,其UI划分为上下两个面板,即Timeline Panel(时间线面板)和Profile Panel(分析面板)。图1-4所示为Timeline Panel界面:
 
图1-4  Traceview Timeline Panel示意图
图1-4中的Timeline Panel又可细分为左右两个Pane:
左边Pane显示的是测试数据中所采集的线程信息。由图1-4可知,本次测试数据采集了main线程,两个Binder线程和其它系统辅助线程(例如GC线程等)的信息。
右边Pane所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图1-4可知,main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
另外,开发者可以在时间线Pane中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
现在来看Traceview的Profile Panel界面,如图1-5所示:
 
图1-5  TraceviewProfile Panel界面
Profile Panel是Traceview的核心界面,其内涵非常丰富。它主要展示了某个线程(先在Timeline Panel中选择线程)中各个函数调用的情况,包括CPU使用时间、调用次数等信息。而这些信息正是查找hotspot的关键依据。所以,对开发者而言,一定要了解Profile Panel中各列的含义。笔者总结了其中几个重要列的作用,如表1-1所示:
表1-1  Profile Panel各列作用说明
 
列名    描述       
Name    该线程运行过程中所调用的函数名       
Incl Cpu Time    某函数占用的CPU时间,包含内部调用其它函数的CPU时间       
Excl Cpu Time    某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间       
Incl Real Time    某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间       
Excl Real Time    某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间       
Call+Recur Calls/Total    某函数被调用次数以及递归调用占总调用次数的百分比       
Cpu Time/Call    某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间       
Real Time/Call    同CPU Time/Call类似,只不过统计单位换成了真实时间    
另外,每一个Time列还对应有一个用时间百分比来统计的列(如Incl Cpu Time列对应还有一个列名为Incl Cpu Time %的列,表示以时间百分比来统计的Incl Cpu Time)。
了解完Traceview的UI后,现在介绍如何利用Traceview来查找hotspot。
一般而言,hotspot包括两种类型的函数:
一类是调用次数不多,但每次调用却需要花费很长时间的函数。在示例代码中,它就是hotspot 1。
一类是那些自身占用时间不长,但调用却非常频繁的函数。在示例代码中,它就是hotspot 2。
首先,我们来查找hotspot 1。
在Profile Panel中,选择按Cpu Time/Call进行降序排序(从上之下排列,每项的耗费时间由高到低),得到如图1-6所示的结果:
 
图1-6  按CPU Time/Call降序排列数据
图1-6中:
MainActivity.onCreate是应用程序中的函数,它耗时为4618.684。然后,点击MainActivity.onCreate项,得到箭头所示的小图。
小图中,Parents一行显示的是MainActivity.onCreate的调用者,本例中它是performCreate函数。这部分代码属于Framework部分。Children行显示的是MainActivity.onCreate调用的子函数。
在 MainActivity.onCreate调用的子函数中,我们发现getStringsToShow在Incl Cpu Time %一列中占据了63.3%,它是onCreate子函数耗费时间最长的,而且Calls+Recur Calls/Total列显示其调用次数为1,即它仅仅被调用一次了。这个函数是应用程序实现的,所以极有可能是一个潜在的Hotspot。
另外,由于笔者已经知道getStringsToShow是示例应用自己实现的函数,故在图1-6的大图中,可直接根据MainActivity.getStringsToShow花费了2921.913CPU时间这个信息来确定Hotspot就是它。
相对来说,类型1的hotspot比较好找,步骤是先按降序对时间项进行排列(可以是时间百分比、真实时间或CPU时间),然后查找耗费时间最多的函数。一般而言,先应对应用程序自己实现的函数进行排查,Framework的函数也有可能是hotspot,但主因一般还是在应用本身(例如设置复杂的界面,导致对应XML解析非常慢)。
现在,我们来看如何查找类型2的hotspot。
点击Call/Recur Calls/Total列头,使之按降序排列。关注点放在那些调用频繁并且占用资源较多的函数。图1-7为降序排列的结果图。
 
图1-7类型2 Hotspot查找过程示意之一
图1-7所示的运行最频繁的几个函数中,我们发现了几个怀疑点,由图中的1和2箭头标示。
结 合代码,箭头1所指的函数在代码中实际并不存在。这是因为代码中直接访问了内部类的私有成员,导致java编译器在编译时自动生成了这个函数。这个函数的 调用次数非常多。所以,为了提高效率,我们可以修改内部类成员的访问类型定义为public。不过,该函数的Incl Cpu Time并不高,只有3.2%。
同样,箭头2所指部分的函数调用次数也很多,达到了5888多次。不过它们占用的时间百分比只有0.9%。
第一次查找的潜在点被排除后,继续浏览数据,得到如图1-8所示的结果。
 
图1-8  类型2 Hotspot查找过程示意之二
在图1-8中:
红框处有两个重载的MyMD5.getHashString函数调用,它们各运行了368次,而且占用的CPU时间百分比达到了31.8%和53.2%。很显然,这2处调用就有优化的余地,这就是我们所怀疑的hotspot2。
找到hotspot之后,开发者就需要结合代码来进行对应的优化了。关于Java代码优化,读者可参考如下资料:http://developer.android.com/training/articles/perf-tips.html
总体而言,Hotspot的查找是一个细致的工作,需要开发者对目标程序的代码,以及Traceview工具都比较熟悉才行。
1.3  Traceview小结
Traceview工具是Android平台应用程序性能分析的利器。不过笔者觉得它的UI还是有些复杂。并且使用时感觉流畅度不够好。
Google官方关于Traceview的介绍可参考以下链接,不过其内容以及较久未更新了。http://developer.android.com/tools/debugging/debugging-tracing.html。
 
二Systrace介绍
2.1  Systrace简介
Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务)的运行信息,从而帮助开发者更直观的分析系统瓶颈,改进性能。
Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中,它主要由3部分组成:
内核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用Systrace的话,必须开启kernel中和ftrace相关的模块。
数据采集部分:Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时,Android还有一个atrace程序,它可以从ftrace中读取统计信息然后交给数据分析工具来处理。
数 据分析工具:Android提供一个systrace.py(python脚本文件,位于Android SDK目录/tools/systrace中,其内部将调用atrace程序)用来配置数据采集的方式(如采集数据的标签、输出文件名等)和收集 ftrace统计数据并生成一个结果网页文件供用户查看。
从本质上说,Systrace是对Linux Kernel中ftrace的封装。应用进程需要利用Android提供的Trace类来使用Systrace。Android 4.1为系统中的几个关键进程和模块都添加了Systrace功能。以显示系统中重要模块Hwcomposer为例,其代码中使用Systrace的方法如图2-1所示:
 
图2-1  Hwcomposer模块Systrace使用示例
图2-1中,应用程序只要通过三个宏就可使用Systrace了:
定义ATRACE_TAG:Hwcomposer使用了ATRACE_TAG_GRAPHICS,表示它和Graphics相关。
ATRACE_INIT:用于统计某个变量使用的情况。下文将见到代码中”VSYNC”的统计结果。
ATRACE_CALL:用于统计函数的调用情况。
由于篇幅关系,关于Trace使用更多的信息请读者阅读frameworks/native/include/utils/Trace.h或者android.os.Trace类。下面,我们通过一个示例来展示Systrace的使用。
2.2  Systrace实例
首先,在PC机上运行如下命令以启动Systrace,如图2-2所示:
 
图2-2  Systrace操作步骤
执行上述命令后,将得到一个名为trace.html的文件(trace.html是默认文件名,读者也可在命令行中指定其他文件名)。通过浏览器打开此文件,结果如图2-3所示:
 
图 2-3  trace.html内容示意
图2-3中所示的trace.html页面内容和Traceview的Timeline Panel非常类似。图中包含的内容如下:
由 于在systrace.py中指定了-f -l和-i参数,Systrace将生成CPU频率、负载和状态相关的信息。它们为图2-1中第一个红框所示。由于笔者所测手机CPU为双核,故图中有 CPU 0和CPU 1之分。为行文方便,笔者用CPU N来指代CPU的某个核。
“CPU N“所示行对应于整个测试时间内,某个核上运行的进程信息。
“CPU N C-State“所示行为整个测试时间内,某个CPU状态的变化。C-State取值见表2-1。
“CPU N Clock Frequency”所示行展示了某个CPU运行的频率。通过点击这一行的色块可以查看某个时间点上CPU N的运行频率。
“cpufreq”: 该行所示内容和CPU交互式频率调节器(Interactive Governor)的工作有关。交互式CPU调节器驱动添加了对CPU频率调节事件的跟踪。感兴趣的读者不妨阅读kernel中的 include/trace/events/cpufreq_interactive.h文件以了解更多的信息。
图2-1中,CPU信息以下的行就是通过Trace.h提供的宏而添加的统计信息,其中:
VSYNC:这一行的统计信息来自于图2-1中ATRACE_INIT宏的使用。在Hwcomposer代码中,ATRACE_INIT宏被用于统计VSYNC[1]的Tick-Tack情况(即0,1,0,1交错输出)。VSYNC行显示了每次Tick Tack的时间大概都在16ms左右。
由 于Framework代码也在显示部分添加了ATRACE_INIT的使用,所以图中 com.example.systracedemo/com.example.systracedemo.MainActivity所示为应用程序占用显 示Buffer的Tick-Tack情况。如果使用时间超过16ms,将导致界面显示迟滞等现象。
SurfaceFlinger使用了ATRACE_CALL宏,故图中SurfaceFlinger行展示了其函数调用的CPU耗时情况(如箭头1所指,SurfaceFlinger中的onMessageReceived函数的运行信息)。
在图2-1最下部的方框中,详细显示了当前鼠标在时间线中选择的部分(即SurfaceFlinger中的onMessageReceived)的详细信息。
表2-1所示为CPU状态取值信息:
表2-1  CPU状态
 
C-state    描述       
C-0    RUN MODE,运行模式。       
C-1    STANDBY,就位模式,随时准备投入运行       
C-2    DORMANT,休眠状态,被唤醒投入运行时有一定的延迟       
C-3    SHUTDOWN,关闭状态,需要有较长的延迟才能进入运行状态,减少耗电    
2.3  Systrace小结
总体来说,Systrace比Traceview用途更广泛,它支持对CPU、Native进程甚至Kernel线程进行性能数据采样,可帮助开发者对整个系统的性能情况进行一个详尽的分析。不过其用法比Traceview要复杂,而且还需要对Kernel做一些配置调整。
Android官方对Systrace也有一些介绍,请读者阅读:
http://developer.android.com/tools/debugging/systrace.html
三Oprofile的使用
3.1  Oprofile简介
Oprofile是另一个功能更强大的性能数据采集和分析工具,其工作原理如下:
它利用性能计数器(Performance Counter)或者定时器(针对kernel不支持性能计数器的情况),通过连续的采样获得统计数据,从而对内核和用户空间进程进行性能分析。
以 性能计数器为例,在系统运行过程中,当某个事件发生时,对应的性能计数器就会自加。当达到计数器的设定值时会产生一个中断。Oprofile驱动利用这个 中断来进行采样统计。通过获取中断发生时PC指针的值以及内核中保存运行的任务的信息等,并把它们转化成对测评有用的数据。
Oprofile包括内核驱动和用户空间工具两个部分,其中:
内 核驱动实现了一个oprofilefs虚拟文件系统。它挂载到/dev/oprofile,用来向用户空间报告数据和接收来自用户空间的设置。它是用户空 间进程与内核通信的桥梁。驱动中还包括了与架构相关和通用的驱动,通过它们访问性能计数器寄存器、收集数据后报告给用户空间。守护进程用户从内核接收数据 并保存在磁盘上以备分析使用。
在用户空间提供了两个工具:oprofiled(作为守护进程在后台通过和/dev/oprofile交互以获取驱动收集的数据)、opcontrol(用户操作的控制工具,它通过读写oprofilefs来控制采样相关的设置)。
Android默认提供了对Oprofile的支持,其组成包括:
代码:位于exetrnal/oprofile中。不过,只有编译类型为非user的系统才会使用它。
四个主要工具,即opcontrol,oprofiled、opreport和opimport。开发者只要使用opcontrol和opreport即可。
读者应该熟练掌握opcontrol和oprofiled工具的作用,我们此处也总结了它们的用法:
opcontrol:它用来控制采样过程,比如采样的开始和结束、采样的事件类型和频率等。其内部通过读写oprofilefs来实现。opcontrol的常用选项如表3-1所示:
表3-1  opcontrol常用选项
 
opcontrol选项    功能       
--list-events    列出当前CPU所支持的事件       
--setup    对测评进行设置,比如关闭旧的守护进程、挂载oprofilefs       
--vmlinux=    设置将要分析的Android内核镜像文件       
--callgraph    设置跟踪函数调用的层数       
--kernel-range=start,end    内核二进制文件起始和结束的虚拟地址       
--start/--stop    开始/停止采样       
--event=name:count:unitmask:kernel:user    设置对某事件进行采样。
Name:事件的名字
Count:采样时事件发生的次数Unitmask:事件的掩码(CPU支持的事件以及掩码见oprofile的文档)
Kernel:是否采样内核事件
User:是否采样用户事件    
opreport:opreport 是使用采样数据生成报告的工具,可根据用户要求生成不同的报告。一般用法是“opreport [options] [image]”,其中image指定报告需要显示的程序的名字(指程序名字、共享库名字和内核)。image参数可选。不指定它时,opreport将 打印所有进程的报告结果。常用options如表3-2所示:
表3-2  opreport常用选项
 
opreprt选项    功能       
-l    显示函数调用的符号名字       
-g    以调试的形式打印函数符号,包括函数所在文件及行数等。       
-c    显示函数调用堆栈       
-o    报告输出到指定文件    
另外,Android提供了一个特别的工具opimport_pull。它可把采样数据从手机中pull到PC上,并对数据进行一些简单处理以供opreport使用。所以,在Android平台上,开发者只要使用opimport_pull了就可以了。
现在,我们来看Oprofile的使用实例。
3.2  Oprofile实例
Oprofile的使用大体可以分成以下三步:
内核加载oprofile驱动(如果该驱动静态编译到内核中,则可略过此步骤)。
配置采样事件、然后进行采样。
获取报告、进行分析,针对分析结果进行改进。
下面分别来看这三个步骤:
3.2.1  Oprofile内核配置
如下所示为内核配置的示例,如图3-1所示:
 
图3-1  Oprofile内核配置示意
运行Oprofile需要root权限,所以目标设备中最好运行的是userdebug或者engineer版本的Android OS。
3.2.2  Oprofile用户空间配置
Oprofile用户空间配置的示例如图3-2所示。假设当前目录为Android源码根目录,并且已经初始化Android编译环境(执行完毕build/envsetup.sh和lunch)。
 
图3-2  Oprofile用户空间配置示意
用户空间的配置主要通过执行opcontrol命令来完成。而opcontrol内部是通过往oprofilefs传递对应的控制参数来完成的。例如图3-2中“opcontrol --callgraph=16”命令也可通过“echo 16> /dev/oprofile/backtrace_depth”来实现。
3.2.3  结果分析
在上一步中,我们已经获取了测评采样的数据。现在,就可以使用它们来生成采样报告了,方法如图3-3所示:
 
图3-3  oprofile生成采样报告方法示意
图3-4为报告的一部分内容:
 
图3-4  Oprofile测评报告概要
图3-4中,我们发现libc.so调用的采样数为117299,排第4位。那么libc.so中哪个函数调用次数最多呢?开发者可通过如下命令获取libc.so的更为详细的信息。方法如图3-5所示:
 
图3-5  opreport使用示例
执行上述命令后的结果如图3-6所示:
 
图3-6  Oprofile关于libc的详细结果
由图3-6可知,memcpy()函数占用最多的CPU资源。所以可以考虑优化memcpy()。
此处,笔者针对Cortex-A9双核SMP处理器,使用ARM汇编的方法对memcpy进行了优化。优化后的结果如图3-7所示。对比图3-6和图3-7可明显看出,优化后的memcpy对资源的占用降低了2.7个百分点。
 
图3-7  优化memcpy()后的测试结果
3.3  Oprofile小结
在性能分析中,Oprofile无疑是一个使用最广泛、功能最强大的测评工具。对于Android平台开发者来说,它可以采集和分析整个系统的运行状态信息,对于分析查找系统瓶颈进而优化系统具有重大意义。
 
四  总结
性能调优向来是一件“高深莫测”的任务,但打破它们神秘面纱的工具就是上文所述的工具了。所以,对有志开展这方面工作的读者而言,首要一步的工作就是先了解各个工具的作用及优缺点。
除了本文介绍的这三个工具外,Android系统还支持其他一些更有针对性的测试工具,例如用于测评系统整体功能的lmbench,lttng、测试系统启动性能的bootchart、测试文件系统性能的iozone等。由于篇幅关系,笔者就不再一一介绍它们了。
 [1]关于VSYNC的详情,读者可参考http://blog.csdn.net/innost/article/details/8272867,“Android Project Butter分析“一文。




http://blog.jobbole.com/78995/
正确使用Android性能分析工具——TraceView
前面唠叨
最近公司app中有些列表在滑动的时候会有卡顿现象,我就开始着手解决这些问题,解决问题之前首先要分析列表滑动的性能瓶颈在什么地方。因为之前不会正确使用TraceView这个工具,主要是看不懂TraceView界面下方数据指标的值代表什么意思…以前我用StopWatch类来分析性能,现在觉得弱爆了…不过有些地方StopWatch工具类还是很简单好用的~
网上可以找了很多博客来介绍这个工具的使用方法,很多都是讲解了一些一些就会的方法,讲一个大概,包括StackOverFlow上我也没有找到很好的讲解TraceView各个数据指标代码什么意思的回答
因为我要解决列表滑动的卡顿问题,就必须要找到导致卡顿现象的原因,我就在StackOverFlow上找着别人零散的回答慢慢琢磨这个工具的使用方法。现在我学会了,至少能看懂每个指标什么意思,最后发现这个工具实在太强大了!!!
TraceView界面
现来看一下整个界面的图,整个界面包括上下两部分,上面是你测试的进程中每个线程的执行情况,每个线程占一行;下面是每个方法执行的各个指标的值
上面一部分是你测试进程的中每个线程运行的时间线,下图中可以可以看到,主要只有一个main线程在执行,因为我滑动了一下列表,main线程(UI线程)正在进行绘制View呢~
然后我点击了序号为133的一个方法io.bxbxbai.android.examples.activity.ExpandableLayoutMainActivity$SimpleAdapter.getItemView,就会出现两部分数据:
Parents
Children
Parents表示调用133这个方法的父方法,可以看到序号为130。Children表示方法133调用的其他方法,可以看到有好几个方法。
 
如何使用TraceView
因为这次我主要是分析列表滑动卡顿问题,我就讲讲我是怎么使用这个工具的,并且我是怎么分析的。
使用TraceView主要有两种方式:
最简单的方式就是直接打开DDMS,选择一个进程,然后按上面的“Start Method Profiling”按钮,等红色小点变成黑色以后就表示TraceView已经开始工作了。然后我就可以滑动一下列表(现在手机上的操作肯定会很卡,因 为Android系统在检测Dalvik虚拟机中每个Java方法的调用,这是我猜测的)。操作最好不要超过5s,因为最好是进行小范围的性能测试。然后再按一下刚才按的按钮,等一会就会出现上面这幅图,然后就可以开始分析了。
第2种方式就是使用android.os.Debug.startMethodTracing();和android.os.Debug.stopMethodTracing();方法,当运行了这段代码的时候,就会有一个trace文件在/sdcard目录中生成,也可以调用startMethodTracing(String traceName) 设置trace文件的文件名,最后你可以使用adb pull /sdcard/test.trace /tmp 命令将trace文件复制到你的电脑中,然后用DDMS工具打开就会出现第一幅图了
第一种方式相对来说是一种简单,但是测试的范围很宽泛,第二中方式相对来说精确一点,不过我个人喜欢使用第一种,因为简单,而且它是检测你的某一个操作。因为第二中更适合检测某一个方法的性能,其实也没有那种好,看使用的场景和喜好了。。。
看懂TraceView中的指标
 
其实我今年7月份就已经开始使用TraceView工具了,但是当时不懂其中每个指标的含义,就没注意到它强大的地方。看不懂界面下方表格中的指标,这些数据其实一点意义都没有。
网上包括Android官网也没有对TraceView工具的使用有详细的说明文档,这点确实比较蛋疼。
纵轴
TraceView界面下方表格中纵轴就是每个方法,包括了JDK的,Android SDK的,也有native方法的,当然最重要的就是app中你自己写的方法,有些Android系统的方法执行时间很长,那么有很大的可能就是你app中调用这些方法过多导致的。
每个方法前面都有一个数字,可能是全部方法按照Incl CPU Time 时间的排序序号(后面会讲到)
点一个方法后可以看到有两部分,一个是Parents,另一个是Children。
Parent表示调用这个方法的方法,可以叫做父方法
Children表示这个方法中调用的其他方法,可以叫做子方法
横轴
 
横轴上是很多指标,这些指标表示什么意思真的困扰了我很长一段时间。。。
能够很衡量一个方法性能的指标应该只有时间了吧? 一个方法肯定就是执行时间越短约好咯~~
1. Incl Cpu Time
define inclusive : 全包括的
上图中可以看到0(toplevel) 的Incl Cpu Time 占了100%的时间,这个不是说100%的时间都是它在执行,请看下面代码:
 
1
2
3
4
5
6    public void top() {
    a();
    b();
    c();
    d();
}    
Incl Cpu Time表示方法top执行的总时间,假如说方法top的执行时间为10ms,方法a执行了1ms,方法b执行了2ms,方法c执行了3ms,方法d执行 了4ms(这里是为了举个栗子,实际情况中方法a、b、c、d的执行总时间肯定比方法top的执行总时间要小一点)。
而且调用方法top的方法的执行时间是100ms,那么:
 
        Incl Cpu Time       
top        10%       
    a    10%       
    b    20%       
    c    30%       
    d    40%    
从上面图中可以看到:
toplevel的 Incl Cpu Time 是1110.943,而io.bxbxbai.android.examples.activity.ExpandableLayoutMainActivity$SimpleAdapter.getItemView方法的Incl Cpu Time为12.859,说明后者的Incl Cpu Time % 约为1.2%
这个指标表示 这个方法以及这个方法的子方法(比如top方法中的a、b、c、d方法)一共执行的时间
2. Excl Cpu Time
理解了Incl Cpu Time以后就可以很好理解Excl Cpu Time了,还是上面top方法的栗子:
方法top 的 Incl Cpu Time 减去 方法a、b、c、d的Incl Cpu Time 的时间就是方法top的Excl Cpu Time 了
3. Incl Real Time
这个感觉和Incl Cpu Time 差不多,第7条会讲到。
4. Excl Real Time
同上
5. Calls + Recur Calls / Total
这个指标非常重要!
它表示这个方法执行的次数,这个指标中有两个值,一个Call表示这个方法调用的次数,Recur Call表示递归调用次数,看下图:
 
我选中了一个方法,可以看到这个方法的Calls + Recur Calls 值是14 + 0,表示这个方法调用了14次,但是没有递归调用
从Children这一块来看,很多方法调用都是13的倍数,说明父方法中有一个判断,但是这不是重点,有些Child方法调用Calls为26,这说明了这些方法被调用了两遍,是不是可能存在重复调用的情况?这些都是可能可以优化性能的地方。
6. Cpu Time / Call
重点来了!!!!!!!!!!
 
这个指标应该说是最重要的,从上图可以看到,133这个方法的调用次数为20次,而它的Incl Cpu Time为12.859ms,那么133方法每一次执行的时间是0.643ms(133这个方法是SimpleAdapter的getItemView方法)
对于一个adapter的getView方法来说0.643ms是非常快的(因为这个adapter中只有一个TextView,我为了测试用的)
如果getView方法执行时间很长,那么必然导致列表滑动的时候产生卡顿现象,可以在getView方法的Children方法列表中找到耗时最长的方法,分析出现问题的原因:
是因为有过多的计算?
还是因为有读取SD卡的操作?
还是因为adapter中View太复杂了?
还是因为需要有很多判断,设置View的显示还是隐藏
还是因为其他原因…
7. Real Time / Call
Real Time 和 Cpu Time 我现在还不太明白它们的区别,我的理解应该是:
Cpu Time 应该是某个方法占用CPU的时间
Real Time 应该是这个方法的实际运行时间
为什么它们会有区别呢?可能是因为CPU的上下文切换、阻塞、GC等原因方法的实际执行时间要比Cpu Time 要稍微长一点。
总结
TraceView是一个非常强大的性能分析工具,因为Android 官网对这个工具的使用介绍文档很少,而且一些中文博客中写的也都是抄来抄去,没有讲到底怎么使用。
最近我在做这方面的性能分析,就慢慢琢磨了这么工具的使用,发现非常强大,写下来总结一下。
Android的性能分析工具还有很多,比如:
Eclipse Memory Analyzer Tool 来分析Android app的内存使用
Dump UI Hierarchy for UI Atomator,分析UI层级
systrace
其他

















http://www.cnblogs.com/zhucai/p/weibo-graphics-performance-analyse.html
为什么微博的app在iPhone比Android上流畅
我来说下我所知道的事情。我不知道iOS为什么流畅,但我知道一些Android为什么不流畅的原因。

首先,就题主所说的问题,我用iPad和小米Pad对比了一下微博滑动滚屏这件事情(2014年8月10日目前微博app最新版本)。正如题主所说,直观感受上明显感觉iOS要流畅、舒服。

在这件事情上我认为主要是这三个原因:
速度曲线。
当你滑动界面然后松手,这时界面会继续滑动,然后速度减小,直到速度为0时停止。iOS下速度减小的这个过程比较慢,尤其是快要停的时候是慢慢停的,视觉上有种很顺滑的感觉;Android下则从松手到停要快很多,相比之下有种戛然而止的感觉。
从数据/技术角度来看这个事情,我们滑动界面的最终目的不是为了“动”,而是为了“停”,因此只要平滑的到达目的地,似乎越快完成这个过程越好,所以Android的选择是理所当然的。但事实是,大家普遍更喜欢iOS的方式,这样做显得更顺滑、更优雅。
帧率。
绝大部分时间两者都能保持60FPS左右的满帧率。但都会有偶尔的掉帧。并且Android上 要比iOS上严重很多。(好吧,比起前两年,已经好太多了。)我前前后后滑动了几十次,iOS在前面遇到1次掉帧,后面就很稳定了。而Android几乎 每滑动一次都会伴随一次掉帧。这完全就是真真实实的卡顿,用户必然会感觉到那一刻的不流畅。Android掉帧的原因我后面再详细分析。
触摸响应速度。
从手指碰到触摸屏,到屏幕上显示处理这次触摸产生的画面,是需要时间的。时间越短感觉 越跟手。据说iOS的触摸屏的处理时间要比一般的Android手机快,这不是我的专长,不知道怎么验证。但在软件系统层面,Android的显示机制是 app-->SurfaceFlinger-->Display,这比传统的app-->Display多了一步,主要基于这个原因, 画面最终输出到屏幕要比传统的方式慢一帧(16.7ms)。

我觉得第1点造成的影响最大,恰恰却是最技术/设备无关的。如果微博app或者Android系统要改变,很容易就可以调得跟iOS一模一样。但正是由于这是产品形态上的差别而不是纯粹技术上的优劣,反倒成了Android最不太可能改变的。

第2点的影响其次,当然是指在目前这个大部分时候都能满帧的情况下。这方面是Android从早期到现在进步最明显的方面,使用了很多方法来优化帧率。但就算现在Android进化了很多,硬件性能也进化了很多,却仍旧不可能彻底消灭掉帧的情况。

第3点通俗的讲就是跟手性,跟手性的重要性不言而喻,但现在的差别比较细微,且具体数据我也不清楚。
我想过一个办法让桌面、微博这种内容和手一起动的应用绘制到屏幕的速度快一帧(16.7ms),其实就是抵消之前提到的慢的那一帧,需要framework层和app层一起配合改动,目前已申请了专利但代码还没进,将来有时间了应该会进到MIUI。感兴趣的可以看看专利:滑动操作响应方法、装置及终端设备。

最后我来用专业技术分析一下微博app在Android里掉帧的原因。非编程人员可以不看下去了。(这个过程我使用的是小米3高通版+最新版微博app。)

最初,我认为这种现象很像GC(垃圾回收)导致的,于是打开logcat观察每次卡顿的时候有没有GC发生。结果发现并没有。停下来的时候才会有GC,这说明微博app在滑动过程中控制得不错,在停下来的一刻才去分配内存,使GC不影响帧率。

然 后我打开“开发者选项”->“GPU呈现模式分析”->“在屏幕上显示为条形图”(好像是4.4才有这个选项,之前的只能在dumpsys里 看),这会在屏幕上直观的显示每帧绘制花费的时间。屏幕上有条基准线大概是16ms,如果超过这条线则很有可能掉帧了。如果下面的蓝色部分很长则说明是软 件draw的部分太费时,那么可以通过traceview来继续分析draw的java代码。(我假定了现在的app都是硬件加速的方式。)如果中间红色 部分很长则说明是OpenGL ES绘制过程太费时。可以用gltrace来分析OpenGL ES的调用过程。
打开选项后使用微博app,发现虽然偶尔有超基准线,但并不严重,并且卡顿的时候并没有明显异常。

于是我开始使用systrace,这下就很明显了:
 
可以发现每隔几帧,就会有一次大间隙,图中我圈了红圈圈的两个地方都明显超过正常的耗时时间。现在问题是,这些地方的代码在干什么呢?是什么花费了这么长时间?
 
放大后发现都是这两个:obtainView和setupListItem。它们都是在draw之前调用的,这也解释了通过上一步的方法(“GPU呈现模式分析”)为什么没有显示出来,因为那个方法只展示draw里面的时间消耗。
通过在源码里搜索obtainView和setupListItem,可以发现是AbsListView的obtainView方法和ListView的setupChild方法打下的这个log。

接下来,我们用traceview来看看这两个方法里面究竟调用了什么代码导致耗时过长。
跟踪obtainView最终到了这里:
 
代码混淆了,看不太出来了,也懒得再跟下去了。但随便点了点然后看到:
 
TextView.setText用了挺多的时间。感觉Android团队应该优化优化这个方法。
然后再来跟踪setupChild方法:
 
都是measure占用的时间,并且调用次数比较多。这应该说明了View的数量不少。用hierarchyviewer看了下,的确不少,一条微博有50多个View:
 
结论:我们最终大致找到了耗时的代码及部分原因。这两个耗时方法应该都是有可能减下来的,但应该不那么容易。
在Android上实现功能比较容易,但如果默认实现有了性能问题,要想解决就不好说了,有时候要绕很远。










































http://blog.csdn.net/ddna/article/details/5527072
【Android工具】被忽略的UI检视利器:Hierarchy Viewer
        Hierarchy Viewer是随AndroidSDK发布的工具,位置在tools文件夹下,名为hierarchyviewer.bat。它是Android自带的非常有用而且使用简单的工具,可以帮助我们更好地检视和设计用户界面(UI),绝对是UI检视的利器,但是好像很少有人提它,难道是因为太简单?
 
具体来说主要功能有2个:
1.       从可视化的角度直观地获得UI布局设计结构和各种属性的信息,帮助我们优化布局设计;
2.       结合debug帮助观察特定的UI对象进行invalidate和requestLayout操作的过程。
 
1.       基本使用方法
(1)hierarchyviewer的使用非常简单,启动模拟器或者连接上真机后,启动hierarchyviewer.bat,会看到下面的界面,Devices里列出了可以观察的设备,Windows里列出的是当前选中的设备的可以用来显示View结构的Window:
 
     选中某个想要观察的Window,比如上面列出的com.android.launcher/com.android.launcher.Launcher项,然后点击菜单栏的Load View Hierarchy,就进入Layout View,由于要解析相关Window,所以这个过程要几秒钟,左边列出的是当前窗口的树型布局结构图,右边列出的是当前选中的某个子View的属性信息和在窗口中的位置:
 
    需要注意的是:Layout View列出的View结构是从视图的根节点开始的,比如针对Launcher使用的layout,它的底层基础布局DragLayer实际上是放在一个FrameLayout里的,该FrameLayout又是被PhoneWindow的DecorView管理的。
 
(2)点击界面左下角类似九宫格的按钮,就进入了Android称之为Pixel Perfect View的界面,这个界面里主要是从细节上观察UI效果:
 
      左边是浏览视图,中间是全局的视图,右边是当前关注的地方的细节放大,是像素级别的,对于观察细节非常有用。
Refresh Rate用来控制View多久从模拟器或者真机上更新一次视图数据。
Zoom就是放大局部细节用的,细节显示在最右边的视图上。
Overlay比较有意思,主要用来测试在当前视图上加载新的图片后的效果,点击Load…选择图片后,可以控制在当前界面上显示的透明读,滑动0%~100%的控件即可。如果选择了Show in Loupe,右侧的放大视图也会将加载的图片的细节结合着透明度显示出来。不过目前这个Overlay做的比较简单,合成的图只能从界面的左下角为原点画出来,不能移动。
 
 
(3)在Layout View中,选中一个view的图示,点击工具栏的Display View,就可以看到这个view的实际显示效果,可以点选Show Extras,这个功能也比较实用,可以显示出该View中不同元素显示的边界,帮助我们检查是否正确。
 
 
 2.       Hierarchyviewer的invalidate和requestLayout功能
对于Android的UI来说,invalidate和requestLayout是最重要的过程,Hierarchyviewer提供了帮助我们Debug特定的UI执行invalidate和requestLayout过程的途径,方法很简单,只要选择希望执行这两种操作的View点击按钮就可以。当然,我们需要在例如onMeasure()这样的方法中打上断点。这个功能对于UI组件是自定义的非常有用,可以帮助单独观察相关界面显示逻辑是否正确。














http://www.tuicool.com/articles/jMfiUjj
使用Systrace分析UI性能
原文链接 : Analyzing UI Performance with Systrace
原文作者 : Android Developers
译文出自 : 开发技术前线 www.devtf.cn。未经允许,不得转载!
译者 : desmond1121
校对者: desmond1121
开发应用的时候,应该检查它是否有流畅的用户体验,即60fps的帧率。如果由于某种原因丢帧,我们首先要做的就是知道系统在做什么(造成丢帧的原因)。
Systrace允许你监视和跟踪Android系统的行为(trace)。它会告诉你系统都在哪些工作上花费时间、CPU周期都用在哪里,甚至 你可以看到每个线程、进程在指定时间内都在干嘛。它同时还会突出观测到的问题,从垃圾回收到渲染内容都可能是问题对象,甚至提供给你建议的解决方案。本文 章将介绍如何导出trace以及使用它来优化UI的办法。
总览
Systrace可以帮助你分析应用在不同Android系统上的运行情况。它将系统和应用的线程运行情况放置在同一条时间线上分析。你首先需要 收集系统和应用的trace(后面会告诉你怎么做),之后Systrace会帮你生成一份细致、直观的报告,它展示了设备在你监测的这段时间内所发生的事 情。
 图1. 连续滑动应用5秒的Trace,它并没有表现得很完美。
图1展示了应用在滑动不流畅的时候生成的trace。默认缩放成全局显示,你可以放大到自己所关注的地方。横轴代表着时间线,事件记录按照进程分组,同一个进程内按线程进行纵向拆分,每个线程记录自己的工作。
在本例中,一共有三个组:Kernel, SurfaceFlinger, App,他们分别以包名为标识。每个应用进程都会包含其中所有线程的记录信号,你可以看到从InputEvent到RenderThread都有。
生成Trace
在获取trace之前需要做一些启动工作。首先,设备要求API>=16(Android 4.1),之后通过正常的Debug流程(开启调试、连接工作环境、安装App)连接设备。由于需要记录磁盘活动和内核工作,你可能需要root权限。不 过大部分时候你只要能够正常Debug即可。
Systrace 可以通过 命令行 或者 图形界面 启动,本篇文章重点介绍通过命令行使用Systrace。
在Android 4.3及以上的系统中获取trace
在4.3以上的系统获取Trace步骤:
保证设备USB连接正常,并可以debug;
在命令行中设置选项,开启trace,比如:
$ cd android-sdk/platform-tools/systrace
    $ python systrace.py --time=10 -o mynewtrace.html sched gfx view wm
    $cdandroid-sdk/platform-tools/systrace
    $pythonsystrace.py--time=10-omynewtrace.htmlschedgfxviewwm
 
在设备上做任何你想让trace记录的操作。
你可以通过 Systrace选项 来了解更多命令行选项。
在Android 4.2及以下的系统中获取trace
在4.2及以下的系统中高效地使用Systrace的话,你需要在配置的时候显式指定要trace的进程种类。一共有这两类种类:
普通系统进程,比如图形、声音、输入等。(通过tags设置,具体在 Systrace命令行 中有介绍)
底层系统进程,比如CPU、内核、文件系统活动。(通过options设置,具体在 Systrace命令行 中有介绍)
你可以通过以下命令行操作来设置tags:
使用 --set-tags 选项:
$ cd android-sdk/platform-tools/systrace
    $ python systrace.py --set-tags=gfx,view,wm
    $cdandroid-sdk/platform-tools/systrace
    $pythonsystrace.py--set-tags=gfx,view,wm
 
重启adb shell来trace这些进程:
$ adb shell stop
    $ adb shell start
    $adbshellstop
    $adbshellstart
 
你也可以通过手机上的图形界面设置tags:
在设备上进入设置> 开发者选项 > 监控 > 启用跟踪(部分手机上没有这个选项);
选择追踪进程类型,点击确认。
注意: 在图形界面中设置tag时adb shell不用重新启动。
在配置完tags后,你可以开始收集操作信息了。
如何在当前设置下启动trace:
保证设备的usb连接正常,并且可以正常debug;
使用低系统等级的命令行选项开启trace,比如:
$ python systrace.py --cpu-freq --cpu-load --time=10 -o mytracefile.html
在设备上做任何你想让trace记录的操作。
你可以通过 Systrace选项 来了解更多命令行选项。
分析trace报告
在你获取trace之后你可以在网页浏览器中打开它。这部分内容告诉你怎么通过trace去分析和解决UI性能。
监视帧数
每个应用都有一行专门显示frame,每一帧就显示为一个绿色的圆圈。不过也有例外,当显示为黄色或者红色的时候,它的渲染时间超过了16.6ms(即达不到60fps的水准)。’w’键可以放大,看看这一帧的渲染过程中系统到底做了什么。
提示:你可以按右上角的’?’按钮来查看界面使用帮助。
 图2. Systrace显示长渲染时间的帧
单击该帧可以高亮它,这时候跟该帧有关的内容会被突出显示。在5.0及以上的系统中,显示工作被拆分成UI线程和Render线程两部分;在5.0以下的系统中,所有的显示工作在UI线程中执行。
点击单个Frame下面的组件可以看他们所花费的时间。每个事件(比如 performTraversals )都会在你选中的时候显示出它们调用了哪些方法及所用的时间。
调查警告事件
Systrace会自动分析事件,它会将任何它认为性能有问题的东西都高亮警告,并提示你要怎么去优化。
 图3. 选择一个被高亮帧,它会显示出检测到的问题(回收ListView消耗时间太长)。
在你选择类似图三中的问题帧之后,它就会提示你检测出的问题。在这个例子中,它被警告的主要原因是ListView的回收和重新绑定花费太多时间。在Systrace中也会提供一些对应链接,它们会提供更多解释。
如果你想知道UI线程怎么会花费这么多时间的话,你可以使用 TraceView ,它会告诉你都是哪些函数在消耗时间。
你可以通过右侧的’Alert’选项卡来查看整个trace过程中发生的所有问题,并进行快速定位。
 图4. 点击Alert选项卡。
你可以将Alert面板中的问题视为需要处理的bug,很有可能每一次微小的优化能够去除整个应用中的警告!
应用级别调试
Systrace并不会追踪应用的所有工作,所以你可以在有需求的情况下自己添加要追踪的代码部分。在Android 4.3及以上的代码中,你可以通过 Trace 类来实现这个功能。它能够让你在任何时候跟踪应用的一举一动。在你获取trace的过程中, Trace.beginSection() 与 Trace.endSection() 之间代码工作会一直被追踪。
下面这部分代码展示了使用 Trace 的例子,在整个方法中含有两个Trace块。
public void ProcessPeople() {
    Trace.beginSection("ProcessPeople");
    try {
        Trace.beginSection("Processing Jane");
        try {
           // 待追踪的代码
        } finally {
            Trace.endSection(); // 结束 "Processing Jane"
        }

        Trace.beginSection("Processing John");
        try {
            // 待追踪的代码
        } finally {
            Trace.endSection(); // 结束 "Processing John"
        }
    } finally {
        Trace.endSection(); // 结束 "ProcessPeople"
    }
}
publicvoidProcessPeople(){
  Trace.beginSection("ProcessPeople");
  try{
    Trace.beginSection("Processing Jane");
    try{
       // 待追踪的代码
    }finally{
      Trace.endSection();// 结束 "Processing Jane"
    }
    Trace.beginSection("Processing John");
    try{
      // 待追踪的代码
    }finally{
      Trace.endSection();// 结束 "Processing John"
    }
  }finally{
    Trace.endSection();// 结束 "ProcessPeople"
  }
}
注意:在Trace是被嵌套在另一个Trace中的时候, endSection() 方法只会结束理它最近的一个 beginSection(String) 。即在一个Trace的过程中是无法中断其他Trace的。所以你要保证 endSection() 与 beginSection(String) 调用次数匹配。
注意:Trace的begin与end必须在同一线程之中执行!
当你使用应用级别追踪的时候,你必须通过 -a 或者 -app= 来显式地指定应用包名。可以通过 Systrace指南 查看更多关于它的信息。
你在评估应用的时候应该开启应用级别跟踪,即使当你没有手动添加 Trace 信号。因为很多库函数里面是有添加Trace信号的(比如 RecyclerView ),它们往往能够提供很多信息。

http://blog.jobbole.com/78995/
正确使用Android性能分析工具——TraceView
2014/10/27 · Android, 开发 · Android
分享到: 37
MongoDB集群之分片技术应用
Hello,移动WEB
Linux权限管理之特殊权限
Android高级特效-索引
原文出处: bxbxbai 的博客(@白瓦力)   欢迎分享原创到伯乐头条
前面唠叨
最近公司app中有些列表在滑动的时候会有卡顿现象,我就开始着手解决这些问题,解决问题之前首先要分析列表滑动的性能瓶颈在什么地方。因为之前不会正确使用TraceView这个工具,主要是看不懂TraceView界面下方数据指标的值代表什么意思…以前我用StopWatch类来分析性能,现在觉得弱爆了…不过有些地方StopWatch工具类还是很简单好用的~
网上可以找了很多博客来介绍这个工具的使用方法,很多都是讲解了一些一些就会的方法,讲一个大概,包括StackOverFlow上我也没有找到很好的讲解TraceView各个数据指标代码什么意思的回答
因为我要解决列表滑动的卡顿问题,就必须要找到导致卡顿现象的原因,我就在StackOverFlow上找着别人零散的回答慢慢琢磨这个工具的使用方法。现在我学会了,至少能看懂每个指标什么意思,最后发现这个工具实在太强大了!!!
TraceView界面
现来看一下整个界面的图,整个界面包括上下两部分,上面是你测试的进程中每个线程的执行情况,每个线程占一行;下面是每个方法执行的各个指标的值
上面一部分是你测试进程的中每个线程运行的时间线,下图中可以可以看到,主要只有一个main线程在执行,因为我滑动了一下列表,main线程(UI线程)正在进行绘制View呢~
然后我点击了序号为133的一个方法io.bxbxbai.android.examples.activity.ExpandableLayoutMainActivity$SimpleAdapter.getItemView,就会出现两部分数据:
Parents
Children
Parents表示调用133这个方法的父方法,可以看到序号为130。Children表示方法133调用的其他方法,可以看到有好几个方法。
 
如何使用TraceView
因为这次我主要是分析列表滑动卡顿问题,我就讲讲我是怎么使用这个工具的,并且我是怎么分析的。
使用TraceView主要有两种方式:
最简单的方式就是直接打开DDMS,选择一个进程,然后按上面的“Start Method Profiling”按钮,等红色小点变成黑色以后就表示TraceView已经开始工作了。然后我就可以滑动一下列表(现在手机上的操作肯定会很卡,因 为Android系统在检测Dalvik虚拟机中每个Java方法的调用,这是我猜测的)。操作最好不要超过5s,因为最好是进行小范围的性能测试。然后再按一下刚才按的按钮,等一会就会出现上面这幅图,然后就可以开始分析了。
第2种方式就是使用android.os.Debug.startMethodTracing();和android.os.Debug.stopMethodTracing();方法,当运行了这段代码的时候,就会有一个trace文件在/sdcard目录中生成,也可以调用startMethodTracing(String traceName) 设置trace文件的文件名,最后你可以使用adb pull /sdcard/test.trace /tmp 命令将trace文件复制到你的电脑中,然后用DDMS工具打开就会出现第一幅图了
第一种方式相对来说是一种简单,但是测试的范围很宽泛,第二中方式相对来说精确一点,不过我个人喜欢使用第一种,因为简单,而且它是检测你的某一个操作。因为第二中更适合检测某一个方法的性能,其实也没有那种好,看使用的场景和喜好了。。。
看懂TraceView中的指标
 
其实我今年7月份就已经开始使用TraceView工具了,但是当时不懂其中每个指标的含义,就没注意到它强大的地方。看不懂界面下方表格中的指标,这些数据其实一点意义都没有。
网上包括Android官网也没有对TraceView工具的使用有详细的说明文档,这点确实比较蛋疼。
纵轴
TraceView界面下方表格中纵轴就是每个方法,包括了JDK的,Android SDK的,也有native方法的,当然最重要的就是app中你自己写的方法,有些Android系统的方法执行时间很长,那么有很大的可能就是你app中调用这些方法过多导致的。
每个方法前面都有一个数字,可能是全部方法按照Incl CPU Time 时间的排序序号(后面会讲到)
点一个方法后可以看到有两部分,一个是Parents,另一个是Children。
Parent表示调用这个方法的方法,可以叫做父方法
Children表示这个方法中调用的其他方法,可以叫做子方法
横轴
 
横轴上是很多指标,这些指标表示什么意思真的困扰了我很长一段时间。。。
能够很衡量一个方法性能的指标应该只有时间了吧? 一个方法肯定就是执行时间越短约好咯~~
1. Incl Cpu Time
define inclusive : 全包括的
上图中可以看到0(toplevel) 的Incl Cpu Time 占了100%的时间,这个不是说100%的时间都是它在执行,请看下面代码:
 
1
2
3
4
5
6    public void top() {
    a();
    b();
    c();
    d();
}    
Incl Cpu Time表示方法top执行的总时间,假如说方法top的执行时间为10ms,方法a执行了1ms,方法b执行了2ms,方法c执行了3ms,方法d执行 了4ms(这里是为了举个栗子,实际情况中方法a、b、c、d的执行总时间肯定比方法top的执行总时间要小一点)。
而且调用方法top的方法的执行时间是100ms,那么:
 
        Incl Cpu Time       
top        10%       
    a    10%       
    b    20%       
    c    30%       
    d    40%    
从上面图中可以看到:
toplevel的 Incl Cpu Time 是1110.943,而io.bxbxbai.android.examples.activity.ExpandableLayoutMainActivity$SimpleAdapter.getItemView方法的Incl Cpu Time为12.859,说明后者的Incl Cpu Time % 约为1.2%
这个指标表示 这个方法以及这个方法的子方法(比如top方法中的a、b、c、d方法)一共执行的时间
2. Excl Cpu Time
理解了Incl Cpu Time以后就可以很好理解Excl Cpu Time了,还是上面top方法的栗子:
方法top 的 Incl Cpu Time 减去 方法a、b、c、d的Incl Cpu Time 的时间就是方法top的Excl Cpu Time 了
3. Incl Real Time
这个感觉和Incl Cpu Time 差不多,第7条会讲到。
4. Excl Real Time
同上
5. Calls + Recur Calls / Total
这个指标非常重要!
它表示这个方法执行的次数,这个指标中有两个值,一个Call表示这个方法调用的次数,Recur Call表示递归调用次数,看下图:
 
我选中了一个方法,可以看到这个方法的Calls + Recur Calls 值是14 + 0,表示这个方法调用了14次,但是没有递归调用
从Children这一块来看,很多方法调用都是13的倍数,说明父方法中有一个判断,但是这不是重点,有些Child方法调用Calls为26,这说明了这些方法被调用了两遍,是不是可能存在重复调用的情况?这些都是可能可以优化性能的地方。
6. Cpu Time / Call
重点来了!!!!!!!!!!
 
这个指标应该说是最重要的,从上图可以看到,133这个方法的调用次数为20次,而它的Incl Cpu Time为12.859ms,那么133方法每一次执行的时间是0.643ms(133这个方法是SimpleAdapter的getItemView方法)
对于一个adapter的getView方法来说0.643ms是非常快的(因为这个adapter中只有一个TextView,我为了测试用的)
如果getView方法执行时间很长,那么必然导致列表滑动的时候产生卡顿现象,可以在getView方法的Children方法列表中找到耗时最长的方法,分析出现问题的原因:
是因为有过多的计算?
还是因为有读取SD卡的操作?
还是因为adapter中View太复杂了?
还是因为需要有很多判断,设置View的显示还是隐藏
还是因为其他原因…
7. Real Time / Call
Real Time 和 Cpu Time 我现在还不太明白它们的区别,我的理解应该是:
Cpu Time 应该是某个方法占用CPU的时间
Real Time 应该是这个方法的实际运行时间
为什么它们会有区别呢?可能是因为CPU的上下文切换、阻塞、GC等原因方法的实际执行时间要比Cpu Time 要稍微长一点。
总结
TraceView是一个非常强大的性能分析工具,因为Android 官网对这个工具的使用介绍文档很少,而且一些中文博客中写的也都是抄来抄去,没有讲到底怎么使用。
最近我在做这方面的性能分析,就慢慢琢磨了这么工具的使用,发现非常强大,写下来总结一下。
Android的性能分析工具还有很多,比如:
Eclipse Memory Analyzer Tool 来分析Android app的内存使用
Dump UI Hierarchy for UI Atomator,分析UI层级
systrace
其他
下图这一条工具栏中有很多性能分析工具~~~
 































http://blog.csdn.net/aaa2832/article/details/19419679
[Android Memory] 内存分析工具 MAT 的使用
1 内存泄漏的排查方法
 
Dalvik Debug Monitor Server (DDMS) 是 ADT插件的一部分,其中有两项功能可用于内存检查 :
·    heap 查看堆的分配情况
·    allocation tracker跟踪内存分配情况
DDMS 这两项功能有助于找到内存泄漏的操作行为。
Eclipse Memory Analysis Tools (MAT) 是一个分析 Java堆数据的专业工具,用它可以定位内存泄漏的原因。
工具地址 : https://www.eclipse.org/mat/
 
1.1 观察 Heap
 
·        运行程序,然后进入 DDMS管理界面,如下:
 
PS : 点击工具栏上的   来更新统计信息
点击右侧的 Cause GC 按钮或工具栏上的   即可查看当前的堆情况,如下:
 
主要关注两项数据:
o    Heap Size 堆的大小,当资源增加,当前堆的空余空间不够时,系统会增加堆的大小,若超过上限 (例如64M,视平台和具体机型而定)则会被杀掉
o    Allocated 堆中已分配的大小,这是应用程序实际占用的内存大小,资源回收后,此项数据会变小
·        查看操作前后的堆数据,看是否有内存泄漏
对单一操作(比如添加页,删除页)进行反复操作,如果堆的大小一直增加,则有内存泄漏的隐患。
 
1.2 利用MAT分析内存堆
 
DDMS 可以将当前的内存 Dump成一个 hprof格式的文件,MAT 读取这个文件后会给出方便阅读的信息,配合它的查找,对比功能,就可以定位内存泄漏的原因。
·        获取 hprof文件
点击工具栏上的   按钮,将内存信息保存成文件。 如果是用 MAT Eclipse 插件获取的 Dump文件,则不需要经过转换,Adt会自动进行转换然后打开。
·        转换 hprof文件
DDMS Dump 出的文件要经过转换才能被 MAT识别,Android SDK提供了这个工具 hprof-conv (位于 sdk/tools下)
·    ./hprof-conv xxx-a.hprof xxx-b.hprof
·        用 MAT打开转换后的 hprof文件
 
 
1.3  Histogram 查询
 
用的最多的功能是 Histogram,点击 Actions下的 Histogram项将得到 Histogram结果:
 
它按类名将所有的实例对象列出来,可以点击表头进行排序,在表的第一行可以输入正则表达式来匹配结果 :
 
在某一项上右键打开菜单选择 list objects ->with incoming refs 将列出该类的实例:
 
它展示了对象间的引用关系,比如展开后的第一个子项表示这个 HomePage(0x420ca5b0)被HomePageContainer(0x420c9e40)中的 mHomePage属性所引用.
快速找出某个实例没被释放的原因,可以右健 Path to GC Roots-->exclue all phantom/weak/soft etc. reference :
 
得到的结果是:
 
从表中可以看出 PreferenceManager -> … ->HomePage这条线路就引用着这个 HomePage实例。用这个方法可以快速找到某个对象的 GC Root,一个存在 GC Root的对象是不会被 GC回收掉的.
 
1.4  Histogram 对比
 
为查找内存泄漏,通常需要两个 Dump结果作对比,打开 Navigator History面板,将两个表的 Histogram结果都添加到 Compare Basket中去 :
 
添加好后,打开 Compare Basket面板,得到结果:
 
点击右上角的 ! 按钮,将得到比对结果:
 
注意,上面这个对比结果不利于查找差异,可以调整对比选项:
 
再把对比的结果排序,就可得到直观的对比结果:
 
也可以对比两个对象集合,方法与此类似,都是将两个 Dump结果中的对象集合添加到Compare Basket中去对比。找出差异后用 Histogram查询的方法找出 GC Root,定位到具体的某个对象上。
 
1.5  例子
 
举例一个典型的分析内存泄漏的过程:
1.  使用 Heap查看当前堆大小为 23.00M
2.  添加一个页后堆大小变为 23.40M
3.  将添加的一个页删除,堆大小为 23.40M
4.  多次操作,结果仍相似,说明添加/删除页存在内存泄漏 (也应注意排除其它因素的影响)
5.  Dump 出操作前后的 hprof 文件 (1.hprof,2.hprof),用 mat打开,并得到 histgram结果
6.  使用 HomePage字段过滤 histgram结果,并列出该类的对象实例列表,看到两个表中的对象集合大小不同,操作后比操作前多出一个 HomePage,说明确实存在泄漏
7.  将两个列表进行对比,找出多出的一个对象,用查找 GC Root的方法找出是谁串起了这条引用线路,定位结束
PS :
·        很多时候堆增大是 Bitmap引起的,Bitmap在 Histogram中的类型是 byte [],对比两个 Histogram中的 byte[]对象就可以找出哪些 Bitmap有差异
·        多使用排序功能,对找出差异很有用
 
 
2 内存泄漏的原因分析
 
总结出来只有一条: 存在无效的引用!
良好的模块设计以及合理使用设计模式有助于解决此问题。
 
 
3 Tips
 
·    使用 android:largeHeap="true"标记 (API Level >= 11)
在 AndroidManifest.xml中的 Application节点中声明即可分配到更大的堆内存, android:largeHeap标记在 Android系统应用中也有广泛的应用 ,比如 Launcher, Browser这些内存大户上均有使用.
 
 
http://www.jianshu.com/p/c49f778e7acf

使用Android studio分析内存泄露
字数1340 阅读19600 评论19 喜欢87
This post is a permitted translation of badoo Tech Blog and I add some text and screenshots for android studio users.
Origin Author: Dmytro Voronkevych
follow badoo on Tweet
Translator: Miao1007
截至androidstudio1.3为止,其内部的MemoryDump功能都很难使用,还是使用MAT更佳。
Android使用java作为平台开发,帮助了我们解决了很多底层问题,比如内存管理,平台依赖等等。然而,我们也经常遇到OutOfMemoey问题,垃圾回收到底去哪了?
接下来是一个Handler Leak的例子,它一般会在编译器中被警告提示。
所需要的工具
Android Studio 1.1 or higher
Eclipse MemoryAnalyzer
示例代码
public class NonStaticNestedClassLeakActivity extends ActionBarActivity {

  TextView textView;

  public static final String TAG = NonStaticNestedClassLeakActivity.class.getSimpleName();

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_non_static_nested_class_leak);
    textView = (TextView)findViewById(R.id.textview);
    Handler handler = new Handler();

    handler.postDelayed(new Runnable() {
      @Override public void
        textView.setText("Done");
      }//a mock for long time work
    }, 800000L);


  }
}
这是一个非常基础的Activity.注意这个匿名的Runnable被送到了Handler中,而且延迟非常的长。现在我们运行这个Activity,反复旋转屏幕,然后导出内存并分析。
导入 Memory 到Eclipse MemoryAnalyzer
使用Androidstudio导出 heap dump
 
Android Studio dump Memory Analyze
点击左下角的Android
选中你的程序的包名
点击 initiates garbage collection on selected vm
点击 dump java heap for selected client
打开MAT,进行分析
MAT是对java heap中变量分析的一个工具,它可以用于分析内存泄露。
点击OQL图标
在窗口输入select * from instanceof android.app.Activity并按Ctrl + F5或者!按钮
奇迹出现了,现在你发现泄露了许多的activity
这个真是相当的不容乐观,我们来分析一下为什么GC没有回收它
 
EMA
在OQL(Object Query Language)窗口下输入的查询命令可以获得所有在内存中的Activities,这段查询代码是不是非常简单高效呢?
点击一个activity对象,右键选中Path to GC roots
 
GC root
 
Message in looper hold a reference to Activity
在打开的新窗口中,你可以发现,你的Activity是被this$0所引用的,它实际上是匿名类对当前类的引用。this$0又被callback所引用,接着它又被Message中一串的next所引用,最后到主线程才结束。
任何情况下你在class中创建非静态内部类,内部类会(自动)拥有对当前类的一个强引用。
一旦你把Runnable或者Message发送到Handler中,它就会被放入LooperThread的消息队列,并且被保持引用,直到Message被处理。发送postDelayed这样的消息,你输入延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会照成一个临时的泄露。
尝试使用static inner class来解决
现在把Runnable变成静态的class
 
StaticClass
现在,摇一摇手机,导出内存
 
StaticClass_memory_analyze
为什么又出现了泄露呢?我们看一看Activities的引用.
 
StaticClass_memory_analyze_explained
看到下面的mContext的引用了吗,它被mTextView引用,这样说明,使用静态内部类还远远不够,我们仍然需要修改。
使用弱引用 + static Runnable
现在我们把刚刚内存泄露的罪魁祸首 - TextView改成弱引用。
 
StaticClassWithWeakRef_code
再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机
小心地操作WeakReferences,它们随时可以为空,在使用前要判断是否为空.
 
StaticClassWithWeakRef_memory_analyze
哇!现在只有一个Activity的实例了,这回终于解决了我们的问题。
所以,我们应该记住:
使用静态内部类
Handler/Runnable的依赖要使用弱引用。
如果你把现在的代码与开始的代码相比,你会发现它们大不相同,开始的代码易懂简介,你甚至可以脑补出运行结果。
而现在的代码更加复杂,有很多的模板代码,当把postDelayed设置为一个短时间,比如50ms的情况下,写这么多代码就有点亏了。其实,还有一个更简单的方法。
onDestroy中手动控制声明周期
Handler可以使用removeCallbacksAndMessages(null),它将移除这个Handler所拥有的Runnable与Message。
//Fixed by manually control lifecycle
  @Override protected void onDestroy() {
    super.onDestroy();
    myHandler.removeCallbacksAndMessages(null);
  }
现在运行,旋转手机,导出内存
 
removeCallbacks_memory_analyze
Good!只有一个实例。
这样写可以让你的代码更加简洁与可读。唯一要记住的就是就是要记得在生命周期onDestory的时候手动移除所有的消息。
使用WeakHander
(这个是第三方库,我就不翻译了,大家去Github上去学习吧)
结论
在Handler中使用postDelayed需要额外的注意,为了解决问题,我们有三种方法
使用静态内部Handler/Runnable + 弱引用
在onDestory的时候,手动清除Message
使用Badoo开发的第三方的 WeakHandler
这三种你可以任意选用,第二种看起来更加合理,但是需要额外的工作。第三种方法是我最喜欢的,当然你也要注意WeakHandler不能与外部的强引用共同使用。



http://www.cnblogs.com/sunzn/p/3192231.html

Android 编程下的 TraceView 简介及其案例实战
TraceView 是 Android 平台配备一个很好的性能分析的工具。它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到 method。详细内容参考:Profiling with Traceview and dmtracedump
TraceView 简介
TraceView 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程序的 hotspot。TraceView 本身只是一个数据分析工具,而数据的采集则需要使用 Android SDK 中的 Debug 类或者利用 DDMS 工具。二者的用法如下:
开 发者在一些关键代码段开始前调用 Android SDK 中 Debug 类的 startMethodTracing 函数,并在关键代码段结束前调用 stopMethodTracing 函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是 Java 线程)的函数执行情况,并将采集数据保存到 /mnt/sdcard/ 下的一个文件中。开发者然后需要利用 SDK 中的 TraceView 工具来分析这些数据。
借助 Android SDK 中的 DDMS 工具。DDMS 可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。
DDMS 中 TraceView 使用示意图如下,调试人员可以通过选择 Devices 中的应用后点击  按钮 Start Method Profiling(开启方法分析)和点击   Stop Method Profiling(停止方法分析)
 
开启方法分析后对应用的目标页面进行测试操作,测试完毕后停止方法分析,界面会跳转到 DDMS 的 trace 分析界面,如下图所示:
 
TraceView 界面比较复杂,其 UI 划分为上下两个面板,即 Timeline Panel(时间线面板)和 Profile Panel(分析面板)。上图中的上半部分为 Timeline Panel(时间线面板),Timeline Panel 又可细分为左右两个 Pane:
左边 Pane 显示的是测试数据中所采集的线程信息。由图可知,本次测试数据采集了 main 线程,传感器线程和其它系统辅助线程的信息。
右边 Pane 所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图可知,Thread-1412 线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
另外,开发者可以在时间线 Pane 中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
上图中的下半部分为 Profile Panel(分析面板),Profile Panel 是 TraceView 的核心界面,其内涵非常丰富。它主要展示了某个线程(先在 Timeline Panel 中选择线程)中各个函数调用的情况,包括 CPU 使用时间、调用次数等信息。而这些信息正是查找 hotspot 的关键依据。所以,对开发者而言,一定要了解 Profile Panel 中各列的含义。下表列出了 Profile Panel 中比较重要的列名及其描述。
 
TraceView 实战
了解完 TraceView 的 UI 后,现在介绍如何利用 TraceView 来查找 hotspot。一般而言,hotspot 包括两种类型的函数:
一类是调用次数不多,但每次调用却需要花费很长时间的函数。
一类是那些自身占用时间不长,但调用却非常频繁的函数。
测试背景:APP 在测试机运行一段时间后出现手机发烫、卡顿、高 CPU 占有率的现象。将应用切入后台进行 CPU 数据的监测,结果显示,即使应用不进行任何操作,应用的 CPU 占有率都会持续的增长。
按照 TraceView 简介中的方法进行测试,TraceView 结果 UI 显示后进行数据分析,在 Profile Panel 中,选择按 Cpu Time/Call 进行降序排序(从上之下排列,每项的耗费时间由高到低)得到如图所示结果:
 
图中 ImageLoaderTools$2.run() 是应用程序中的函数,它耗时为 1111.124。然后点击 ImageLoaderTools$2.run() 项,得到更为详尽的调用关系图:
 
上图中 Parents 为 ImageLoaderTools$2.run() 方法的调用者:Parents (the methods calling this method);Children 为 ImageLoaderTools$2.run() 调用的子函数或方法:Children (the methods called by this method)。本例中 ImageLoaderTools$2.run() 方法的调用者为 Framework 部分,而  ImageLoaderTools$2.run() 方法调 用的自方法中我们却发现有三个方法的 Incl Cpu Time % 占用均达到了 14% 以上,更离谱的是 Calls+RecurCalls/Total 显示这三个方法均被调用了 35000 次以上,从包名可以识别出这些方法为测试者自身所实现,由此可以判断 ImageLoaderTools$2.run() 极有可能是手机发烫、卡顿、高 CPU 占用率的原因所在。
代码验证
大致可以判断是 ImageLoaderTools$2.run() 方法出现了问题,下面找到这个方法进行代码上的验证:
 
  1 package com.sunzn.app.utils;
  2
  3 import java.io.File;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.lang.ref.SoftReference;
  7 import java.util.ArrayList;
  8 import java.util.HashMap;
  9
 10 import android.content.Context;
 11 import android.graphics.Bitmap;
 12 import android.os.Environment;
 13 import android.os.Handler;
 14 import android.os.Message;
 15
 16 public class ImageLoaderTools {
 17
 18     private HttpTools httptool;
 19
 20     private Context mContext;
 21
 22     private boolean isLoop = true;
 23
 24     private HashMap<String, SoftReference<Bitmap>> mHashMap_caches;
 25
 26     private ArrayList<ImageLoadTask> maArrayList_taskQueue;
 27
 28     private Handler mHandler = new Handler() {
 29         public void handleMessage(android.os.Message msg) {
 30             ImageLoadTask loadTask = (ImageLoadTask) msg.obj;
 31             loadTask.callback.imageloaded(loadTask.path, loadTask.bitmap);
 32         };
 33     };
 34
 35     private Thread mThread = new Thread() {
 36
 37         public void run() {
 38
 39             while (isLoop) {
 40
 41                 while (maArrayList_taskQueue.size() > 0) {
 42
 43                     try {
 44                         ImageLoadTask task = maArrayList_taskQueue.remove(0);
 45
 46                         if (Constant.LOADPICTYPE == 1) {
 47                             byte[] bytes = httptool.getByte(task.path, null, HttpTools.METHOD_GET);
 48                             task.bitmap = BitMapTools.getBitmap(bytes, 40, 40);
 49                         } else if (Constant.LOADPICTYPE == 2) {
 50                             InputStream in = httptool.getStream(task.path, null, HttpTools.METHOD_GET);
 51                             task.bitmap = BitMapTools.getBitmap(in, 1);
 52                         }
 53
 54                         if (task.bitmap != null) {
 55                             mHashMap_caches.put(task.path, new SoftReference<Bitmap>(task.bitmap));
 56                             File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
 57                             if (!dir.exists()) {
 58                                 dir.mkdirs();
 59                             }
 60                             String[] path = task.path.split("/");
 61                             String filename = path[path.length - 1];
 62                             File file = new File(dir, filename);
 63                             BitMapTools.saveBitmap(file.getAbsolutePath(), task.bitmap);
 64                             Message msg = Message.obtain();
 65                             msg.obj = task;
 66                             mHandler.sendMessage(msg);
 67                         }
 68                     } catch (IOException e) {
 69                         e.printStackTrace();
 70                     } catch (Exception e) {
 71                         e.printStackTrace();
 72                     }
 73
 74                     synchronized (this) {
 75                         try {
 76                             wait();
 77                         } catch (InterruptedException e) {
 78                             e.printStackTrace();
 79                         }
 80                     }
 81
 82                 }
 83
 84             }
 85
 86         };
 87
 88     };
 89
 90     public ImageLoaderTools(Context context) {
 91         this.mContext = context;
 92         httptool = new HttpTools(context);
 93         mHashMap_caches = new HashMap<String, SoftReference<Bitmap>>();
 94         maArrayList_taskQueue = new ArrayList<ImageLoaderTools.ImageLoadTask>();
 95         mThread.start();
 96     }
 97
 98     private class ImageLoadTask {
 99         String path;
100         Bitmap bitmap;
101         Callback callback;
102     }
103
104     public interface Callback {
105         void imageloaded(String path, Bitmap bitmap);
106     }
107
108     public void quit() {
109         isLoop = false;
110     }
111
112     public Bitmap imageLoad(String path, Callback callback) {
113         Bitmap bitmap = null;
114         String[] path1 = path.split("/");
115         String filename = path1[path1.length - 1];
116
117         if (mHashMap_caches.containsKey(path)) {
118             bitmap = mHashMap_caches.get(path).get();
119             if (bitmap == null) {
120                 mHashMap_caches.remove(path);
121             } else {
122                 return bitmap;
123             }
124         }
125
126         File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
127
128         File file = new File(dir, filename);
129
130         bitmap = BitMapTools.getBitMap(file.getAbsolutePath());
131         if (bitmap != null) {
132             return bitmap;
133         }
134
135         ImageLoadTask task = new ImageLoadTask();
136         task.path = path;
137         task.callback = callback;
138         maArrayList_taskQueue.add(task);
139
140         synchronized (mThread) {
141             mThread.notify();
142         }
143
144         return null;
145     }
146
147 }
 
以上代码即是 ImageLoaderTools 图片工具类的全部代码,先不着急去研究这个类的代码实现过程,先来看看这个类是怎么被调用的:
 
 1 ImageLoaderTools imageLoaderTools = imageLoaderTools = new ImageLoaderTools(this);
 2
 3 Bitmap bitmap = imageLoaderTools.imageLoad(picpath, new Callback() {
 4
 5     @Override
 6     public void imageloaded(String picPath, Bitmap bitmap) {
 7         if (bitmap == null) {
 8             imageView.setImageResource(R.drawable.default);
 9         } else {
10             imageView.setImageBitmap(bitmap);
11         }
12     }
13 });
14
15 if (bitmap == null) {
16     imageView.setImageResource(R.drawable.fengmianmoren);
17 } else {
18     imageView.setImageBitmap(bitmap);
19 }
 
ImageLoaderTools 被调用的过程非常简单:1.ImageLoaderTools 实例化;2.执行 imageLoad() 方法加载图片。
在 ImageLoaderTools 类的构造函数(90行-96行)进行实例化过程中完成了网络工具 HttpTools 初始化、新建一个图片缓存 Map、新建一个下载队列、开启下载线程的操作。这时候请注意开启线程的操作,开启线程后执行 run() 方法(35行-88行),这时 isLoop 的值是默认的 true,maArrayList_taskQueue.size() 是为 0 的,在任务队列 maArrayList_taskQueue 中还没有加入下载任务之前这个循环会一直循环下去。在执行 imageLoad() 方法加载图片时会首先去缓存 mHashMap_caches 中查找该图片是否已经被下载过,如果已经下载过则直接返回与之对应的 bitmap 资源,如果没有查找到则会往 maArrayList_taskQueue 中添加下载任务并唤醒对应的下载线程,之前开启的线程在发现 maArrayList_taskQueue.size() > 0 后就进入下载逻辑,下载完任务完成后将对应的图片资源加入缓存 mHashMap_caches 并更新 UI,下载线程执行 wait() 方法被挂起。一 个图片下载的业务逻辑这样理解起来很顺畅,似乎没有什么问题。开始我也这样认为,但后来在仔细的分析代码的过程中发现如果同样一张图片资源重新被加载就会 出现死循环。还记得缓存 mHashMap_caches 么?如果一张图片之前被下载过,那么缓存中就会有这张图片的引用存在。重新去加载这张图片的时候如果重复的去初始化 ImageLoaderTools,线程会被开启,而使用 imageLoad() 方法加载图片时发现缓存中存在这个图片资源,则会将其直接返回,注意这里使用的是 return bitmap; 那 就意味着 imageLoad() 方法里添加下载任务到下载队列的代码不会被执行到,这时候 run() 方法中的 isLoop = true 并且 maArrayList_taskQueue.size() = 0,这样内层 while 里的逻辑也就是挂起线程的关键代码 wait() 永远不会被执行到,而外层 while 的判断条件一直为 true,就这样程序出现了死循环。死循环才是手机发烫、卡顿、高 CPU 占用率的真正原因所在。
解决方案
准确的定位到代码问题所在后,提出解决方案就很简单了,这里提供的解决方案是将 wait() 方法从内层 while 循环提到外层 while 循环中,这样重复加载同一张图片时,死循环一出现线程就被挂起,这样就可以避免死循环的出现。代码如下:
 
 1 private Thread mThread = new Thread() {
 2
 3     public void run() {
 4
 5         while (isLoop) {
 6
 7             while (maArrayList_taskQueue.size() > 0) {
 8
 9                 try {
10                     ImageLoadTask task = maArrayList_taskQueue.remove(0);
11
12                     if (Constant.LOADPICTYPE == 1) {
13                         byte[] bytes = httptool.getByte(task.path, null, HttpTools.METHOD_GET);
14                         task.bitmap = BitMapTools.getBitmap(bytes, 40, 40);
15                     } else if (Constant.LOADPICTYPE == 2) {
16                         InputStream in = httptool.getStream(task.path, null, HttpTools.METHOD_GET);
17                         task.bitmap = BitMapTools.getBitmap(in, 1);
18                     }
19
20                     if (task.bitmap != null) {
21                         mHashMap_caches.put(task.path, new SoftReference<Bitmap>(task.bitmap));
22                         File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
23                         if (!dir.exists()) {
24                             dir.mkdirs();
25                         }
26                         String[] path = task.path.split("/");
27                         String filename = path[path.length - 1];
28                         File file = new File(dir, filename);
29                         BitMapTools.saveBitmap(file.getAbsolutePath(), task.bitmap);
30                         Message msg = Message.obtain();
31                         msg.obj = task;
32                         mHandler.sendMessage(msg);
33                     }
34                 } catch (IOException e) {
35                     e.printStackTrace();
36                 } catch (Exception e) {
37                     e.printStackTrace();
38                 }
39
40             }
41             
42             synchronized (this) {
43                 try {
44                     wait();
45                 } catch (InterruptedException e) {
46                     e.printStackTrace();
47                 }
48             }
49
50         }
51
52     };
53
54 };
 
最后再附上代码修改后代码运行的性能图,和之前的多次被重复执行,效率有了质的提升,手机发烫、卡顿、高 CPU 占用率的现象也消失了。
 
专注移动互联网产品设计研发 分享最新的移动互联网产品和技术








http://www.oschina.net/news/60157/android-performance-patterns

Google 发布 Android 性能优化典范
oschina 发布于: 2015年03月04日 (55评)
分享到:
收藏 +696
3月19日,深圳源创会火热报名中,go>>>»  
 
2015年伊始,Google发布了关于Android性能优化典范的专题, 一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App。课程专题不仅仅介绍了Android系统中有关性能问题的底层工作原理,同时也介绍了如何通过工具来找出性能问题以及提升性能的建议。主要从三个 方面展开,Android的渲染机制,内存与GC,电量优化。下面是对这些问题和建议的总结梳理。
0)Render Performance
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。从设计师的角度,他们希望App能够有更多的动画,图片等时尚元素来实现流畅的用 户体验。但是Android系统很有可能无法及时完成那些复杂的界面渲染操作。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
 
如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。
 
用户容易在UI执行动画或者滑动ListView的时候感知到卡顿不流畅,是因为这里的操作相对复杂,容易发生丢帧的现象,从而感觉卡顿。有很多原 因可以导致丢帧,也许是因为你的layout太过复杂,无法在16ms内完成渲染,有可能是因为你的UI上有层叠太多的绘制单元,还有可能是因为动画执行 的次数过多。这些都会导致CPU或者GPU负载过重。
我们可以通过一些工具来定位问题,比如可以使用HierarchyViewer来查找Activity中的布局是否过于复杂,也可以使用手机设置里 面的开发者选项,打开Show GPU Overdraw等选项进行观察。你还可以使用TraceView来观察CPU的执行情况,更加快捷的找到性能瓶颈。
1)Understanding Overdraw
Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
 
当设计上追求更华丽的视觉效果的时候,我们就容易陷入采用越来越多的层叠组件来实现这种视觉效果的怪圈。这很容易导致大量的性能问题,为了获得最佳的性能,我们必须尽量减少Overdraw的情况发生。
幸运的是,我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,可以观察UI上的Overdraw情况。
 
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Overdraw有时候是因为你的UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面 的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加 蓝色区域的占比。这一措施能够显著提升程序性能。
2)Understanding VSYNC
为了理解App是如何进行渲染的,我们必须了解手机硬件是如何工作,那么就必须理解什么是VSYNC。
在讲解VSYNC之前,我们需要了解两个相关的概念:
Refresh Rate:代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60Hz。
Frame Rate:代表了GPU在一秒内绘制操作的帧数,例如30fps,60fps。
GPU会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停的进行协作。
 
不幸的是,刷新频率和帧率并不是总能够保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现Tearing的现象(画面上下两部分显示内容发生断裂,来自不同的两帧数据发生重叠)。
 
 
理解图像渲染里面的双重与三重缓存机制,这个概念比较复杂,请移步查看这里:http://source.android.com/devices/graphics/index.html,还有这里http://article.yeeyan.org/view/37503/304664。
通常来说,帧率超过刷新频率只是一种理想的状况,在超过60fps的情况下,GPU所产生的帧数据会因为等待VSYNC的刷新信息而被Hold住,这样能够保持每次刷新都有实际的新的数据可以显示。但是我们遇到更多的情况是帧率小于刷新频率。
 
在这种情况下,某些帧显示的画面内容就会与上一帧的画面相同。糟糕的事情是,帧率从超过60fps突然掉到60fps以下,这样就会发生LAG,JANK,HITCHING等卡顿掉帧的不顺滑的情况。这也是用户感受不好的原因所在。
3)Tool:Profile GPU Rendering
性能问题如此的麻烦,幸好我们可以有工具来进行调试。打开手机里面的开发者选项,选择Profile GPU Rendering,选中On screen as bars的选项。
 
选择了这样以后,我们可以在手机画面上看到丰富的GPU绘制图形信息,分别关于StatusBar,NavBar,激活的程序Activity区域的GPU Rending信息。
 
随着界面的刷新,界面上会滚动显示垂直的柱状图来表示每帧画面所需要渲染的时间,柱状图越高表示花费的渲染时间越长。
 
中间有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
 
每一条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间。
4)Why 60fps?
我们通常都会提到60fps与16ms,可是知道为何会是以程序是否达到60fps来作为App性能的衡量标准吗?这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。
12fps大概类似手动快速翻动书籍的帧率,这明显是可以感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的 效果。24fps是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容,同时能够最大的减少费用支出。但是低于30fps是 无法顺畅表现绚丽的画面内容的,此时就需要用到60fps来达到想要的效果,当然超过60fps是没有必要的。
开发app的性能目标就是保持60fps,这意味着每一帧你只有16ms=1000/60的时间来处理所有的任务。
5)Android, UI and the GPU
了解Android是如何利用GPU进行画面渲染有助于我们更好的理解性能问题。那么一个最实际的问题是:activity的画面是如何绘制到屏幕上的?那些复杂的XML布局文件又是如何能够被识别并绘制出来的?
 
Resterization栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作。它把那些组件拆分到不同的像素上进行显示。这是一个很费时的操作,GPU的引入就是为了加快栅格化的操作。
CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染。
 
然而每次从CPU转移到GPU是一件很麻烦的事情,所幸的是OpenGL ES可以把那些需要渲染的纹理Hold在GPU Memory里面,在下次需要渲染的时候直接进行操作。所以如果你更新了GPU所hold住的纹理内容,那么之前保存的状态就丢失了。
在Android里面那些由主题所提供的资源,例如Bitmaps,Drawables都是一起打包到统一的Texture纹理当中,然后再传递到 GPU里面,这意味着每次你需要使用这些资源的时候,都是直接从纹理里面进行获取渲染的。当然随着UI组件的越来越丰富,有了更多演变的形态。例如显示图 片的时候,需要先经过CPU的计算加载到内存中,然后传递给GPU进行渲染。文字的显示更加复杂,需要先经过CPU换算成纹理,然后再交给GPU进行渲 染,回到CPU绘制单个字符的时候,再重新引用经过GPU渲染的内容。动画则是一个更加复杂的操作流程。
为了能够使得App流畅,我们需要在每一帧16ms以内处理完所有的CPU与GPU计算,绘制,渲染等等操作。
6)Invalidations, Layouts, and Performance
顺滑精妙的动画是app设计里面最重要的元素之一,这些动画能够显著提升用户体验。下面会讲解Android系统是如何处理UI组件的更新操作的。
通常来说,Android需要把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
在某个View第一次需要被渲染时,DisplayList会因此而被创建,当这个View要显示到屏幕上时,我们会执行GPU的绘制指令来进行渲 染。如果你在后续有执行类似移动这个View的位置等操作而需要再次渲染这个View时,我们就仅仅需要额外操作一次渲染指令就够了。然而如果你修改了 View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并 更新到屏幕上。
需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一 系列操作。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子,假设某个Button的大小需要增大 到目前的两倍,在增大Button大小之前,需要通过父View重新计算并摆放其他子View的位置。修改View的大小会触发整个 HierarcyView的重新计算大小的操作。如果是修改View的位置则会触发HierarchView重新计算其他View的位置。如果布局很复 杂,这就会很容易导致严重的性能问题。我们需要尽量减少Overdraw。
 
我们可以通过前面介绍的Monitor GPU Rendering来查看渲染的表现性能如何,另外也可以通过开发者选项里面的Show GPU view updates来查看视图更新的操作,最后我们还可以通过HierarchyViewer这个工具来查看布局,使得布局尽量扁平化,移除非必需的UI组 件,这些操作能够减少Measure,Layout的计算时间。
7)Overdraw, Cliprect, QuickReject
引起性能问题的一个很重要的方面是因为过多复杂的绘制操作。我们可以通过工具来检测并修复标准UI组件的Overdraw问题,但是针对高度自定义的UI组件则显得有些力不从心。
有一个窍门是我们可以通过执行几个APIs方法来显著提升绘制操作的性能。前面有提到过,非可见的UI组件进行绘制更新会导致Overdraw。例 如Nav Drawer从前置可见的Activity滑出之后,如果还继续绘制那些在Nav Drawer里面不可见的UI组件,这就导致了Overdraw。为了解决这个问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少 Overdraw。那些Nav Drawer里面不可见的View就不会被执行浪费资源。
 
但是不幸的是,对于那些过于复杂的自定义的View(重写了onDraw方法),Android系统无法检测具体在onDraw里面会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来 帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠 组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执 行,那些部分内容在矩形区域内的组件,仍然会得到绘制。
 
除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。做了那些优化之后,我们可以通过上面介绍的Show GPU Overdraw来查看效果。
8)Memory Churn and performance
虽然Android有自动管理内存的机制,但是对内存的不恰当使用仍然容易引起严重的性能问题。在同一帧里面创建过多的对象是件需要特别引起注意的事情。
Android系统里面有一个Generational Heap Memory的模型,系统会根据内存中不同 的内存数据类型分别执行不同的GC操作。例如,最近刚分配的对象会放在Young Generation区域,这个区域的对象通常都是会快速被创建并且很快被销毁回收的,同时这个区域的GC操作速度也是比Old Generation区域的GC操作速度更快的。
 
除了速度差异之外,执行GC操作的时候,任何线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。
 
通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。
导致GC频繁执行有两个原因:
Memory Churn内存抖动,内存抖动是因为大量的对象被创建又在短时间内马上被释放。
瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加 Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。
 
解决上面的问题有简洁直观方法,如果你在Memory Monitor里面查看到短时间发生了多次内存的涨跌,这意味着很有可能发生了内存抖动。
 
同时我们还可以通过Allocation Tracker来查看在短时间内,同一个栈中不断进出的相同对象。这是内存抖动的典型信号之一。
当你大致定位问题之后,接下去的问题修复也就显得相对直接简单了。例如,你需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循 环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw 方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里 需要注意结束使用之后,需要手动释放对象池中的对象。
9)Garbage Collection in Android
JVM的回收机制给开发人员带来很大的好处,不用时刻处理对象的分配与回收,可以更加专注于更加高级的代码实现。相比起Java,C与C++等语言 具备更高的执行效率,他们需要开发人员自己关注对象的分配与回收,但是在一个庞大的系统当中,还是免不了经常发生部分对象忘记回收的情况,这就是内存泄 漏。
原始JVM中的GC机制在Android中得到了很大程度上的优化。Android里面是一个三级Generation的内存模型,最近分配的对象 会存放在Young Generation区域,当这个对象在这个区域停留的时间达到一定程度,它会被移动到Old Generation,最后到Permanent Generation区域。
 
每一个级别的内存区域都有固定的大小,此后不断有新的对象被分配到此区域,当这些对象总的大小快达到这一级别内存区域的阀值时,会触发GC的操作,以便腾出空间来存放其他新的对象。
 
前面提到过每次GC发生的时候,所有的线程都是暂停状态的。GC所占用的时间和它是哪一个Generation也有关系,Young Generation的每次GC操作时间是最短的,Old Generation其次,Permanent Generation最长。执行时间的长短也和当前Generation中的对象数量有关,遍历查找20000个对象比起遍历50个对象自然是要慢很多 的。
虽然Google的工程师在尽量缩短每次GC所花费的时间,但是特别注意GC引起的性能问题还是很有必要。如果不小心在最小的for循环单元里面执 行了创建对象的操作,这将很容易引起GC并导致性能问题。通过Memory Monitor我们可以查看到内存的占用情况,每一次瞬间的内存降低都是因为此时发生了GC操作,如果在短时间内发生大量的内存上涨与降低的事件,这说明 很有可能这里有性能问题。我们还可以通过Heap and Allocation Tracker工具来查看此时内存中分配的到底有哪些对象。
10)Performance Cost of Memory Leaks
虽然Java有自动回收的机制,可是这不意味着Java中不存在内存泄漏的问题,而内存泄漏会很容易导致严重的性能问题。
内存泄漏指的是那些程序不再使用的对象无法被GC识别,这样就导致这个对象一直留在内存当中,占用了宝贵的内存空间。显然,这还使得每级Generation的内存区域可用空间变小,GC就会更容易被触发,从而引起性能问题。
寻找内存泄漏并修复这个漏洞是件很棘手的事情,你需要对执行的代码很熟悉,清楚的知道在特定环境下是如何运行的,然后仔细排查。例如,你想知道程序 中的某个activity退出的时候,它之前所占用的内存是否有完整的释放干净了?首先你需要在activity处于前台的时候使用Heap Tool获取一份当前状态的内存快照,然后你需要创建一个几乎不这么占用内存的空白activity用来给前一个Activity进行跳转,其次在跳转到 这个空白的activity的时候主动调用System.gc()方法来确保触发一个GC操作。最后,如果前面这个activity的内存都有全部正确释 放,那么在空白activity被启动之后的内存快照中应该不会有前面那个activity中的任何对象了。
 
如果你发现在空白activity的内存快照中有一些可疑的没有被释放的对象存在,那么接下去就应该使用Alocation Track Tool来仔细查找具体的可疑对象。我们可以从空白activity开始监听,启动到观察activity,然后再回到空白activity结束监听。这样操作以后,我们可以仔细观察那些对象,找出内存泄漏的真凶。
 
11)Memory Performance
通常来说,Android对GC做了大量的优化操作,虽然执行GC操作的时候会暂停其他任务,可是大多数情况下,GC操作还是相对很安静并且高效的。但是如果我们对内存的使用不恰当,导致GC频繁执行,这样就会引起不小的性能问题。
为了寻找内存的性能问题,Android Studio提供了工具来帮助开发者。
Memory Monitor:查看整个app所占用的内存,以及发生GC的时刻,短时间内发生大量的GC操作是一个危险的信号。
Allocation Tracker:使用此工具来追踪内存的分配,前面有提到过。
Heap Tool:查看当前内存快照,便于对比分析哪些对象有可能是泄漏了的,请参考前面的Case。
12)Tool – Memory Monitor
Android Studio中的Memory Monitor可以很好的帮组我们查看程序的内存使用情况。
 
 
 
13)Battery Performance
电量其实是目前手持设备最宝贵的资源之一,大多数设备都需要不断的充电来维持继续使用。不幸的是,对于开发者来说,电量优化是他们最后才会考虑的的事情。但是可以确定的是,千万不能让你的应用成为消耗电量的大户。
Purdue University研究了最受欢迎的一些应用的电量消耗,平均只有30%左右的电量是被程序最核心的方法例如绘制图片,摆放布局等等所使用掉的,剩下的 70%左右的电量是被上报数据,检查位置信息,定时检索后台广告信息所使用掉的。如何平衡这两者的电量消耗,就显得非常重要了。
有下面一些措施能够显著减少电量的消耗:
我们应该尽量减少唤醒屏幕的次数与持续的时间,使用WakeLock来处理唤醒的问题,能够正确执行唤醒操作并根据设定及时关闭操作进入睡眠状态。
某些非必须马上执行的操作,例如上传歌曲,图片处理等,可以等到设备处于充电状态或者电量充足的时候才进行。
触发网络请求的操作,每次都会保持无线信号持续一段时间,我们可以把零散的网络请求打包进行一次操作,避免过多的无线信号引起的电量消耗。关于网络请求引起无线信号的电量消耗,还可以参考这里http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
我们可以通过手机设置选项找到对应App的电量消耗统计数据。我们还可以通过Battery Historian Tool来查看详细的电量消耗。
 
如果发现我们的App有电量消耗过多的问题,我们可以使用JobScheduler API来对一些任务进行定时处理,例如我们可以把那些任务重的操作等到手机处于充电状态,或者是连接到WiFi的时候来处理。
关于JobScheduler的更多知识可以参考http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html
14)Understanding Battery Drain on Android
电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情。唯一可行的方案是使用第三方监测电量的设备,这样才能够获取到真实的电量消耗。
当设备处于待机状态时消耗的电量是极少的,以N5为例,打开飞行模式,可以待机接近1个月。可是点亮屏幕,硬件各个模块就需要开始工作,这会需要消耗很多电量。
使用WakeLock或者JobScheduler唤醒设备处理定时的任务之后,一定要及时让设备回到初始状态。每次唤醒无线信号进行数据传递,都会消耗很多电量,它比WiFi等操作更加的耗电,详情请关注http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
 
修复电量的消耗是另外一个很大的课题,这里就不展开继续了。
15)Battery Drain and WakeLocks
高效的保留更多的电量与不断促使用户使用你的App来消耗电量,这是矛盾的选择题。不过我们可以使用一些更好的办法来平衡两者。
假设你的手机里面装了大量的社交类应用,即使手机处于待机状态,也会经常被这些应用唤醒用来检查同步新的数据信息。Android会不断关闭各种硬 件来延长手机的待机时间,首先屏幕会逐渐变暗直至关闭,然后CPU进入睡眠,这一切操作都是为了节约宝贵的电量资源。但是即使在这种睡眠状态下,大多数应 用还是会尝试进行工作,他们将不断的唤醒手机。一个最简单的唤醒手机的方法是使用PowerManager.WakeLock的API来保持CPU工作并 防止屏幕变暗关闭。这使得手机可以被唤醒,执行工作,然后回到睡眠状态。知道如何获取WakeLock是简单的,可是及时释放WakeLock也是非常重 要的,不恰当的使用WakeLock会导致严重错误。例如网络请求的数据返回时间不确定,导致本来只需要10s的事情一直等待了1个小时,这样会使得电量 白白浪费了。这也是为何使用带超时参数的wakelock.acquice()方法是很关键的。但是仅仅设置超时并不足够解决问题,例如设置多长的超时比 较合适?什么时候进行重试等等?
解决上面的问题,正确的方式可能是使用非精准定时器。通常情况下,我们会设定一个时间进行某个操作,但是动态修改这个时间也许会更好。例如,如果有 另外一个程序需要比你设定的时间晚5分钟唤醒,最好能够等到那个时候,两个任务捆绑一起同时进行,这就是非精确定时器的核心工作原理。我们可以定制计划的 任务,可是系统如果检测到一个更好的时间,它可以推迟你的任务,以节省电量消耗。
 
这正是JobScheduler API所做的事情。它会根据当前的情况与任务,组合出理想的唤醒时间,例如等到正在充电或者连接到WiFi的时候,或者集中任务一起执行。我们可以通过这个API实现很多免费的调度算法。
从Android 5.0开始发布了Battery History Tool,它可以查看程序被唤醒的频率,又谁唤醒的,持续了多长的时间,这些信息都可以获取到。
请关注程序的电量消耗,用户可以通过手机的设置选项观察到那些耗电量大户,并可能决定卸载他们。所以尽量减少程序的电量消耗是非常有必要的。
稿源:hukai.me


Android MemInfo 各项的意义(转) 
可以使用adb shell dumpsys meminfo -a <process id>/<process name>来查看一个进程的memory。截图如下:
 
Naitve Heap Size: 从mallinfo usmblks获得,代表最大总共分配空间
Native Heap Alloc: 从mallinfo uorblks获得,总共分配空间
Native Heap Free: 从mallinfo fordblks获得,代表总共剩余空间 
Native Heap Size 约等于Native Heap Alloc + Native Heap Free
mallinfo是一个C库, mallinfo 函数提供了各种各样的通过C的malloc()函数分配的内存的统计信息。
Dalvik Heap Size:从Runtime totalMemory()获得,Dalvik Heap总共的内存大小。
Dalvik Heap Alloc: Runtime totalMemory()-freeMemory() ,Dalvik Heap分配的内存大小。
Dalvik Heap Free:从Runtime freeMemory()获得,Dalvik Heap剩余的内存大小。
Dalvik Heap Size 约等于Dalvik  Heap Alloc + Dalvik  Heap Free
OtherPss, include Cursor,Ashmem, Other Dev, .so mmap, .jar mmap, .apk mmap, .ttf mmap, .dex mmap, Other mmap, Unkown统计信息都可以在process的smap文件看到。
Objects and SQL 信息都是从Android Debug信息中获得。
其他类型               smap 路径名称          描述
Cursor                  /dev/ashmem/Cursor  Cursor消耗的内存(KB)
Ashmem               /dev/ashmem            匿名共享内存用来提供共享内存通过分配一个多个进程
                                                         可以共享的带名称的内存块
Other dev             /dev/                        内部driver占用的在 “Other dev”                                                 
.so mmap             .so                            C 库代码占用的内存
.jar mmap            .jar                           Java 文件代码占用的内存
.apk mmap           .apk                           apk代码占用的内存
.ttf mmap              .ttf                           ttf 文件代码占用的内存
.dex mmap             .dex                         Dex 文件代码占用的内存
Other mmap                                          其他文件占用的内存

But as to what the difference is between "Pss", "PrivateDirty", and "SharedDirty"... well now the fun begins.
A lot of memory in Android (and Linux systems in general) is actually shared across multiple processes. So how much memory a processes uses is really not clear. Add on top of that paging out to disk (let alone swap which we don't use on Android) and it is even less clear.
Thus if you were to take all of the physical RAM actually mapped in to each process, and add up all of the processes, you would probably end up with a number much greater than the actual total RAM.
The Pss number is a metric the kernel computes that takes into account memory sharing -- basically each page of RAM in a process is scaled by a ratio of the number of other processes also using that page. This way you can (in theory) add up the pss across all processes to see the total RAM they are using, and compare pss between processes to get a rough idea of their relative weight.
The other interesting metric here is PrivateDirty, which is basically the amount of RAM inside the process that can not be paged to disk (it is not backed by the same data on disk), and is not shared with any other processes. Another way to look at this is the RAM that will become available to the system when that process goes away (and probably quickly subsumed into caches and other uses of it).
That is pretty much the SDK APIs for this. However there is more you can do as a developer with your device.
Using adb, there is a lot of information you can get about the memory use of a running system. A common one is the command "adb shell dumpsys meminfo" which will spit out a bunch of information about the memory use of each Java process, containing the above info as well as a variety of other things. You can also tack on the name or pid of a single process to see, for example "adb shell dumpsys meminfo system" give me the system process:
** MEMINFO in pid 890 [system] **
                    native   dalvik    other    total
            size:    10940     7047      N/A    17987
       allocated:     8943     5516      N/A    14459
            free:      336     1531      N/A     1867
           (Pss):     4585     9282    11916    25783
  (shared dirty):     2184     3596      916     6696
    (priv dirty):     4504     5956     7456    17916

 Objects
           Views:      149        ViewRoots:        4
     AppContexts:       13       Activities:        0
          Assets:        4    AssetManagers:        4
   Local Binders:      141    Proxy Binders:      158
Death Recipients:       49
 OpenSSL Sockets:        0

 SQL
            heap:      205          dbFiles:        0
       numPagers:        0   inactivePageKB:        0
    activePageKB:        0
The top section is the main one, where "size" is the total size in address space of a particular heap, "allocated" is the kb of actual allocations that heap thinks it has, "free" is the remaining kb free the heap has for additional allocations, and "pss" and "priv dirty" are the same as discussed before specific to pages associated with each of the heaps.
If you just want to look at memory usage across all processes, you can use the command "adb shell procrank". Output of this on the same system looks like:
  PID      Vss      Rss      Pss      Uss  cmdline
  890   84456K   48668K   25850K   21284K  system_server
 1231   50748K   39088K   17587K   13792K  com.android.launcher2
  947   34488K   28528K   10834K    9308K  com.android.wallpaper
  987   26964K   26956K    8751K    7308K  com.google.process.gapps
  954   24300K   24296K    6249K    4824K  com.android.phone
  948   23020K   23016K    5864K    4748K  com.android.inputmethod.latin
  888   25728K   25724K    5774K    3668K  zygote
  977   24100K   24096K    5667K    4340K  android.process.acore
...
   59     336K     332K      99K      92K  /system/bin/installd
   60     396K     392K      93K      84K  /system/bin/keystore
   51     280K     276K      74K      68K  /system/bin/servicemanager
   54     256K     252K      69K      64K  /system/bin/debuggerd
Here the Vss and Rss columns are basically noise (these are the straight-forward address space and RAM usage of a process, where if you add up the RAM usage across processes you get an ridiculously large number).
Pss is as we've seen before, and Uss is Priv Dirty.
Interesting thing to note here: Pss and Uss are slightly (or more than slightly) different than what we saw in meminfo. Why is that? Well procrank uses a different kernel mechanism to collect its data than meminfo does, and they give slightly different results. Why is that? Honestly I haven't a clue. I believe procrank may be the more accurate one... but really, this just leave the point: "take any memory info you get with a grain of salt; often a very large grain."
Finally there is the command "adb shell cat /proc/meminfo" that gives a summary of the overall memory usage of the system. There is a lot of data here, only the first few numbers worth discussing (and the remaining ones understood by few people, and my questions of those few people about them often resulting in conflicting explanations):
MemTotal:         395144 kB
MemFree:          184936 kB
Buffers:             880 kB
Cached:            84104 kB
SwapCached:            0 kB
MemTotal is the total amount of memory available to the kernel and user space (often less than the actual physical RAM of the device, since some of that RAM is needed for the radio, DMA buffers, etc).
MemFree is the amount of RAM that is not being used at all. The number you see here is very high; typically on an Android system this would be only a few MB, since we try to use available memory to keep processes running
Cached is the RAM being used for filesystem caches and other such things. Typical systems will need to have 20MB or so for this to avoid getting into bad paging states; the Android out of memory killer is tuned for a particular system to make sure that background processes are killed before the cached RAM is consumed too much by them to result in such paging.
















Android 内存分析工具 - LogCat GC

D/dalvikvm: , , ,

一、GC_Reason 触发垃圾回收的回收的集中原因:
 
类型    描述       
GC_CONCURRENT    内存使用将满时,并发的进行垃圾回收。       
GC_FOR_MALLOC    当内存已满应用尝试分配内存时会出触发垃圾回收,所以系统会停止应用进行垃圾整理       
GC_HPROF_DUMP_HEAP    当创建HPROF文件分析内存时触发垃圾收集。       
GC_EXPLICIT    显示的垃圾收集,例如当你调用gc() (应该避免调用,而是交由系统处理)       
GC_EXTERNAL_ALLOC    只会在API 10以下版本触发。新版都只会在Dalvik Heap上分配。    



二、Amount freed 回收的内存大小

三、Heap stats 空闲内存比例和(活跃对象总数/内存大小)

四、External memory stats API 10以下内存分配大小

五、Pause time 越大的堆暂停时间越长,并发会显示两个暂停:一个是回收开始时间,另外一个是回收结束时间


例子: D/dalvikvm(27235): GC_FOR_ALLOC freed 836K, 27% free 9653K/13116K, paused 101ms, total 104ms
 
LOG信息    描述       
freed 836K    此次回收836K       
27% free    可用内存空间27%       
9653K/13116K     活跃对象与总大小具体指       
paused 101ms    暂停进行垃圾回收用时101ms       
total 104ms    总用时104ms    

 
其他 external 0K/0K,表示可用外部内存/外部内存总量 paused 2ms+2ms,第一个时间值表示markrootset的时间,第二个时间值表示第二次mark的时间。 如果触发原因不是GC_CONCURRENT,这一行为单个时间值,表示垃圾收集的耗时时间。
可以通过在LogCat通过 “GC_” 关键字 + TAG 两项过滤

参考资料: https://developer.android.com/tools/debugging/debugging-memory.html#LogMessages





































http://www.it165.net/pro/html/201406/16404.html

Android使用procrank和dumpsysmeminfo分析内存占用情况
如果你想查看所有进程的内存使用情况,可以使用命令procrank、dumpsys meminfo查看,当然也只可以过滤出某个进程如:dumpsys meminfo | grep -i phone
先来看下procrank
 
view sourceprint?
01.sh-4.2# procrank
02.PID      Vss      Rss      Pss      Uss  cmdline
03.1078   59840K   59708K   42125K   39344K  com.csr.BTApp
04.2683   59124K   59040K   37960K   33032K  com.android.launcher
05.1042   51572K   51488K   35686K   33604K  android.process.acore
06.782   32808K   32748K   16775K   14716K  system_server
07.667   20560K   17560K   12739K    8940K  /system/bin/surfaceflinger
08.851   30124K   30036K   12085K    7996K  com.android.systemui
09.2999   27680K   27596K    9929K    7040K  com.baidu.input
10.959   20764K   20676K    5522K    3788K  com.android.phone
11.3468   21892K   21800K    4591K    1920K  com.apical.dreamthemetime
12.982   19880K   19792K    4438K    2644K  com.csr.csrservices
13.668   19592K   19480K    3525K    1360K  zygote
14.670    2960K    2960K    2407K    2356K  /system/bin/mediaserver
15.663    1784K    1784K    1209K    1116K  /system/bin/synergy_service
16.756    3404K    1348K    1133K    1124K  /usr/bin/gpsexe
17.669    1468K    1468K     959K     928K  /system/bin/drmserver
18.675     692K     692K     692K     692K  /bin/sh
19.758    1060K    1060K     630K     604K  /system/bin/audiotransfer
20.3482     656K     652K     456K     444K  procrank
21.664     664K     664K     403K     392K  /system/bin/netd
22.658     584K     584K     331K     320K  /system/bin/vold
23.666     548K     548K     270K     256K  /system/bin/rild
24.671     416K     412K     212K     204K  /system/bin/dbus-daemon
25.673     336K     332K     170K     164K  /system/bin/keystore
26.1     164K     164K     144K     144K  /init
27.674     152K     152K     136K     136K  /sbin/adbd
28.662     312K     312K     112K     104K  /system/bin/dvdd
29.672     328K     324K     109K     100K  /system/bin/installd
30.657     268K     264K     102K      96K  /system/bin/servicemanager
31.649      84K      84K      84K      84K  /sbin/ueventd
32.665     260K     256K      83K      76K  /system/bin/debuggerd
33.------   ------  ------
34.195031K  163724K  TOTAL
35.
36.RAM: 480380K total, 3624K free, 732K buffers, 299788K cached, 264844K shmem, 7632K slab
从以上打印可以看出,一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS
VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)是单个进程全部可访问的地址空间
RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)是单个进程实际占用的内存大小,对于单个共享库, 尽管无论多少个进程使用,实际该共享库只会被装入内存一次。
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)USS 是一个非常非常有用的数字, 因为它揭示了运行一个特定进程的真实的内存增量大小。如果进程被终止, USS 就是实际被返还给系统的内存大小。
USS 是针对某个进程开始有可疑内存泄露的情况,进行检测的最佳数字。怀疑某个程序有内存泄露可以查看这个值是否一直有增加
使用dumpsys meminfo查看内存信息
view sourceprint?
01.sh-4.2# dumpsys meminfo
02.Applications Memory Usage (kB):
03.Uptime: 3353863 Realtime: 3353850
04.
05.Total PSS by process:
06.41743 kB: com.csr.BTApp (pid 1078)
07.36924 kB: com.android.launcher (pid 2683)
08.35452 kB: android.process.acore (pid 1042)
09.16094 kB: system (pid 782)
10.11609 kB: com.android.systemui (pid 851)
11.8564 kB: com.baidu.input (pid 2999)
12.5298 kB: com.android.phone (pid 959)
13.4443 kB: com.apical.dreamthemetime (pid 4448)
14.4203 kB: com.csr.csrservices (pid 982)
15.4130 kB: com.apical.apicalradio (pid 4518)
16.
17.Total PSS by OOM adjustment:
18.16094 kB: System
19.16094 kB: system (pid 782)
20.21110 kB: Persistent
21.11609 kB: com.android.systemui (pid 851)
22.5298 kB: com.android.phone (pid 959)
23.4203 kB: com.csr.csrservices (pid 982)
24.36924 kB: Foreground
25.36924 kB: com.android.launcher (pid 2683)
26.85759 kB: Perceptible
27.41743 kB: com.csr.BTApp (pid 1078)
28.35452 kB: android.process.acore (pid 1042)
29.8564 kB: com.baidu.input (pid 2999)
30.4443 kB: A Services
31.4443 kB: com.apical.dreamthemetime (pid 4448)
32.4130 kB: Background
33.4130 kB: com.apical.apicalradio (pid 4518)
34.
35.Total PSS by category:
36.56020 kB: Dalvik
37.30214 kB: Other dev
38.27716 kB: Native
39.24504 kB: Cursor
40.13198 kB: Unknown
41.7723 kB: Other mmap
42.6895 kB: .so mmap
43.1232 kB: .apk mmap
44.888 kB: .dex mmap
45.36 kB: .ttf mmap
46.34 kB: Ashmem
47.0 kB: .jar mmap
48.
49.Total PSS: 168460 kB
打印某个程序内存信息,把包名写上,如:com.android.launcher
view sourceprint?
01.sh-4.2# dumpsys meminfo com.android.launcher
02.Applications Memory Usage (kB):
03.Uptime: 4497753 Realtime: 4497741
04.
05.** MEMINFO in pid 2683 [com.android.launcher] **
06.Shared  Private     Heap     Heap     Heap
07.Pss    Dirty    Dirty     Size    Alloc     Free
08.------   ------   ------   ------   ------   ------
09.Native     2144      988     2040     8636     5124     1699
10.Dalvik     9481     8292     8644    13639    13335      304
11.Cursor        0        0        0                         
12.Ashmem        2        4        0                         
13.Other dev        4       20        0                         
14..so mmap      922     1892      292                         
15..jar mmap        0        0        0                         
16..apk mmap       90        0        0                         
17..ttf mmap        0        0        0                         
18..dex mmap      300        0        0                         
19.Other mmap     1634       16      112                         
20.Unknown     1830      580     1772                         
21.TOTAL    16407    11792    12860    22275    18459     2003
22.
23.Objects
24.Views:      146         ViewRootImpl:        1
25.AppContexts:      374           Activities:        1
26.Assets:        4        AssetManagers:        4
27.Local Binders:       13        Proxy Binders:       23
28.Death Recipients:        1
29.OpenSSL Sockets:        0
30.
31.SQL
32.heap:       59          MEMORY_USED:       59
33.PAGECACHE_OVERFLOW:        1          MALLOC_SIZE:       46
34.
35.DATABASES
36.pgsz     dbsz   Lookaside(b)          cache  Dbname
37.1      179             55         2/11/2  launcher.db
38.
39.Asset Allocations
40.zip:/system/app/Launcher2.apk:/resources.arsc: 340K
41.zip:/system/app/MediaCenter.apk:/resources.arsc: 604K
Android程序内存被分为2部分:native和dalvik,dalvik就是我们平常说的java堆,我们创建的对象是在这里面分配的,而bitmap是直接在native上分配的,对于内存的限制是 native+dalvik 不能超过最大限制。Android程序内存一般限制在16M,当然也有24M的。
从上信息对于分析内存泄露,内存溢出都有极大的作用,从以上信息可以看到该应用程序占用的native和dalvik,当TOTAL 16407 11792 12860 22275 18459 2003超过内存最大限制时会出现OOM错误。
dumpsys能做的事还有很多
view sourceprint?
1.dumpsys [options]
2.meminfo 显示内存信息
3.cpuinfo 显示CPU信息
4.account 显示accounts信息
5.activity 显示所有的activities的信息
6.window 显示键盘,窗口和它们的关系
7.wifi 显示wifi信息
 






http://blog.csdn.net/watermusicyes/article/details/46333925
LeakCanary——直白的展现Android中的内存泄露
之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用。直到今天终于发现了这个新工具:
当我们的App中存在内存泄露时会在通知栏弹出通知:
 
当点击该通知时,会跳转到具体的页面,展示出Leak的引用路径,如下图所示:
 
LeakCanary 可以用更加直白的方式将内存泄露展现在我们的面前。
以下是我找到的学习资料,写的非常棒:
1、LeakCanary: 让内存泄露无所遁形
2、LeakCanary 中文使用说明
AndroidStudio (官方)上使用LeakCanary 请移步:
https://github.com/square/leakcanary
Eclipse 上使用LeakCanary 请移步我的:
https://github.com/SOFTPOWER1991/LeakcanarySample-Eclipse
android studio (自己弄的)上使用LeakCanary也可以看这个:
leakcanarySample_androidStudio
工程包括:
LeakCanary库代码
LeakCanaryDemo示例代码
使用步骤:
将LeakCanary import 入自己的工程
添加依赖:
compile project(':leakcanary')
在Application中进行配置
public class ExampleApplication extends Application {

  ......
  //在自己的Application中添加如下代码
public static RefWatcher getRefWatcher(Context context) {
    ExampleApplication application = (ExampleApplication) context
            .getApplicationContext();
    return application.refWatcher;
}

  //在自己的Application中添加如下代码
private RefWatcher refWatcher;

@Override
public void onCreate() {
    super.onCreate();
    ......
        //在自己的Application中添加如下代码
    refWatcher = LeakCanary.install(this);
    ......
}

.....
}
在Activity中进行配置
public class MainActivity extends AppCompatActivity {

    ......
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
            //在自己的应用初始Activity中加入如下两行代码
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(this);
        refWatcher.watch(this);

        textView = (TextView) findViewById(R.id.tv);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startAsyncTask();
            }
        });

    }

    private void async() {

        startAsyncTask();
    }

    private void startAsyncTask() {
        // This async task is an anonymous class and therefore has a hidden reference to the outer
        // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),
        // the activity instance will leak.
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                // Do some slow work in background
                SystemClock.sleep(20000);
                return null;
            }
        }.execute();
    }


}
在AndroidMainfest.xml 中进行配置,添加如下代码
        <service
            android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"
            android:enabled="false"
            android:process=":leakcanary" />
        <service
            android:name="com.squareup.leakcanary.DisplayLeakService"
            android:enabled="false" />

        <activity
            android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
            android:enabled="false"
            android:icon="@drawable/__leak_canary_icon"
            android:label="@string/__leak_canary_display_activity_label"
            android:taskAffinity="com.squareup.leakcanary"
            android:theme="@style/__LeakCanary.Base" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>














http://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
LeakCanary 中文使用说明
10 May 2015

LeakCanary
Android 和 Java 内存泄露检测。
“A small leak will sink a great ship.” - Benjamin Franklin
千里之堤, 毁于蚁穴。 -- 《韩非子·喻老》
 
demo
一个非常简单的 LeakCanary demo: https://github.com/liaohuqiu/leakcanary-demo
开始使用
在 build.gradle 中加入引用,不同的编译使用不同的引用:
 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
 }
在 Application 中:
public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}
这样,就万事俱备了! 在 debug build 中,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知。
为什么需要使用 LeakCanary?
问得好,看这个文章LeakCanary: 让内存泄露无所遁形
如何使用
使用 RefWatcher 监控那些本该被回收的对象。
RefWatcher refWatcher = {...};

// 监控
refWatcher.watch(schrodingerCat);
LeakCanary.install() 会返回一个预定义的 RefWatcher,同时也会启用一个 ActivityRefWatcher,用于自动监控调用 Activity.onDestroy() 之后泄露的 activity。
public class ExampleApplication extends Application {

  public static RefWatcher getRefWatcher(Context context) {
    ExampleApplication application = (ExampleApplication) context.getApplicationContext();
    return application.refWatcher;
  }

  private RefWatcher refWatcher;

  @Override public void onCreate() {
    super.onCreate();
    refWatcher = LeakCanary.install(this);
  }
}
使用 RefWatcher 监控 Fragment:
public abstract class BaseFragment extends Fragment {

  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
  }
}
工作机制
RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
然后在后台线程检查引用是否被清除,如果没有,调用GC。
如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
如何复制 leak trace?
在 Logcat 中,你可以看到类似这样的 leak trace:
In com.example.leakcanary:1.0:1 com.example.leakcanary.MainActivity has leaked:

* GC ROOT thread java.lang.Thread.<Java Local> (named 'AsyncTask #1')
* references com.example.leakcanary.MainActivity$3.this$0 (anonymous class extends android.os.AsyncTask)
* leaks com.example.leakcanary.MainActivity instance

* Reference Key: e71f3bf5-d786-4145-8539-584afaecad1d
* Device: Genymotion generic Google Nexus 6 - 5.1.0 - API 22 - 1440x2560 vbox86p
* Android Version: 5.1 API: 22
* Durations: watch=5086ms, gc=110ms, heap dump=435ms, analysis=2086ms
你甚至可以通过分享按钮把这些东西分享出去。
SDK 导致的内存泄露
随着时间的推移,很多SDK 和厂商 ROM 中的内存泄露问题已经被尽快修复了。但是,当这样的问题发生时,一般的开发者能做的事情很有限。
LeakCanary 有一个已知问题的忽略列表,AndroidExcludedRefs.java,如果你发现了一个新的问题,请提一个 issue 并附上 leak trace, reference key, 机器型号和 SDK 版本。如果可以附带上 dump 文件的 链接那就再好不过了。
对于最新发布的 Android,这点尤其重要。你有机会在帮助在早期发现新的内存泄露,这对整个 Android 社区都有极大的益处。
开发版本的 Snapshots 包在这里: Sonatype's snapshots repository。
leak trace 之外
有时,leak trace 不够,你需要通过 MAT 或者 YourKit 深挖 dump 文件。
通过以下方法,你能找到问题所在:
查找所有的 com.squareup.leakcanary.KeyedWeakReference 实例。
检查 key 字段
Find the KeyedWeakReference that has a key field equal to the reference key reported by LeakCanary.
找到 key 和 和 logcat 输出的 key 值一样的 KeyedWeakReference。
referent 字段对应的就是泄露的对象。
剩下的,就是动手修复了。最好是检查到 GC root 的最短强引用路径开始。
自定义
UI 样式
DisplayLeakActivity 有一个默认的图标和标签,你只要在你自己的 APP 资源中,替换以下资源就可。
res/
  drawable-hdpi/
    __leak_canary_icon.png
  drawable-mdpi/
    __leak_canary_icon.png
  drawable-xhdpi/
    __leak_canary_icon.png
  drawable-xxhdpi/
    __leak_canary_icon.png
  drawable-xxxhdpi/
    __leak_canary_icon.png
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="__leak_canary_display_activity_label">MyLeaks</string>
</resources>
保存 leak trace
DisplayLeakActivity saves up to 7 heap dumps & leak traces in the app directory. You can change that number by providing R.integer.__leak_canary_max_stored_leaks in your app:
在 APP 的目录中,DisplayLeakActivity 保存了 7 个 dump 文件和 leak trace。你可以在你的 APP 中,定义 R.integer.__leak_canary_max_stored_leaks 来覆盖类库的默认值。
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <integer name="__leak_canary_max_stored_leaks">20</integer>
</resources>
上传 leak trace 到服务器
你可以改变处理完成的默认行为,将 leak trace 和 heap dump 上传到你的服务器以便统计分析。
创建一个 LeakUploadService, 最简单的就是继承 DisplayLeakService :
public class LeakUploadService extends DisplayLeakService {
  @Override
  protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    if (!result.leakFound || result.excludedLeak) {
      return;
    }
    myServer.uploadLeakBlocking(heapDump.heapDumpFile, leakInfo);
  }
}
请确认 release 版本 使用 RefWatcher.DISABLED:
public class ExampleApplication extends Application {

  public static RefWatcher getRefWatcher(Context context) {
    ExampleApplication application = (ExampleApplication) context.getApplicationContext();
    return application.refWatcher;
  }

  private RefWatcher refWatcher;

  @Override public void onCreate() {
    super.onCreate();
    refWatcher = installLeakCanary();
  }

  protected RefWatcher installLeakCanary() {
    return RefWatcher.DISABLED;
  }
}
自定义 RefWatcher:
public class DebugExampleApplication extends ExampleApplication {
  protected RefWatcher installLeakCanary() {
    return LeakCanary.install(app, LeakUploadService.class);
  }
}
别忘了注册 service:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <application android:name="com.example.DebugExampleApplication">
    <service android:name="com.example.LeakUploadService" />
  </application>
</manifest>


demo
一个非常简单的 LeakCanary demo: https://github.com/liaohuqiu/leakcanary-demo























如何使用eclipse单独调试android系统的app
修改系统的app的时候,如果需要编译整个工程,那的确很慢。

虽然强大的make功能可以方便的使用mm只编译修改了的那个应用。

单独的一个应用跑起来才够快。

做法是这样:

一次编译过后(比如你改了framework,有新的修改在里面)
把out/target/common/obj/java_libraries/
里面的相关jar考出来作为工程的jar source library。
主要是core_intermediates,ext_intermediates,framework_intermediates,google-framework_intermediates
这4个。就够用了。
























http://www.2cto.com/kf/201207/140247.html
Android上oprofile使用说明
1.     目的
本文介绍了oprofile的功能并基于Android 2.1介绍了使用oprofile的基本方法和步骤。本文的读者为软件开发人员和白盒测试人员。
2.     oprofile简介
Oprofile是用于Linux的若干种评测和性能监控工具中的一种,它可以工作在不同的体系结构上,包括IA32、IA64、AMD Athlon系列及ARM等。Oprofile包含在Linux 2.5和更高版本的内核中,也包含在大多数较新的Linux发行版本中,在Android中已经集成了Oprofile。
oprofile以很低的开销对系统中所有运行的代码(包括kernel、kernel模块、库、应用程序)进行函数级别的性能分析(function-level profiling),跟踪占用CPU高的函数的调用信息,从而判断程序中哪些地方存在需要优化的性能瓶颈。
oprofile支持两种采样(sampling)方式:基于事件的采样(event based)和基于时间的采样(time based)。
基于事件的采样是oprofile只记录特定事件(比如L2 cache miss)的发生次数,当达到用户设定的定值时oprofile就记录一下(采一个样)。这种方式需要CPU内部有性能计数器(performance counter)。
基于时间的采样是oprofile借助OS时钟中断的机制,每个时钟中断oprofile都会记录一次(采一次样),又分为RTC模式(RTC mode,适用于2.2/2.4内核)和定时器中断模式(timer interrupt mode,适用于2.6以上内核)。引入定时器采样模式的目的在于,提供对没有性能计数器的CPU的支持,其精度相对于基于事件的采样要低,并且因为要借 助OS时钟中断的支持,对禁用中断的代码oprofile不能对其进行分析。
高通QSD8K处理器具备性能计数器,但当前发布的软件版本只支持定时器模式。
在Android上,oprofile分为target端和host端两部分。target端运行在设备上,主要包括一个内核模块 (oprofile.ko)和一个用户空间的守护进程(oprofiled),前者负责访问性能计数器或者注册基于时间采样的函数(使用 register_timer_hook注册之,使时钟中断处理程序最后执行profile_tick时可以访问之),并采样置于内核的缓冲区内;后者在 后台运行,负责从内核空间收集数据,写入文件。host端运行在PC上,包括一组后处理工具用于从原始采样数据生成可读的分析报告。
3.     oprofile使用方法
本节基于Android 2.1 (Eclair)介绍oprofile的使用方法,在Android 1.6 (Donut)和Android 2.2 (Froyo)上的使用方法与之相似。
在使用oprofile之前,首先必须确保烧到手机上的系统具有root权限,否则无法使用oprofile,并确保/data下有足够的可用空间用于保存采样数据(一般几十MB足够)。依次按照如下步骤操作。
3.1   步骤一:安装target端
Android源代码中已经包含了移植好的oprofile源代码,kernel缺省配置选项也已经enable了对oprofile的支持。在将 oprofile的target端安装到手机之前,请首先在eng模式下编译源代码,然后按照如下步骤将所需文件复制到手机上:
1. 内核模块oprofile.ko:
在PC上运行:
source build/envsetup.sh
choosecombo
adb push $ANDROID_PRODUCT_OUT/obj/KERNEL_OBJ/arch/arm/oprofile/oprofile.ko /data
2. 守护进程oprofiled和控制程序opcontrol:
如果手机上烧的system.img是eng模式编出来的,里面已经包含了oprofiled和opcontrol(在/system/xbin/),这一步可以跳过;如果是user模式编出来的system.img则不包含这两个文件,请运行:
adb push $ANDROID_PRODUCT_OUT/system/xbin/oprofiled /system/xbin
adb push $ANDROID_PRODUCT_OUT/system/xbin/opcontrol /system/xbin
3. elf内核映像文件:
如果要对内核进行profiling,需要将压缩的elf内核映像文件vmlinux复制到手机上,运行:
adb push $ANDROID_PRODUCT_OUT/obj/KERNEL_OBJ/arch/arm/boot/compressed/vmlinux /data
该vmlinux必须与手机上实际运行的内核一致。
3.2   步骤二:计算内核虚拟地址范围
要对内核进行profiling,需要计算内核开始和结束地址:
运行:
adb pull /proc/kallsyms .
在文件kallsyms中查找_text,其数值即为kernel start地址;查找_etext,其数值即为kernel end地址。
3.3   步骤三:将CPU设置在保持最高频率运行
为确保oprofile采样结果的准确性和一致性,在采样开始之前需将CPU设置在保持最高频率运行,为此在adb shell中运行:
mkdir /data/debug
mount -t debugfs debugfs /data/debug
echo 1 > /data/debug/nohlt
echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
3.4    步骤四:配置oprofile
在adb shell中运行:
insmod /data/oprofile.ko timer=1
opcontrol --setup
oprofiled --session-dir=/data/oprofile --vmlinux=/data/vmlinux --kernel-range=start,end --events=CPU_CYCLES:255:0:50000:0:1:1 --separate-lib=1 --separate-kernel=1
说明:
其中--kernel-range中的start和end即为上述步骤二中获得的kernel start地址和kernel end地址。
3.5   步骤五:开始采样
运行需要进行profiling的应用程序或场景,然后在adb shell中运行:
opcontrol --start
此时oprofile已开始采样,可在adb shell中运行:
opcontrol --status
随时查看oprofile运行状态和已采集的样本数。
3.6   步骤六:停止采样
当采集的样本数足够多的时候(根据经验一般数千即可),在adb shell中运行:
opcontrol --stop
3.7   步骤七:上传数据和生成分析结果
在PC上运行:
source build/envsetup.sh
choosecombo
cp $ANDROID_PRODUCT_OUT/obj/KERNEL_OBJ/vmlinux $ANDROID_PRODUCT_OUT/symbols
python $ANDROID_BUILD_TOP/external/oprofile/opimport_pull ~/oprofile-result
此时手机上的采样数据经转换后存在PC上~/oprofile-result中,运行以下命令显示分析结果报告:
cd ~/oprofile-result
$OPROFILE_EVENTS_DIR/bin/opreport --session-dir=. -p $ANDROID_PRODUCT_OUT/symbols -d -l
(注:在Android 2.1 (Eclair)中执行上面的命令出错,需将opreport换成自己编译的 0.9.4版本的opreport,编译步骤参见本文附录。)
注意:执行以上步骤时若遇到如下出错信息:
opreport: error while loading shared libraries: libbfd-2.18.0.20080103.so: cannot open shared object file: No such file or directory
可运行如下命令予以解决:
ln -s /usr/lib/libbfd.so /usr/lib/libbfd-2.18.0.20080103.so
3.8   重新运行oprofile
上述步骤一至七完成了一次完整的oprofile采样和分析过程,若要再次运行oprofile,需复位oprofile并清理上一次的采样数据,在adb shell中运行:
opcontrol --shutdown
rm /data/oprofile
umount /data/debug
rm -rf /data/debug
然后重新执行上述步骤三至七进行下一次的采集和分析。
4.     简化oprofile使用步骤的脚本
从上文的介绍可以看到oprofile的使用比较复杂,为简化使用步骤,特编写若干脚本,只需按照如下步骤运行这些脚本即完成oprofile的各项操作:
1.     工作环境准备:①在eng模式下编译源代码;②为使用oprofile创建一个工作目录(例如~/oprofile),将下面附的压缩包 oprofile_cmd.zip解开到该目录下,并进入到该目录下;③按照前文3.2节的方法获取kernel start地址和end地址填入op.sh;④确保烧到手机上的系统具有root权限,且所编译的内核源代码与手机上实际运行的内核一致;⑤确保运行过 choosecombo;
2.     安装target端:用数据线连接手机和PC,在PC上的oprofile工作目录下运行:
./prepare.sh
(注:若系统是Android 1.6 (Donut),则运行./prepare.sh donut)
3.     采样:运行需要进行profiling的应用程序或场景,然后在adb shell中运行:
cd /data
sh op.sh
此时可运行opcontrol --status查看oprofile运行状态和已采集的样本数,当采集的样本数足够多的时候(根据经验一般数千即可),在adb shell中运行:
opcontrol --stop
4.     上传数据:在PC上的oprofile工作目录下运行:
./import.sh
5.     显示分析结果:在PC上的oprofile工作目录下运行:
./report.sh
6.     重新运行oprofile:在adb shell中运行:
cd /data
sh opclean.sh
然后重新执行上述步骤3至5。
5.     oprofile分析结果实例
下面是在Android 2.1 (Eclair)上对一个运行alpha animation的应用程序进行oprofile分析的结果:
CPU: CPU with timer interrupt, speed 0 MHz (estimated)
Profiling through timer interrupt
          TIMER:0|
  samples|      %|
------------------
     1769 98.4418 app_process
              TIMER:0|
      samples|      %|
    ------------------
         1299 73.4313 libskia.so
          386 21.8202 vmlinux
           39  2.2046 libdvm.so
           17  0.9610 libc.so
            9  0.5088 libui.so
            4  0.2261 libbinder.so
            4  0.2261 libsurfaceflinger.so
            4  0.2261 libutils.so
            3  0.1696 libandroid_runtime.so
            1  0.0565 libGLES_android.so
            1  0.0565 gralloc.qsd8k.so
            1  0.0565 libm.so
            1  0.0565 libstdc++.so
       13  0.7234 rild
              TIMER:0|
      samples|      %|
    ------------------
            9 69.2308 vmlinux
            4 30.7692 linker
        6  0.3339 vmlinux
        4  0.2226 cnd
              TIMER:0|
      samples|      %|
    ------------------
            2 50.0000 linker
            1 25.0000 cnd
            1 25.0000 vmlinux
        2  0.1113 adbd
              TIMER:0|
      samples|      %|
    ------------------
            2 100.000 vmlinux
        1  0.0556 init
              TIMER:0|
      samples|      %|
    ------------------
            1 100.000 vmlinux
        1  0.0556 port-bridge
              TIMER:0|
      samples|      %|
    ------------------
            1 100.000 vmlinux
        1  0.0556 opcontrol
              TIMER:0|
      samples|      %|
    ------------------
            1 100.000 vmlinux
 
samples  %        image name               app name                 symbol name
554      30.8292  libskia.so               app_process              S32A_Opaque_BlitRow32_neon
456      25.3756  libskia.so               app_process              S32A_D565_Blend_neon(unsigned short*, unsigned int const*, int, unsigned int, int, int)
128       7.1230  vmlinux                  app_process              __memzero
113       6.2883  libskia.so               app_process              memset_128_loop
78        4.3406  libskia.so               app_process              memset_128_loop
65        3.6171  vmlinux                  app_process              _spin_unlock_irqrestore
20        1.1130  vmlinux                  app_process              get_page_from_freelist
18        1.0017  libskia.so               app_process              Sprite_D32_S32::blitRect(int, int, int, int)
17        0.9460  vmlinux                  app_process              v7wbi_flush_user_tlb_range
16        0.8904  vmlinux                  app_process              free_hot_cold_page
15        0.8347  libskia.so               app_process              Sprite_D16_S32_BlitRowProc::blitRect(int, int, int, int)
13        0.7234  vmlinux                  app_process              _spin_unlock_irq
12        0.6678  libdvm.so                app_process              dalvik_inst
12        0.6678  libskia.so               app_process              SkDraw::drawPaint(SkPaint const&) const
11        0.6121  vmlinux                  app_process              __dabt_usr
11        0.6121  vmlinux                  app_process              v7_dma_flush_range
10        0.5565  libskia.so               app_process              S32A_D565_Opaque_Dither(unsigned short*, unsigned int const*, int, unsigned int, int, int)
...
从以上结果可以看出,在运行alpha animation的过程中,在oprofile采样数据中2D图形库libskia.so占了最高的比例,达73.4313%,具体是 libskia.so中的函数S32A_Opaque_BlitRow32_neon和S32A_D565_Blend_neon分别占了最高的前二名, 说明在这一过程中最影响性能的瓶颈是在skia库中的这几个函数。
附录  关于在Android 1.6 (Donut)上使用oprofile的注意事项
Android 1.6 (Donut)中自带的opimport和opreport(位于$OPROFILE_EVENTS_DIR/bin/)是64位的,只能在安装了64位Linux系统的PC上运行,要在32位系统的PC上运行,解决方法是自己编译0.9.4版本的opimport和opreport(因Android 1.6 (Donut)中自带的oprofile是0.9.4版本的)。0.9.4版本oprofile源代码附在下面:(略)
 
编译步骤如下:
source build/envsetup.sh
choosecombo
tar -zxvf oprofile-0.9.4.tar.gz
cd oprofile-0.9.4
./configure --with-linux=$ANDROID_PRODUCT_OUT/obj/KERNEL_OBJ
make
make install
或者采用Android 2.1 (Eclair)以上版本中自带的opimport和opreport(版本为0.9.5)也可以。
此外Android 1.6 (Donut)中的脚本$ANDROID_BUILD_TOP/external/oprofile/opimport_pull有问题,可替换成Android 2.1 (Eclair)以上版本中的此文件。
作者:迷糊







































http://www.cnblogs.com/bangerlee/archive/2012/08/30/2659435.html
谁动了我的cpu——oprofile使用札记
引言
cpu无端占用高?应用程序响应慢?苦于没有分析的工具?
oprofile利用cpu硬件层面提供的性能计数器(performance counter),通过计数采样,帮助我们从进程、函数、代码层面找出占用cpu的"罪魁祸首"。下面我们通过实例,了解oprofile的具体使用方法。
 
常用命令
使用oprofile进行cpu使用情况检测,需要经过初始化、启动检测、导出检测数据、查看检测结果等步骤,以下为常用的oprofile命令。
初始化
opcontrol --no-vmlinux : 指示oprofile启动检测后,不记录内核模块、内核代码相关统计数据
opcontrol --init : 加载oprofile模块、oprofile驱动程序
检测控制
opcontrol --start : 指示oprofile启动检测
opcontrol --dump : 指示将oprofile检测到的数据写入文件
opcontrol --reset : 清空之前检测的数据记录
opcontrol -h : 关闭oprofile进程
查看检测结果
opreport : 以镜像(image)的角度显示检测结果,进程、动态库、内核模块属于镜像范畴
opreport -l : 以函数的角度显示检测结果
opreport -l test : 以函数的角度,针对test进程显示检测结果
opannotate -s test : 以代码的角度,针对test进程显示检测结果
opannotate -s /lib64/libc-2.4.so : 以代码的角度,针对libc-2.4.so库显示检测结果
 
opreport输出解析
正如以上命令解析所言,不加参数的opreport命令从镜像的角度显示cpu的使用情况:
 
linux # opreport
CPU: Core 2, speed 2128.07 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 100000
CPU_CLK_UNHALT.........|
  samples |           %|
------------------------
   31645719     87.6453      no-vmlinux  
    4361113     10.3592      libend.so
       7683      0.1367      libpython2.4.so.1.0
       7046      0.1253      op_test
        ⋯⋯
 
以上列表按以下形式输出:
              samples |                            %|
-----------------------------------------------------
     镜像内发生的采样次数     采样次数所占总采样次数的百分比      镜像名称
 
因我们在初始化时执行了"opcontrol --no-vmlinux"命令,指示oprofile不对模块和内核进行检测,因而在探测结果中,模块和内核一同显示成no-vmlinux镜像。输出 中,libend.so和libpython2.4.so.1.0均为动态库,op_test为进程。以上采样数据表明,检测时间内,cpu主要执行内核 和模块代码,用于执行libend.so库函数的比重亦较大,达到10%左右。
 
进一步地,我们可以查看到进程、动态库中的每个函数在检测时间内占用cpu的情况:
 
linux # opreport -l
 samples           %        image name        app name         symbol name
31645719     87.4472        no-vmlinux      no-vmlinux         /no-vmlinux
 4361113     10.3605         libend.so       libend.so             endless
    7046      0.1253           op_test         op_test                main
    ⋯⋯
 
以上输出显示消耗cpu的函数为libend.so库中的endless函数,以及op_test程序中的main函数。
 
进行oprofile初始化时,若我们执行opcontrol --vmlinux=vmlinux-`uname -r`,指定oprofile对内核和内核模块进行探测,在执行opreport查看检测结果时,内核和内核模块就不再显示为no-vmlinux,而是 内核和各个内核模块作为单独的镜像,显示相应cpu占用情况。
 
使用opannotate从代码层看cpu占用情况
以上介绍了使用oprofile的opreport命令,分别从进程和函数层面查看cpu使用情况的方法。看到这里,有的同学可能有这样的疑问:使 用opreport,我找到了消耗cpu的进程A,找到了进程A中最消耗cpu的函数B,进一步地,是否有办法找到函数B中最消耗cpu的那一行代码呢?
 
oprofile中的opannotate命令可以帮助我们完成这个任务,结合具备调试信息的程序、带有debuginfo的动态 库,opannotate命令可显示代码层面占用cpu的统计信息。下面我们通过几个简单的程序实例,说明opannotate命令的使用方法。
 
首先,我们需要一个消耗cpu的程序,该程序代码如下:
 
//op_test.c
extern void endless();
int main()
{
  int i = 0, j = 0;
  for (; i < 10000000; i++ )
     {
           j++;
     }
  endless();
  return 0;
}
 
该程序引用了外部函数endless,endless函数定义如下:
 
//end.c
void endless()
{
  int i = 0;
  while(1)
     {
         i++;
     }
}
 
endless函数同样很简单,下面我们将定义了endless函数的end.c进行带调试信息地编译,并生成libend.so动态库文件:
linux # gcc -c -g -fPIC end.c
linux # gcc -shared -fPIC -o libend.so end.o
linux # cp libend.so /usr/lib64/libend.so
接着,带调试信息地编译op_test.c,生成op_test执行文件:
linux # gcc -g -lend -o op_test op_test.c
 
之后,我们开启oprofile进行检测,并拉起op_test进程:
linux # opcontrol --reset
linux # opcontrol --start
linux # ./op_test &
在程序运行一段时间后,导出检测数据,使用opannotate进行结果查看:
 
linux # opcontrol --dump
linux # opannotate -s op_test
/*
 * Total samples for file : "/tmp/lx/op_test.c"
 *
 * 7046  100.00
 */
               : int main()
               :{ /*main total : 7046  100.000 */
              :    int i = 0, j = 0;
6447   91.4987 :    for (; i < 10000000; i++ )
               :    {
 599    8.5013 :          j++;
               :    }
             :    endless();
             :    return 0;
               :}
 
以上输出表明,在op_test程序的main函数中,主要消耗cpu的是for循环所在行代码,因该段代码不仅包含变量i的自增运算,还将i与10000000进行比较。
 
下面显示对自编动态库libend.so的检测结果:
 
linux # opannotate -s /usr/lib64/libend.so
/*
 * Total samples for file : "/tmp/lx/end.c"
 *
 * 4361113  100.00
 */
                 : void endless()
                 : {
                :     int i = 0;
                :     while(1)
                 :     {
  25661   0.6652 :          i++;
4335452  99.3348 :     }
                 : }
 
 
查看c库代码占用cpu情况
以上使用opannotate,分别查看了应用程序代码、自编动态库代码的cpu占用情况,对于c库中的代码,我们是否也能查看其消耗cpu的情况呢?
 
在使用oprofile查看c库代码信息前,需要安装glibc的debuginfo包,安装debuginfo包之后,我们即可以通过opannotate查看到c库代码,以下展示了malloc底层实现函数_int_malloc的部分代码:
 
linux # opannotate -s /lib64/libc-2.4.so
/*
 ----------------malloc---------------------
 */
                :Void_t *
                :_int_malloc( mstate av, size_t bytes )
                :{  /* _int_malloc total: 118396  94.9249 */
                     ⋯⋯
                :       assert((fwd->size & NON_MAIN_ARENA) == 0);
115460  92.5709 :       while((unsigned long)(size) < (unsigned long)(fwd->size)) {
  1161   0.9308 :            fwd = fwd->fd;
                :            assert((fwd->size & NON_MAIN_ARENA) == 0);
                :        }
                :}
 
 
在进行程序性能调优时,根据oprofile检测到的c库代码占用cpu的统计信息,可以判别程序性能瓶颈是否由c库代码引起。若oprofile 检测结果显示cpu被过多地用于执行c库中的代码,我们可进一步地采用修改c库代码、升级glibc版本等方法解决c库引发的应用程序性能问题。
 
小结
本文介绍了使用oprofile工具从进程、函数和代码层面检测cpu使用情况的方法,对于代码层面,分别介绍了查看程序代码、自编动态库代码以及 gblic代码cpu统计情况的方法,中间过程使用到opcontrol、opreport、opannotate三个常用的oprofile命令。
 
当系统出现cpu使用率异常偏高情况时,oprofile不但可以帮助我们分析出是哪一个进程异常使用cpu,还可以揪出进程中占用cpu的函数、 代码。在分析应用程序性能瓶颈、进行性能调优时,我们可以通过oprofile,得出程序代码的cpu使用情况,找到最消耗cpu的那部分代码进行分析与 调优,做到有的放矢。另外,进行程序性能调优时,我们不应仅仅关注自己编写的上层代码,也应考虑底层库函数,甚至内核对应用程序性能的影响。
 
关于oprofile工具可用于分析的场景,本文仅介绍了cpu使用情况一种,我们还可以通过oprofile查看高速缓存的利用率、错误的转移预 测等信息,"opcontrol --list-events"命令显示了oprofile可检测到的所有事件,更多的oprofile使用方法,请参看oprofile manual。
















http://blog.csdn.net/mr_raptor/article/details/44937619
Android性能优化案例研究(上)
2015-04-08 10:42 663人阅读 评论(0) 收藏 举报
这 是Google的Android开发工程师Romain Guy刊登在个人Blog上的一篇文章。Romain Guy 作为Android图形渲染和系统优化的专家,是Android 4.1中的“黄油项目”开发者之一。这篇译文将分为上下两个部分,上部分将通过一个实际的例子来展示如何利用现有的工具来定位Android应用程序的性 能瓶颈,下部分将提供一些有效的方法来解决性能问题。希望能给读者和开发者带来启发和借 鉴。
Falcon Pro
最近我在我的Nexus4上安装了Falcon Pro(下 图),一个新款的推特(Twitter)客户端。我觉得这款应用真的很赞,但我也注意到一些使用时的瑕疵:似乎在划屏滚动主界面的时间轴时,帧率并不能很 稳定。于是我利用我每天工作中所使用的工具和方法对此稍加研究,很快发现了Falcon Pro不能达到其应有性能的一些原因。
我这篇文章的主旨在于告诉你如何在一个应用中追踪和定位性能问题,甚至在没有它的源代码的情况下。你所要做的只是要获得最新的Android4.2SDK(最新的ADT工具可以帮你轻而易举的完成此事)。我强烈推荐你“能够”去下载这款有待研究的应用。不幸的是,Falcon Pro是一款付费应用,我因此只能提供一些文件的链接以便你能对照我的分析。
说说关于性能优化
Android4.1通过“黄油项目”将 焦点放在性能优化上,并且它也引入了一些性能分析的工具,比如systrace。Android4.2并没有提供像systrace那样显著的工具,但也 为你的工具集增加了一些很有用的功能。你将会在接下来的篇幅中发现到它们。(黄油计划 Project Butter,是Google在 Android 4.1 Jellybean版本开始启动的Android性能提升计划,其寓意为“像黄油一样顺滑”,该项目主要针对Android长期以来的饱受诟病的运行流畅 度问题,通过底层的优化,确保系统设备能达到60fps的帧刷新率,从而大大提高用户体验的流畅性——译者注)
性能分析通常是一项复杂的任务,它需要大量的经验,需要对工具,硬件,API等方面的深入理解。这些经验让我在这只要几分钟就可以做出分析(你可以在我12月1日的推特(Twitter)上看到它的实况转播。)而你可能得试上几次后才能对此得心应手。
证实我的疑问
记 忆中关于性能优化最重要的一件事就是通过量化来验证你的工作。即使对我而言,Falcon Pro在的Nexus4上有着很明显的丢帧现象,我仍然得用实际的数据来证明。因此我将这款应用安装到Nexus7上,因为Nexus7比Nexus4性 能更强大,同时Nexus7在性能分析上也有着比Neux4更有意思的优势,关于这一点,我将在稍后加以讨论。
这 款应用安装到Nexus7上也没有出现多大差别,我仍然能看到丢帧的现象甚至还略差。为了量化这个问题,我决定使用“Profile GPU rendering”(GPU渲染分析),一款Android4.1所引入的工具。你可以在“设置”应用的“开发者选项”中找到这个工具。
如果开发者选项在你的Android4.2设备上不可见,你可以在“关于手机”或者“关于桌面选择”的界面底部,点击“版本号”七次。
我这篇文章的主旨在于告诉你如何在一个应用中追踪和定位性能问题,甚至在没有它的源代码的情况下。你所要做的只是要获得最新的Android4.2SDK(最新的ADT工具可以帮你轻而易举的完成此事)。我强烈推荐你“能够”去下载这款有待研究的应用。不幸的是,Falcon Pro是一款付费应用,我因此只能提供一些文件的链接以便你能对照我的分析。
当这个选项打开,系统将会记录画每个窗口绘画最后128帧所需要的时间。在使用这个工具前,你得先杀掉这个应用(Android未来的版本将会去掉这个要求)。
方法:
除非特别需要,在为这个分析做每一次测量时,需缓慢的滚动主界面的时间轴,让其滚动一段像素,使其能展现额外的条目。
在重新启动这个应用并滚动时间轴主界面时,我在终端上运行了下面这个命令:
$ adb shell dumpsys gfxinfo com.jv.falcon.pro
在 产生的日志中,你会发现一段标记为“Profile”的毫秒量级的数据。这段数据包含了一个有三列数据的表,应用的每个window(窗口)都有一个这样 的表。为了使用这个数据,你可以简单的将这个表拷到你最喜欢的电子制表软件中,从而生成一个数据堆叠的列图。以下这个图就是我的测量结果。
查看大图
每一列给出了每一帧花在渲染上的时间估计:
“Draw”是指Java层用在创建“display lists”(显示列表)上的时间。它表明运行例如View.onDraw(Canvas)需要多少时间。
“Process”是指Android 2D渲染引擎用在执行“display lists”上的时间。你的UI层级(hierarchy)中的View数量越多,需要执行的绘画命令就越多。
“Execute”是指将一帧图像交给合成器(compositor)的时间。这部分占用的时间通常比较少
提醒:
要以60fps的帧率进行平滑的渲染,每一帧所占用的时间需要少于16ms。
关于“Execute”:
如 果Excute花费很多时间,这就意味着你跑在了系统绘图流水线的前面。Android在运行状态时最多可以用3块缓存,如果此时你的应用还需要一块缓 存,那应用就会被阻塞直到三块中的一块缓存被释放。这种情况的发生一般有两个原因。第一个原因是你的应用在Dalvik(java虚拟机)端画的太快,而 在它的Display list在GPU端执行太慢。第二个原因是你的应用花费太多时间在前几帧的渲染上,一旦流水线满了,它就跟不上,直到动画的完成。这些是我们想在下一个版 本的Android改进的地方。
以上这个图明显的证实了我的疑虑:这个应用在大部分时间运行良好,但某些时候会发生丢帧。
进一步研究
我们收集的数据显示这个应用有时绘图时间过长,但盖棺定论还为时过早。帧率也会被未调度的帧或者错过调度的帧的影响。例如,如果应用总是在16ms内完成一次绘图,但有时在帧与帧之间需要完成很长的任务,它就会因此错过一帧。
Systrace是一个很简单的工具去检查Falcon Pro是否存在这个问题。这个工具是系统级的,额外开销很低。它的时间统计是合理准确的,能给你一个整个系统运行的概况,包括你的应用。
开启Systrace,可以到开发者选项中选择“启动跟踪”,弹出一个对话框,会让你选择你想测量哪些方面的性能。我们只关注“Graphics”和“View”。
注意:
不要忘记关掉之前的GPU渲染分析选项。
使用systrace时,可以打开终端,在Android SDK的tools/systrace目录下,运行systrace.py:
$./systrace.py
这个工具默认会记录5秒钟内发生的事件。我简单的向上和向下滚动时间轴,得到了一个用HTML文档展现的结果图。
技巧:
浏览systrace的文档图,可以使用键盘上的WASD键去移动和缩放。W键是将鼠标所处位置进行放大。
systrace 的文档图显示了很多有意思的信息。例如,它可以显示一个进程是否被调度,是在哪个CPU上调度。如果你放大最后一行(叫做 10440:m.jv.falcon.pro),你可以看到这个应用正在做什么。如果你点击一个“performTraversals”块,你可以看到这 个应用花在输出一帧图像上面多长时间。
大多数的performTraversals显示在16ms临界值以下,但有一些需要更多的时间,因此也证实了之前的猜测。(在935毫秒处放大可以看到这个块。)
更 有意思的是,你可以看到这个应用有时错过一帧是因为它没有管理调度一个draw的操作。在270ms处放大,找到占用25ms的 “deliverInputEvent”块。这个块表明这个应用用了25ms来处理一个触摸事件。考虑到这个应用是使用ListView,很有可能是这个 适配器(adapter)出了问题,等会我们再来探讨这个。Systrace很有用的地方不仅在于证实这个应用花在绘图的时间上太长,也在于帮我们找到另 一个潜在的性能瓶颈。它很有用但也有局限。它只能提供高层级的数据,我们必须转向其他工具来理解此时究竟在运行什么。
可视化重绘
绘 图性能问题有很多根本的原因,但共同的一点是重绘(overdraw)。重绘发生在每次应用让系统在某个画好的地方上面再画别的。想一个最简单的应用:一 个白色背景的窗口(window),上面是一个按钮。当系统要画这个按钮时,它要画在已经画好的白色背景的上面。这就是重绘。重绘是必然的,但太多的重绘 就是个问题。设备的数据传输带宽是有限的,当重绘使得你的应用需要更多的带宽时,性能就会下降。不同的设备能够承担的重绘的代价是不同的。
最佳的准则是重绘的最大次数不能超过两次。这就意味着你可以在屏幕画第一次,然后在这个屏幕上再画第二次,最后在其中某些像素上再画第三次。
重绘的存在通常表明有这些问题:太多的View,复杂的层级,更长的inflation时间等等。
Android提供了三个工具来帮助辨别和解决重绘问题:Hierachy Viewer,Tracer for OpenGL和Show GPU overdraw。前两个可以在ADT工具或者独立的monitor工具中找到,最后一个是在开发者选项的一部分。
Show GPU Overdraw会在屏幕上画不同的颜色来辨别重绘发生在哪儿,重绘了几次。现在就开启它并且别忘了先杀掉你的应用(将来版本的Android会去掉这个要求)。
在我们查看Falcon Pro之前,让我们先看看当打开Show GPU overdraw,“设置”应用是什么样子。
如果你记得每种颜色所表示的含义,你就能很容易的知道结果是什么:
没有颜色就表示没有重绘。每个像素只画了一次。在这个例子里,你可以看到背景是完全无色的。
蓝色:表示重绘了一次。每个像素只画了两次。大块的蓝色是可以接受的。(如果整个window是蓝色的,你就可以使用一个图层(layer)。)
绿色:表示重绘了两次。每个像素画了三次。中等尺寸的绿色方块是可以接受的,但你最好尝试做出优化。
红色:表示重绘了三次。这个像素被画了四次。很小尺寸的红色方块是可以接受的。
黑色:表示重绘了四次及以上。这个像素被画了五次及以上。这个是错的,需要解决。
基于这些信息,你可以看到“设置”应用表现地很好,不需要额外的改进。只有在切换时有一点点红块,但不需要我们再做什么工作了。
透明像素:
再 仔细看看之前的截图。每一个图标都画成了蓝色。你可以看出位图(bitmap)中透明像素是解决了重绘的问题。透明像素必须由GPU处理,开销是昂贵的。 Android为了避免在图层(layer)和9-patches上绘画透明像素,做了优化,所以你只要考虑位图就行了。
重绘和GPU:
有 两种移动GPU架构。第一个使用延迟渲染,比如ImaginationTech的SGX系列。这种架构允许GPU在某些特定的场景下检查和处理重绘。(如 果你混合透明和不透明的像素,它有可能不起作用。) 第二钟架构使用及时渲染,它被NVIDIA的TegraGPU采用。这种架构不能为你优化重绘,这就是为什么我喜欢在Nexus7上测试(Nexus7使 用Tegra3)。这两种架构各有优劣。但这已经超出了本文的主题。仅仅只要知道两者都可以工作的很好就行了。现在就让我们看一下Falcon Pro…
截图上有大量的红色!最感兴趣的却是列表的背景是绿色的。这就显示在应用程序开始描绘它的内容前已经发生了两次重绘。我们这里所看到问题很有可能是和使用了许多全屏图片背景相关。但要解决这个问题通常是很繁琐的。
译者注:下篇作者将会带来如何解决性能问题的方法和思路,敬请期待!
英文原文:Android Performance Case Study  编译:ImportNew - 孙立
译文地址:  http://www.importnew.com/3784.html
【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】
Android性能优化案例研究(下)
2015-04-08 10:42 586人阅读 评论(0) 收藏 举报
译者前言:在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://blog.csdn.net/mr_raptor/article/details/44937779
Android性能优化
 随 着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要求远 远高于PC的桌面应用程序。以上理由,足以需要开发人员更加专心去实现和优化你的代码了。选择合适的算法和数据结构永远是开发人员最先应该考虑的事情。同 时,我们应该时刻牢记,写出高效代码的两条基本的原则:(1)不要做不必要的事;(2)不要分配不必要的内存。
我从去年开始接触Android开发,以下结合自己的一点项目经验,同时参考了Google的优化文档和网上的诸多技术大牛给出的意见,整理出这份文档。
 
1.      内存优化
Android系统对每个软件所能使用的RAM空间进行了限制(如:Nexus one 对每个软件的内存限制是24M),同时Java语言本身比较消耗内存,dalvik虚拟机也要占用一定的内存空间,所以合理使用内存,彰显出一个程序员的素质和技能。

1)       了解JIT
即 时编译(Just-in-time Compilation,JIT),又称动态转译(Dynamic Translation),是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。即时编译前期的两个运行时理论是字节码编译和动 态编译。Android原来Dalvik虚拟机是作为一种解释器实现,新版(Android2.2+)将换成JIT编译器实现。性能测试显示,在多项测试 中新版本比旧版本提升了大约6倍。
详细请参考http://hi.baidu.com/cool_parkour/blog/item/2802b01586e22cd8a6ef3f6b.html
 
2)       避免创建不必要的对象
就 像世界上没有免费的午餐,世界上也没有免费的对象。虽然gc为每个线程都建立了临时对象池,可以使创建对象的代价变得小一些,但是分配内存永远都比不分配 内存的代价大。如果你在用户界面循环中分配对象内存,就会引发周期性的垃圾回收,用户就会觉得界面像打嗝一样一顿一顿的。所以,除非必要,应尽量避免尽力 对象的实例。下面的例子将帮助你理解这条原则:
当 你从用户输入的数据中截取一段字符串时,尽量使用substring函数取得原始数据的一个子串,而不是为子串另外建立一份拷贝。这样你就有一个新的 String对象,它与原始数据共享一个char数组。 如果你有一个函数返回一个String对象,而你确切的知道这个字符串会被附加到一个StringBuffer,那么,请改变这个函数的参数和实现方式, 直接把结果附加到StringBuffer中,而不要再建立一个短命的临时对象。
一个更极端的例子是,把多维数组分成多个一维数组:
int 数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比 (int,int)对象数组性能要好很多。同理,这试用于所有基本类型的组合。如果你想用一种容器存储(Foo,Bar)元组,尝试使用两个单独的 Foo[]数组和Bar[]数组,一定比(Foo,Bar)数组效率更高。(也有例外的情况,就是当你建立一个API,让别人调用它的时候。这时候你要注 重对API接口的设计而牺牲一点儿速度。当然在API的内部,你仍要尽可能的提高代码的效率)
总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。
 
3)       静态方法代替虚拟方法
如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。
 
4)       避免内部Getters/Setters
在源生语言像C++中,通常做法是用Getters(i=getCount())代替直接字段访问(i=mCount)。这是C++中一个好的习惯,因为编译器会内联这些访问,并且如果需要约束或者调试这些域的访问,你可以在任何时间添加代码。
而在Android中,这不是一个好的做法。虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。
无JIT时,直接字段访问大约比调用getter访问快3倍。有JIT时(直接访问字段开销等同于局部变量访问),要快7倍。
 
5)       将成员缓存到本地
访问成员变量比访问本地变量慢得多,下面一段代码:
[java] view plaincopy
for(int i =0; i <this.mCount; i++)  { 
dumpItem(this.mItems); 


 
    
最好改成这样:
[java] view plaincopy
int count = this.mCount; 
Item[] items = this.mItems; 
for(int i =0; i < count; i++)  { 
       dumpItems(items); 


 
    
另一个相似的原则是:永远不要在for的第二个条件中调用任何方法。如下面方法所示,在每次循环的时候都会调用getCount()方法,这样做比你在一个int先把结果保存起来开销大很多。
[java] view plaincopy
for(int i =0; i < this.getCount(); i++) { 
dumpItems(this.getItem(i)); 

 
    
同样如果你要多次访问一个变量,也最好先为它建立一个本地变量,例如:
[java] view plaincopy
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) { 
if(isHorizontalScrollBarEnabled()) { 
intsize = mScrollBar.getSize(false); 
if(size <=0) { 
       size = mScrollBarSize; 

mScrollBar.setBounds(0, height - size, width, height); 
mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false); 
mScrollBar.draw(canvas); 



这里有4次访问成员变量mScrollBar,如果将它缓存到本地,4次成员变量访问就会变成4次效率更高的栈变量访问。
另外就是方法的参数与本地变量的效率相同。
 
1)       对常量使用static final修饰符
让我们来看看这两段在类前面的声明:
[java] view plaincopy
static int intVal = 42; 
static String strVal = "Hello, world!"; 

必 以其会生成一个叫做clinit的初始化类的方法,当类第一次被使用的时候这个方法会被执行。方法会将42赋给intVal,然后把一个指向类中常量表的 引用赋给strVal。当以后要用到这些值的时候,会在成员变量表中查找到他们。 下面我们做些改进,使用“final”关键字:
[java] view plaincopy
static final int intVal = 42; 
static final String strVal = "Hello, world!"; 

现在,类不再需要clinit方法,因为在成员变量初始化的时候,会将常量直接保存到类文件中。用到intVal的代码被直接替换成42,而使用strVal的会指向一个字符串常量,而不是使用成员变量。
将一个方法或类声明为final不会带来性能的提升,但是会帮助编译器优化代码。举例说,如果编译器知道一个getter方法不会被重载,那么编译器会对其采用内联调用。
你也可以将本地变量声明为final,同样,这也不会带来性能的提升。使用“final”只能使本地变量看起来更清晰些(但是也有些时候这是必须的,比如在使用匿名内部类的时候)。
 
2)       使用改进的For循环语法
改 进for循环(有时被称为for-each循环)能够用于实现了iterable接口的集合类及数组中。在集合类中,迭代器让接口调用hasNext() 和next()方法。在ArrayList中,手写的计数循环迭代要快3倍(无论有没有JIT),但其他集合类中,改进的for循环语法和迭代器具有相同 的效率。下面展示集中访问数组的方法:
[java] view plaincopy
static class Foo { 
        int mSplat; 
    } 
    Foo[] mArray = ... 
  
    public void zero() { 
        int sum = 0; 
        for (int i = 0; i < mArray.length; ++i) { 
            sum += mArray[i].mSplat; 
        } 
    } 
  
    public void one() { 
        int sum = 0; 
        Foo[] localArray = mArray; 
        int len = localArray.length; 
  
        for (int i = 0; i < len; ++i) { 
            sum += localArray[i].mSplat; 
        } 
    } 
  
    public void two() { 
        int sum = 0; 
        for (Foo a : mArray) { 
            sum += a.mSplat; 
        } 



在zero()中,每次循环都会访问两次静态成员变量,取得一次数组的长度。
在one()中,将所有成员变量存储到本地变量。
two() 使用了在java1.5中引入的foreach语法。编译器会将对数组的引用和数组的长度保存到本地变量中,这对访问数组元素非常好。但是编译器还会在每 次循环中产生一个额外的对本地变量的存储操作(对变量a的存取)这样会比one()多出4个字节,速度要稍微慢一些。
 
3)       避免使用浮点数
通常的经验是,在Android设备中,浮点数会比整型慢两倍,在缺少FPU和JIT的G1上对比有FPU和JIT的Nexus One中确实如此(两种设备间算术运算的绝对速度差大约是10倍)
从速度方面说,在现代硬件上,float和double之间没有任何不同。更广泛的讲,double大2倍。在台式机上,由于不存在空间问题,double的优先级高于float。
但即使是整型,有的芯片拥有硬件乘法,却缺少除法。这种情况下,整型除法和求模运算是通过软件实现的,就像当你设计Hash表,或是做大量的算术那样,例如a/2可以换成a*0.5。
 
4)       了解并使用类库
选择Library中的代码而非自己重写,除了通常的那些原因外,考虑到系统空闲时会用汇编代码调用来替代library方法,这可能比JIT中生成的等价的最好的Java代码还要好。
           i.    当你在处理字串的时候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊实现的方法。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。

           ii.    System.arraycopy方法在有JIT的Nexus One上,自行编码的循环快9倍。

           iii.    android.text.format包下的Formatter类,提供了IP地址转换、文件大小转换等方法;DateFormat类,提供了各种时间转换,都是非常高效的方法。
详细请参考http://developer.android.com/reference/android/text/format/package-summary.html

           iv.    TextUtils类
对于字符串处理Android为我们提供了一个简单实用的TextUtils类,如果处理比较简单的内容不用去思考正则表达式不妨试试这个在android.text.TextUtils的类,详细请参考http://developer.android.com/reference/android/text/TextUtils.html

            v.    高性能MemoryFile类。
很多人抱怨Android处理底层I/O性能不是很理想,如果不想使用NDK则可以通过MemoryFile类实现高性能的文件读写操作。
MemoryFile 适用于哪些地方呢?对于I/O需要频繁操作的,主要是和外部存储相关的I/O操作,MemoryFile通过将 NAND或SD卡上的文件,分段映射到内存中进行修改处理,这样就用高速的RAM代替了ROM或SD卡,性能自然提高不少,对于Android手机而言同 时还减少了电量消耗。该类实现的功能不是很多,直接从Object上继承,通过JNI的方式直接在C底层执行。
详细请参考http://developer.android.com/reference/android/os/MemoryFile.html
在此,只简单列举几个常用的类和方法,更多的是要靠平时的积累和发现。多阅读Google给的帮助文档时很有益的。
 
5)       合理利用本地方法
本 地方法并不是一定比Java高效。最起码,Java和native之间过渡的关联是有消耗的,而JIT并不能对此进行优化。当你分配本地资源时(本地堆上 的内存,文件说明符等),往往很难实时的回收这些资源。同时你也需要在各种结构中编译你的代码(而非依赖JIT)。甚至可能需要针对相同的架构来编译出不 同的版本:针对ARM处理器的GI编译的本地代码,并不能充分利用Nexus One上的ARM,而针对Nexus One上ARM编译的本地代码不能在G1的ARM上运行。
当你想部署程序到存在本地代码库的Android平台上时,本地代码才显得尤为有用,而并非为了Java应用程序的提速。
 
6)       复杂算法尽量用C完成
复杂算法尽量用C或者C++完成,然后用JNI调用。但是如果是算法比较单间,不必这么麻烦,毕竟JNI调用也会花一定的时间。请权衡。
 
7)       减少不必要的全局变量
尽量避免static成员变量引用资源耗费过多的实例,比如Context。Android提供了很健全的消息传递机制(Intent)和任务模型(Handler),可以通过传递或事件的方式,防止一些不必要的全局变量。
 
8)       不要过多指望gc
Java的gc使用的是一个有向图,判断一个对象是否有效看的是其他的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开销是可想而知。所以不要过多指望gc。将不用的对象可以把它指向NULL,并注意代码质量。同时,显示让系统gc回收,例如图片处理时,

[java] view plaincopy
if(bitmap.isRecycled()==false) { //如果没有回收 
     bitmap.recycle(); 


 
    
 
9)       了解Java四种引用方式
JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
                     i.    强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

                     ii.    软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

                   iii.    弱引用(WeakReference)
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

                    iv.    虚引用(PhantomReference)
顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
了解并熟练掌握这4中引用方式,选择合适的对象应用方式,对内存的回收是很有帮助的。
详细请参考http://blog.csdn.net/feng88724/article/details/6590064
            
10)     使用实体类比接口好
假设你有一个HashMap对象,你可以将它声明为HashMap或者Map:
[java] view plaincopy
Map map1 = new HashMap(); 
HashMap map2 = new HashMap(); 

哪个更好呢?
按 照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式 系统。调用一个接口的引用会比调用实体类的引用多花费一倍的时间。如果HashMap完全适合你的程序,那么使用Map就没有什么价值。如果有些地方你不 能确定,先避免使用 Map,剩下的交给IDE提供的重构功能好了。(当然公共API是一个例外:一个好的API常常会牺牲一些性能)
 
11)     避免使用枚举
枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。例如:
[java] view plaincopy
public class Foo { 
       public enum Shrubbery { GROUND, CRAWLING, HANGING } 


会 产生一个900字节的.class文件(Foo$Shubbery.class)。在它被首次调用时,这个类会调用初始化方法来准备每个枚举变量。每个枚 举项都会被声明成一个静态变量,并被赋值。然后将这些静态变量放在一个名为”$VALUES”的静态数组变量中。而这么一大堆代码,仅仅是为了使用三个整 数。
这样:Shrubbery shrub =Shrubbery.GROUND;会引起一个对静态变量的引用,如果这个静态变量是final int,那么编译器会直接内联这个常数。
一方面说,使用枚举变量可以让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。但是当性能方面有所限制的时候,你就应该避免这种做法了。
有些情况下,使用ordinal()方法获取枚举变量的整数值会更好一些,举例来说:
[java] view plaincopy
for(int n =0; n < list.size(); n++) { 
       if(list.items[n].e == MyEnum.VAL_X) { 
              // do something 
       } else if(list.items[n].e == MyEnum.VAL_Y) { 
              // do something 
       } 


替换为:
[java] view plaincopy
int valX = MyEnum.VAL_X.ordinal(); 
int valY = MyEnum.VAL_Y.ordinal(); 
int count = list.size(); 
MyItem items = list.items(); 
for(int n =0; n < count; n++) { 
       intvalItem = items[n].e.ordinal(); 
       if(valItem == valX) { 
              // do something 
       } else if(valItem == valY) { 
              // do something 
       } 


会使性能得到一些改善,但这并不是最终的解决之道。
 
12)     在私有内部内中,考虑用包访问权限替代私有访问权限
[java] view plaincopy
public class Foo { 
           public class Inner { 
                public void stuff() { 
                       Foo.this.doStuff(Foo.this.mValue); 
                } 
           } 
           private int mValue; 
           public void run() { 
                Inner in = new Inner(); 
                mValue = 27; 
                in.stuff(); 
           } 
           private void doStuff(int value) { 
                 System.out.println("value:"+value); 
           } 


需要注意的关键是:我们定义的一个私有内部类(Foo$Inner),直接访问外部类中的一个私有方法和私有变量。这是合法的,代码也会打印出预期的“Value is 27”。
但问题是,虚拟机认为从Foo$Inner中直接访问Foo的私有成员是非法的,因为他们是两个不同的类,尽管Java语言允许内部类访问外部类的私有成员,但是通过编译器生成几个综合方法来桥接这些间隙的。
[java] view plaincopy
/*package*/static int Foo.access$100(Foo foo) { 
return foo.mValue; 

/*package*/static void Foo.access%200(Foo foo,int value) { 
       foo.duStuff(value); 


内部类会在外部类中任何需要访问mValue字段或调用doStuff方法的地方调用这些静态方法。这意味着这些代码将直接存取成员变量表现为通过存取器方法访问。之前提到过存取器访问如何比直接访问慢,这例子说明,某些语言约会定导致不可见的性能问题。
如果你在高性能的Hotspot中使用这些代码,可以通过声明被内部类访问的字段和成员为包访问权限,而非私有。但这也意味着这些字段会被其他处于同一个包中的类访问,因此在公共API中不宜采用。
 
13)     将与内部类一同使用的变量声明在包范围内
请看下面的类定义:
[java] view plaincopy
public class Foo { 
       private class Inner { 
           void stuff() { 
               Foo.this.doStuff(Foo.this.mValue); 
           } 
       } 
  
       private int mValue; 
       public void run() { 
           Inner in = new Inner(); 
           mValue = 27; 
           in.stuff(); 
       } 
  
       private void doStuff(int value) { 
           System.out.println("Value is " + value); 
       } 


这其中的关键是,我们定义了一个内部类(Foo$Inner),它需要访问外部类的私有域变量和函数。这是合法的,并且会打印出我们希望的结果Value is 27。
问题是在技术上来讲(在幕后)Foo$Inner是一个完全独立的类,它要直接访问Foo的私有成员是非法的。要跨越这个鸿沟,编译器需要生成一组方法:
[java] view plaincopy
/*package*/ static int Foo.access$100(Foo foo) { 
    return foo.mValue; 

/*package*/ static void Foo.access$200(Foo foo, int value) { 
    foo.doStuff(value); 


 
    
内 部类在每次访问mValueg和gdoStuffg方法时,都会调用这些静态方法。就是说,上面的代码说明了一个问题,你是在通过接口方法访问这些成员变 量和函数而不是直接调用它们。在前面我们已经说过,使用接口方法(getter、setter)比直接访问速度要慢。所以这个例子就是在特定语法下面产生 的一个“隐性的”性能障碍。
通 过将内部类访问的变量和函数声明由私有范围改为包范围,我们可以避免这个问题。这样做可以让代码运行更快,并且避免产生额外的静态方法。(遗憾的是,这些 域和方法可以被同一个包内的其他类直接访问,这与经典的OO原则相违背。因此当你设计公共API的时候应该谨慎使用这条优化原则)。
 
14)     缓存
适量使用缓存,不要过量使用,因为内存有限,能保存路径地址的就不要存放图片数据,不经常使用的尽量不要缓存,不用时就清空。
 
15)     关闭资源对象
对SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O操作等都应该记得显示关闭。
            
2.      视图优化
1)       View优化
                       i.    减少不必要的View以及View的嵌套层次。
比如实现一个listview中常用的layout,可以使用RelativeLayout减少嵌套,要知道每个View的对象会耗费1~2k内存,嵌套层次过多会引起频繁的gc,造成ANR。
                     ii.    通过HierarchyViewer查看布局结构
利用HierarchyViewer来查看View的结构:~/tools/hierarchyviewer,能很清楚地看到RelativeLayout下面的扁平结构,这样能加快dom的渲染速度。
详细请参考http://developer.android.com/guide/developing/tools/hierarchy-viewer.html
                   iii.    通过Layoutopt优化布局
通过Android sdk中tools目录下的layoutopt 命令查看你的布局是否需要优化。详细请参考http://apps.hi.baidu.com/share/detail/34247942
            
2)       多线程解决复杂计算
占用CPU较多的数据操作尽可能放在一个单独的线程中进行,通过handler等方式把执行的结果交于UI线程显示。特别是针对的网络访问,数据库查询,和复杂的算法。目前Android提供了AsyncTask,Hanlder、Message和Thread的组合。
对于多线程的处理,如果并发的线程很多,同时有频繁的创建和释放,可以通过concurrent类的线程池解决线程创建的效率瓶颈。
另外值得注意的是,应用开发中自定义View的时候,交互部分,千万不要写成线程不断刷新界面显示,而是根据TouchListener事件主动触发界面的更新。
 
3)       布局用Java完成比XML快
一 般情况下对于Android程序布局往往使用XML文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以及执行效率,可以通过Java代码执行创 建,虽然Android编译过的XML是二进制的,但是加载XML解析器的效率对于资源占用还是比较大的,Java处理效率比XML快得多,但是对于一个 复杂界面的编写,可能需要一些套嵌考虑,如果你思维灵活的话,使用Java代码来布局你的Android应用程序是一个更好的方法。
 
4)       对大型图片进行缩放
图 片读取是OOM(Out of Memory)的常客,当在Android手机上直接读取4M的图片时,死神一般都会降临,所以导致往往自己手机拍摄的照片都不能直接读取。对大型图片进 行缩放有,处理图片时我们经常会用到BitmapFactory类,android系统中读取位图Bitmap时分给虚拟机中图片的堆栈大小只有8M。用 BitmapFactory解码一张图片时,有时也会遇到该错误。这往往是由于图片过大造成的。这时我们需要分配更少的内存空间来存储。 BitmapFactory.Options.inSampleSize设置恰当的inSampleSize可以使BitmapFactory分配更少的 空间以消除该错误。Android提供了一种动态计算的,如下:
 
读取图片之前先查看其大小:
[java] view plaincopy
BitmapFactory.Options opts = new BitmapFactory.Options(); 
opts.inJustDecodeBounds = true; 
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts); 

使用得到的图片原始宽高计算适合自己的smaplesize

[java] view plaincopy
BitmapFactory.Options opts = new BitmapFactory.Options(); 
 opts.inSampleSize = 4 ;// 4就代表容量变为以前容量的1/4 
 Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts); 

 
    
                   
对于过时的Bitmap对象一定要及时recycle,并且把此对象赋值为null。

[java] view plaincopy
bitmap.recycle(); 
bitmap = null; 

 
    
            
5)       合理使用ViewStub
ViewStub 是一个隐藏的,不占用内存空间的视图对象,它可以在运行时延迟加载布局资源文件。当ViewStub可见,或者调用 inflate()函数时,才会加载这个布局资源文件。 该ViewStub在加载视图时在父容器中替换它本身。因此,ViewStub会一直存在于视图中,直到调用setVisibility(int) 或者inflate()为止。ViewStub的布局参数会随着加载的视图数一同被添加到ViewStub父容器。同样,你也可以通过使用 inflatedId属性来定义或重命名要加载的视图对象的Id值。所以我们可以使用ViewStub延迟加载某些比较复杂的layout,动态加载 View,采用ViewStub 避免一些不经常的视图长期握住引用。
详细请参考http://developer.android.com/reference/android/view/ViewStub.html
 
6)       针对ListView的性能优化
                    i.    复用convertView。

                    ii.    在getItemView中,判断convertView是否为空,如果不为空,可复用。如果couvertview中的view需要添加 listerner,代码一定要在if(convertView==null){}之外。

                   iii.    异步加载图片,item中如果包含有web image,那么最好异步加载。

                    iv.    快速滑动时不显示图片
当 快速滑动列表时(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状态 (SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来。

                      v.    item尽可能的减少使用的控件和布局的层次;背景色与cacheColorHint设置相同颜色;ListView中item的布局至关重要,必须尽可 能的减少使用的控件,布局。RelativeLayout是绝对的利器,通过它可以减少布局的层次。同时要尽可能的复用控件,这样可以减少 ListView的内存使用,减少滑动时gc次数。ListView的背景色与cacheColorHint设置相同颜色,可以提高滑动时的渲染性能。

                      vi.    getView优化
ListView中getView是性能是关键,这里要尽可能的优化。getView方法中要重用view;getView方法中不能做复杂的逻辑计算,特别是数据库和网络访问操作,否则会严重影响滑动时的性能。优化如下所示:
[java] view plaincopy
@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
       Log.d("MyAdapter", "Position:" + position + "---" + String.valueOf(System.currentTimeMillis())); 
       final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
       View v = inflater.inflate(R.layout.list_item_icon_text, null); 
       ((ImageView) v.findViewById(R.id.icon)).setImageResource(R.drawable.icon); 
       ((TextView) v.findViewById(R.id.text)).setText(mData[position]); 
       return v; 


 
    
建议改为:
[java] view plaincopy
@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
       Log.d("Adapter", "Position:" + position + " : " + String.valueOf(System.currentTimeMillis())); 
       ViewHolder holder; 
       if (convertView == null) { 
              final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
              convertView = inflater.inflate(R.layout.list_item_icon_text, null); 
              holder = new ViewHolder(); 
             holder.icon = (ImageView) convertView.findViewById(R.id.icon); 
             holder.text = (TextView) convertView.findViewById(R.id.text); 
             convertView.setTag(holder); 
       } else { 
             holder = (ViewHolder) convertView.getTag(); 
       } 
              holder.icon.setImageResource(R.drawable.icon); 
              holder.text.setText(mData[position]); 
              return convertView; 
       } 
  
       static class ViewHolder { 
               ImageView icon; 
               TextView text; 
       } 


以上是Google IO大会上给出的优化建议,经过尝试ListView确实流畅了许多。使用1000条记录,经测试第一种方式耗时:25211ms,第二种方式耗时:16528ms。
 
7)       其他
                   i.    分辨率适配
-ldpi,-mdpi, -hdpi配置不同精度资源,系统会根据设备自适应,包括drawable, layout,style等不同资源。

                   ii.    尽量使用dp(density independent pixel)开发,不用px(pixel)。

                   iii.    多用wrap_content, fill_parent

                   iv.    抛弃AbsoluteLayout

                   v.    使用9patch(通过~/tools/draw9patch.bat启动应用程序),png格式

                   vi.    采用<merge> 优化布局层数;采用<include >来共享布局。

                   vii.    将Acitivity中的Window的背景图设置为空。getWindow().setBackgroundDrawable(null);android的默认背景是不是为空。

                   viii.    View中设置缓存属性.setDrawingCache为true。
 
3.      网络优化
1)       避免频繁网络请求
访 问server端时,建立连接本身比传输需要跟多的时间,如非必要,不要将一交互可以做的事情分成多次交互(这需要与Server端协调好)。有效管理 Service 后台服务就相当于一个持续运行的Acitivity,如果开发的程序后台都会一个service不停的去服务器上更新数据,在不更新数据的时候就让它 sleep,这种方式是非常耗电的,通常情况下,可以使用AlarmManager来定时启动服务。如下所示,第30分钟执行一次。
[java] view plaincopy
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 
Intent intent = new Intent(context, MyService.class); 
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); 
long interval = DateUtils.MINUTE_IN_MILLIS * 30; 
long firstWake = System.currentTimeMillis() + interval; 
am.setRepeating(AlarmManager.RTC,firstWake,  interval,  pendingIntent); 

2)       数据压缩
传输数据经过压缩 目前大部门网站都支持GZIP压缩,所以在进行大数据量下载时,尽量使用GZIP方式下载,可以减少网络流量,一般是压缩前数据大小的30%左右。

[java] view plaincopy
HttpGet request = new HttpGet("http://example.com/gzipcontent"); 
HttpResponse resp = new DefaultHttpClient().execute(request); 
HttpEntity entity = response.getEntity(); 
InputStream compressed = entity.getContent(); 
InputStream rawData = new GZIPInputStream(compressed); 

 
    
 
3)       使用线程池
线程池,分为核心线程池和普通线程池,下载图片等耗时任务放置在普通线程池,避免耗时任务阻塞线程池后,导致所有异步任务都必须等待。
 
4)       选择合适的数据格式传输形式

   

其中Tree Parse 是DOM解析 Event/Stream是SAX方式解析。
很 明显,使用流的方式解析效率要高一些,因为DOM解析是在对整个文档读取完后,再根据节点层次等再组织起来。而流的方式是边读取数据边解析,数据读取完 后,解析也就完毕了。在数据格式方面,JSON和Protobuf效率明显比XML好很多,XML和JSON大家都很熟悉。
从上面的图中可以得出结论就是尽量使用SAX等边读取边解析的方式来解析数据,针对移动设备,最好能使用JSON之类的轻量级数据格式为佳。
 
1)       其他
设置连接超时时间和响应超时时间。Http请求按照业务需求,分为是否可以缓存和不可缓存,那么在无网络的环境中,仍然通过缓存的HttpResponse浏览部分数据,实现离线阅读。
 
2.      数据库相关
1)       相对于封装过的ContentProvider而言,使用原始SQL语句执行效率高,比如使用方法rawQuery、execSQL的执行效率比较高。
2)       对于需要一次性修改多个数据时,可以考虑使用SQLite的事务方式批量处理。
3)       批量插入多行数据使用InsertHelper或者bulkInsert方法
4)       有些能用文件操作的,尽量采用文件操作,文件操作的速度比数据库的操作要快10倍左右。
 
3.      性能测试
对于Android平台上软件的性能测试可以通过以下几种方法来分析效率瓶颈,目前Google在Android软件开发过程中已经引入了多种测试工具包,比如Unit测试工程,调试类,还有模拟器的Dev Tools都可以直接反应执行性能。
1)       在模拟器上的Dev Tools可以激活屏幕显示当前的FPS,CPU使用率,可以帮助我们测试一些3D图形界面的性能。
2)       一般涉及到网络应用的程序,在效率上和网速有很多关系,这里需要多次的调试才能实际了解。
3)       对于逻辑算法的效率执行,我们使用Android上最普遍的,计算执行时间来查看:

[java] view plaincopy
long start = System.currentTimeMillis(); 
// do something 
long duration = System.currentTimeMillis() - start; 

最终duration保存着实际处理该方法需要的毫秒数。
4)       gc效率跟踪,如果你执行的应用比较简单,可以在DDMS中查看下Logcat的VM释放内存情况,大概模拟下那些地方可以缓存数据或改进算法的。
5)       线 程的使用和同步,Android平台上给我们提供了丰富的多任务同步方法,但在深层上并没有过多的比如自旋锁等高级应用,不过对于Service和 appWidget而言,他们实际的产品中都应该以多线程的方式处理,以释放CPU时间,对于线程和堆内存的查看这些都可以在DDMS中看到。
6)       利用traceview和monkey等工具测试应用。
7)       利用layoutopt和ninepatch等工具优化视图。
 
4.      结束语
本 文给出了一些Android 移动开发中常见的优化方法,多数情况下利用这些优化方法优化后的代码,执行效率有明显的提高,内存溢出情况也有所改善。在实际应用中对程序的优化一定要权 衡是否是必须的,因为优化可能会带来诸如增加BUG,降低代码的可读性,降低代码的移植性等不良效果。
希望不要盲目优化,请先确定存在问题,再进行优化。并且你知道当前系统的性能,否则无法衡量你进行尝试所得到的性能提升。
希望本文能给大家切实带来帮助。欢迎就上述问题进一步交流。如有发现错误或不足,请斧正。



猜你喜欢

转载自blog.csdn.net/blogercn/article/details/77996423