应用性能分析的步骤及常用方法及Systrace使用

来源:互联网 发布:小黄鸭软件 编辑:程序博客网 时间:2024/05/17 08:59

Systrace的简单使用方法

 systrace使用方法很简单,设置好相应的环境后,进入android-sdk/tools/systrace目录,执行如下命令: python systrace.py --cpu-load --time=10 -o  mytrace.html,其中cpu-load是指观察CPU负载,观察时间为10s,输出到mytrace.html中.当然你也可以根据喜好设置 他参数,参数及意义可以通过输入python systrace.py –h得到。请用支持html5的浏览器,如最新的谷歌浏览器打开刚才生成的html文件。 下图显示了一份滑动桌面的Systrace报告: 图1.png 如图一所示:最上边的是时间轴,接下来显示的是CPU0和CPU1的运行状态,有些操作只用到单核,则只会显示CPU0的状态。INPUT_DISPATCHER_X和INPUT_DISPATCHER_Y 识着这段时间内手指坐标变化状态,可以看到在这段时间内Y轴的坐标几乎不变,而X轴则呈现出好几次坐标从低到高或者从高到低的变化趋势,这种变化可以看出使用 者在横向滑动屏幕。图一的下面部分显示了系统正在运行的一些进程及其运行状态。我们可以用WSAD这几个快捷键对上图进行放大缩小或左右平移操作。按W键将上图放  大,用鼠标选取一块放大后的区域,得到如图二所示的结果: 图2.png 图二的箭头指出了在这段时间内com.bbk.Launcher2进程执行了哪些函数以及这些函数运行的时间、次数等。            如果你想对trace视图进行更多的操作,可参考下表所列出的快捷键: 键作用 w放大 s缩小. a左移 d右移 e鼠标位置为中心. g当前选择的进程开始处显示网格 Shift+g当前选择的进程结尾处显示网格 右方向键选定当前选择的时间表下一个事件 左方向键选定当前选择的时间表前一个事件. 双击放大 Shift+双击缩小



用Systrace和Traceview进行性能分析

 下面以联系人列表滑动为例来说明如何用Systrace和Traceview进行性能分析,我们故意在联系人列表滑动的地方插入了一段不太好的代码,使得在滑动联系人列表时非 常卡。于是有了下面这张不太好看的联系人列表滑动的Systrace图,下面我们将分析如何查处问题并提高列表滑动时的性能。 图3.png 从上图可以看到com.android.contacts进程对应的图不是很均匀,有时中间空白区域很长,这个图也说明了滑动时有卡顿,因为这时contacts进程没有工作,屏幕上是  没有刷新的。将上图放大一点,我们可以看到下面这张图: 图4.png 点击contacts进程上方的灰色长条,可以看到此时该进程处于Sleeping状态,并且sleep了83.6ms! 将trace图拉到最上面,再看看这段时间CPU都在做什么,如下图所 示:  图5.png 点击棕色区域,从箭头所指的数据可以看到整段时间CPU0主要被一个叫Binder_3的线程在占用,其对应的线程号为10582,接下来我们要查出这个线程属于哪个进程,运 行命令:adb shell ps -t | grep 10582,得到以下数据,如下图所示: 图6.png 可以看到Binder_3属于android.process.acore进程,这是系统的关键进程,存放着联系人的数据库。Binder_3在做什么呢,这已经超出了Systrace的能力,我们需要借 助Traceview这个强大的进程分析工具。(可以在Eclipse的DDMS中使用Traceview工具,基本操作方法可以参考本文最后的链接) 我们用Traceview工具抓取了一段android.process.acore进程在列表滑动时的状态,如下图所示: 图7.png 上图中有四个Binder线程,每个Binder线程运行在不同的时段。我们在图五Systrace图中看到的Binder_3线程就对应着上图中的一小块区域。 选取Binder_3线程对应的一块区域进行放大,可以看到下图: 图8.png Profile Panel是Traceview的核心界面,其内涵非常丰富。它主要展示了某个线程(先在Timeline Panel中选择线程)中各个函数调用的情况,包括CPU使用时间、调用次数等信息。而这些信息正  是查找关键代码的关键依据。所以,对开发者而言,一定要了解Profile Panel中各列的含义。以下是具体含义: 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类似,只不过统计单位换成了真实时间
 点击图中绿色区域,我们可以在函数调用详情栏中看到这个颜色所对应的函数调用信息。从图中我们可以看到绿色部分对应的是SQLiteConnection执行CursorWindow操   作的函数,这个函数的调用花了差不多80ms! 这也恰好说明了为什么在上面的Systrace图中,联系人进程Sleep了83.6ms.  到这里我们已经基本找到了联系人列表界面滑动卡顿的原因:acore进程执行数据库操作导致了contacts进程的休眠。 那么是Contacts中的什么代码导致了这个问题呢?同样的方法,继续用TraceView对Contacts进程进行进一步分析,我们抓取了一段在列表滑动时的Contacts进程状态,   如下图所示: 图9.png 可以看到这个进程的状态不是很好,因为contacts的UI线程(main线程)在运行过程中有一些空白,而且空白区域很大,空白区域表示该线程没有获得CPU。对于UI线程而   言,如果在列表滑动等需要执行刷新操作的时候出现空白,就意味着有卡顿现象。 选取一个空白区域进行放大,可以看到下图: 图10.png 图中的桃红色区域,也就是放大前的空白区域,将鼠标移到它上面可以看到它是一个BinderProxy的transact调用,而我们更关心的是它之前的调用流程。点击亮蓝色的 矩形区域,我们可以看到它对应着ContentProviderProxy的query函数。看到query,我们大概可以猜出这里在执行数据库的查询操作,继续点击Parents中的函数调用,  直到找到源头,这里的源头指的是当前的跟踪进程,这里是Contacts进程。如下图所示: 图11.png 从圈出的部分我们看到了执行query操作的源头是在ContactListActivity这个类的getView里。原来在每次更新Contacts的ListView时,Contacts进程都要进行数据库查 询的操作。 接下来就是查看问题代码,我们发现在bindView里加入了一段查询数据库的代码,由于查询数据库的操作是阻塞的,而这个操作又不幸在UI线程中,也就是UI线程必须 等数据库查询完了才能进行数据填充,UI刷新等操作,由此导致了列表滑动的卡顿。 据此我们可以对这段代码进行优化,最常见的方式是新建一个线程用来查询数据库。当然也可以有其他方法,关键是避免在UI线程里执行耗时操作。优化后滑动列表的   Systrace截图如下: 图12.png 从这个图也能看出无论是Contacts的时间占用或者是SurfaceFlinger的绘图操作都很均匀,我们的优化工作到这里就完成了。



利用TAG对Systrace进行扩展

 Systrace的强大之处在于它可以针对性地对我们感兴趣的目标进行跟踪,比如绘图,输入事件,View,WindowManager,ActivityManager,Audio,Video,当然我们自  己也可以通过扩展TAG的方式对感兴趣的内容进行跟踪。 下面简单介绍一下Systrace的实现方式: 1.对应着JAVA层,在Trace.java里有如下定义:public final class Trace {// These tags must be kept in sync with frameworks/native/include/utils/Trace.h.public static final long TRACE_TAG_NEVER = 0;public static final long TRACE_TAG_ALWAYS = 1L << 0;public static final long TRACE_TAG_GRAPHICS = 1L << 1;public static final long TRACE_TAG_INPUT = 1L << 2;public static final long TRACE_TAG_VIEW = 1L << 3;public static final long TRACE_TAG_WEBVIEW = 1L << 4;public static final long TRACE_TAG_WINDOW_MANAGER = 1L << 5;public static final long TRACE_TAG_ACTIVITY_MANAGER = 1L << 6;public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7;public static final long TRACE_TAG_AUDIO = 1L << 8;public static final long TRACE_TAG_VIDEO = 1L << 9;
public static final int TRACE_FLAGS_START_BIT = 1;public static final String[] TRACE_TAGS = {"Graphics", "Input", "View","WebView", "Window Manager","Activity Manager", "Sync Manager","Audio", "Video",   }; 2.对应着C层,在Trace.h里的定义:  #define ATRACE_TAG_NEVER 0 // The "never" tag is neverenabled.  #define ATRACE_TAG_ALWAYS (1<<0) // The"always" tag is always enabled.  #define ATRACE_TAG_GRAPHICS (1<<1)  #define ATRACE_TAG_INPUT (1<<2)  #define ATRACE_TAG_VIEW (1<<3)  #define ATRACE_TAG_WEBVIEW (1<<4)  #define ATRACE_TAG_WINDOW_MANAGER (1<<5)  #define ATRACE_TAG_ACTIVITY_MANAGER (1<<6)  #define ATRACE_TAG_SYNC_MANAGER (1<<7)  #define ATRACE_TAG_AUDIO (1<<8)  #define ATRACE_TAG_VIDEO (1<<9)  #define ATRACE_TAG_LAST ATRACE_TAG_VIDEO
 在ViewRootImpl.java里实现对performTraversals的跟踪,只需要这么写: Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try {  performTraversals();  } finally {  Trace.traceEnd(Trace.TRACE_TAG_VIEW);  } 而在native层,比如Surfaceflinger.cpp里跟踪handleRepaint:  void SurfaceFlinger::handleRepaint()  {  ATRACE_CALL();  …} 如果要使用ATRACE_CALL,需要在代码的开始处定义TAG。例如在Surfaceflinger.cpp文件开头有如下定义:  #define ATRACE_TAG ATRACE_TAG_GRAPHICS 为了使跟踪函数的时间准确,最好在函数开头使用ATRACE_CALL()。  ATRACE_XXX这个类型的函数有很多用法,例如在InputDispatcher.cpp里跟踪触屏滑动x、y轴的值:  ATRACE_INT("INPUT_DISPATCHER_X",args->pointerCoords[i]. getAxisValue(AMOTION_EVENT_AXIS_X));  ATRACE_INT("INPUT_DISPATCHER_Y",args->pointerCoords[i]. getAxisValue(AMOTION_EVENT_AXIS_Y));



Android 内存监测工具 DDMS-->Heap

 用 Heap监测应用进程使用内存情况的步骤如下: 1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的;
 2. 将手机通过USB链接至电脑,链接时需要确认手机是处于“USB调试”模式,而不是作为“Mass Storage”;
 3. 链接成功后,在DDMS的Devices视图中将会显示手机设备的序列号,以及设备中正在运行的部分进程信息;
 4. 点击选中想要监测的进程,比如system_process进程;
 5. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标;
 6. 点击Heap视图中的“Cause GC”按钮;
 7. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。 说明: a) 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作; b) 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化; c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。 如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的  类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄 漏。可以这样判断: a) 不断的操作当前应用,同时注意观察data object的Total Size值; b) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断       的生成很多对 象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平; c) 反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大, 直到到达一个上限后导致进程被kill掉。 d) 此处已system_process进程为例,在我的测试环境中system_process进程所占用的内存的data object的Total Size正常情况下会稳定在2.2~2.8之间,而当其值超过   3.55后进程就会被kill。
 在DDMS里检查heap的使用情况 Dalvik Debug Monitor Server(DDMS)是主要的Android调试工具之一,也是ADT Eclipse plug-in 的一部分,独立的程序版本也可以在Android SDK的根目录下的 tools/下面找到。关于DDMS更多的信息,请参考使用DDMS 。
 我们来使用DDMS检查这个应用的heap使用情况。你可以使用下面的两种方法启动DDMS: from Eclipse: click Window > Open Perspective > Other... > DDMS or from the command line: run ddms (or ./ddms on Mac/Linux) in the tools/ directory Heap1.png 在左边的面板选择进程com.example.android.hcgallery,然后在 工具条上边点击Show heap updates按钮。这个时候切换到DDMS的VM Heap分页。它会显示每次gc后 heap内存的一些基本数据。要看第一次gc后的数据内容,点击Cause GC按钮: Heap2.png 我们可以看到现在的值(Allocated列)是有一些超过8MB。现在滑动相片,这时看到 数据在增大。因为只有仅仅13个相片在程序里边,所以泄露的内存只有这么大。在   某种程度上来说,这时最坏的一种内存泄露,因为我们没法得到 OutOfMemoryError来提醒我们说现在内存溢出了。
 生成heap dump 我们现在使用heap dump来追踪这个问题。点击DDMS工具条上面的Dump HPROF文件按钮,选择文件存储位置,然后在运行hprof-conv。在这个例子里我们使用独立的MAT     版本(版本1.0.1),可以从MAT站点下载 。 如果你使用ADT(它包含DDMS的插件)同时也在eclipse里面安装了MAT,点击“dump HPROF”按钮将会自动地做转换(用hprof-conv)同时会在eclipse里面打开转换后 的hprof文件(它其实用MAT打开)。
 用MAT分析heap dumps 启动MAT然后加载刚才我们生成的HPROF文件。MAT是一个强大的工具,讲述它所有的特性超出了本文的范围,所以我只想演示一种你可以用来检测 泄露的方法:直方图  (Histogram)视图。它显示了一个可以排序的类实例的列表,内容包括:shallow heap(所有实例的内存使用总和),或者retained heap(所有类实例被分配的内存总   和,里面也包括他们所有引用的对象)。 Heap3.png 如果我们按照shallow heap排序,我们可以看到byte[]实例在顶端。自从Android3.0(Honeycomb),Bitmap的像素数据被存储在byte数组里 (之前是被存储在Dalvik   的heap里),所以基于这个对象的大小来判断,不用说它一定是我们泄露掉的bitmap。
 右击byte[]类然后选择List Objects > with incoming references。它会生成一个heap上的所有byte数组的列表,在列表里,我们可以按照Shallow Heap的使用情况来   排序。 选择并展开一个比较大的对象,它将展示从根到这个对象的路径--就是一条保证对象有效的链条。注意看,这个就是我们的bitmap缓存! Heap4.png MAT不会明确告诉我们这就是泄露,因为它也不知道这个东西是不是程序还需要的,只有程序员知道。在这个案例里面,缓存使用的大量的内存会影响到后面的应用程 序,所以我们可以考虑限制缓存的大小。
 使用MAT比较heap dumps 调试内存泄露时,有时候适时比较2个地方的heap状态是很有用的。这时你就需要生成2个单独的HPROF文件(不要忘了转换格式)。下面是一些关于如何在MAT里比较2个   heap dumps的内容(有一点复杂):
 第一个HPROF 文件(using File > Open Heap Dump ). 打开 Histogram view. 在Navigation History view里 (如果看不到就从Window > Navigation History找 ), 右击histogram 然后选择Add to Compare Basket . 打开第二个HPROF 文件然后重做步骤2和3. 切换到Compare Basket view, 然后点击Compare the Results (视图右上角的红色"!"图标)。



android应用性能一些通用优化建议

 android应用优化的方法和注意点太多太多了,在此我们就举一些简单的注意点和大家分享下: 1.关注视图的layers,使用SDK工具sdk\tools\hierarchyviewer.bat里的Load View Hierarchy查看具体层次布局                                                      2.view获取:使用SDK工具sdk\tools\hierarchyviewer.bat里的Load View Hierarchy查看具体View个数 3.object获取:使用eclipseMemory Analyzer工具分析,通过MAT的Overview里面的Histogram的objects总数获取 4.bitmap内存:使用eclipseMemory Analyzer工具分析,通过MAT的Overview里面的Histogram的byte[]解析list objects->with incoming references按大小排序后获 取 5.HEAP: 使用eclipseMemory Analyzer工具分析,通过MAT的Overview的SIZE获取 6.Apk大小:使用adb shell连接电脑,执行命令:cd system->ls –al获取具体的apk大小数据 7.应用启动速度:通过执行命令adb shell am start -W -n yourpakagename/MainActivity可以查看应用具体启动时间,可以批量查看应用启动时间,精确到毫秒,这   个方法不包括点击屏幕的时间,如果要算入点击屏幕的时间,可以使用高速摄像机拍摄获取应用启动时间 8.应用耗电曲线,可以根据当前开启应用后电流-未开启应用待机电流来获取 9.FPS曲线判断帧的平滑性,是否有丢帧现象 10.如果是获取网络图片,尽量使用异步加载机制 11.bitmap对象的cycle的及时性,尽量用.9图片 12.尽量避免static成员变量引用资源耗费过多的实例,比如Context 13.使用WeakReference代替强引用,弱引用可以让您保持对对象的引用,同时允许GC在必要时释放对象,回收内存,比如横竖切换的线程泄露可用弱引用 14.循环体里面尽量不要用临时局部变量,会导致栈区的极大浪费(类似于递归的坏处) 15. Cursor?使用完一定要及时释放掉,不能等待GC处理 16.有些能用文件操作的,尽量采用文件操作,文件操作的速度比数据库的操作要快10倍左右。 17.懒加载和缓存机制。访问网络的耗时操作启动一个新线程来做,而不要再UI线程来做。 18.尽量使用局部变量,调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap) 中创建,速度较慢。 19.尽量减少一次性初始化N多大数据量,容易造成GC,并且卡顿 20.尽量少显示调用GC,容易占用CPU,引起卡顿 21.java里面的类似于字符串的相加,尽量不要使用A+B+C的方法,而要使用stringBuffer,因为会导致栈区的减少,垃圾变多,当然要注意StringBuilder和它的区别  22.View中设置缓存属性.setDrawingCache为true. 23.动态加载View. 采用ViewStub 避免一些不经常的视图长期握住引用. 24.采用SurfaceView在子线程刷新UI, 避免手势的处理和绘制在同一UI线程 25.有些能用文件操作的,尽量采用文件操作,文件操作的速度比数据库的操作要快10倍左右。 26.使用增强for循环   如:Set<Object> set = new HashSet<Object>();          // for循环遍历:         for (Object obj: set) {              if(obj instanceof Integer){                    int aa= (Integer)obj;              }else if(obj instanceof String){                      String aa = (String)obj                } ........          }   缺点:在遍历 集合过程中,不能对集合本身进行操作            for (String str : set) {                    set.remove(str);//错误!            } 27.合理利用浮点数,浮点数比整型慢两倍; 28.针对ListView的性能优化,item尽可能的减少使用的控件和布局的层次;背景色与cacheColorHint设置相同颜色;ListView中item的布局至关重要,必须尽可能的减 少使用的控件,布局。RelativeLayout是绝对的利器,通过它可以减少布局的层次。同时要尽可能的复用控件,这样可以减少ListView的内存使用,减少滑动时GC次   数。ListView的背景色与cacheColorHint设置相同颜色,可以提高滑动时的渲染性能。ListView中getView是性能是关键,这里要尽可能的优化。getView方法中要重用   view;getView方法中不能做复杂的逻辑计算,特别是数据库操作,否则会严重影响滑动时的性能。 29.乘法和除法,考虑下面的代码: for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; } 用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码: for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; } 30.使用非阻塞I/O,创建大量线程时可以采用缓冲池,例如线程池,如Web服务器,文件管理器 31.使用硬件加速方法

android性能的StrictMode检测

 StrictMode是Android为了提高性能,当在主线程进行了IO、网络等操作是,进行的严格模式检测。StrictMode有两种级别的policy检测,Thread和dalvikvm。可以检测activity,对象未关闭 ,线程泄露等。
1 0
原创粉丝点击