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

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报告:
 
 如图一所示:最上边的是时间轴,接下来显示的是CPU0和CPU1的运行状态,有些操作只用到单核,则只会显示CPU0的状态。INPUT_DISPATCHER_X和INPUT_DISPATCHER_Y
 识着这段时间内手指坐标变化状态,可以看到在这段时间内Y轴的坐标几乎不变,而X轴则呈现出好几次坐标从低到高或者从高到低的变化趋势,这种变化可以看出使用
 者在横向滑动屏幕。图一的下面部分显示了系统正在运行的一些进程及其运行状态。我们可以用WSAD这几个快捷键对上图进行放大缩小或左右平移操作。按W键将上图放 
 大,用鼠标选取一块放大后的区域,得到如图二所示的结果:
 
 图二的箭头指出了在这段时间内com.bbk.Launcher2进程执行了哪些函数以及这些函数运行的时间、次数等。
           
 如果你想对trace视图进行更多的操作,可参考下表所列出的快捷键:
 键	作用
 w	放大
 s	缩小.
 a	左移
 d	右移
 e	鼠标位置为中心.
 g	当前选择的进程开始处显示网格
 Shift+g	当前选择的进程结尾处显示网格
 右方向键	选定当前选择的时间表下一个事件
 左方向键	选定当前选择的时间表前一个事件.
 双击	放大
 Shift+双击	缩小



用Systrace和Traceview进行性能分析

 下面以联系人列表滑动为例来说明如何用Systrace和Traceview进行性能分析,我们故意在联系人列表滑动的地方插入了一段不太好的代码,使得在滑动联系人列表时非
 常卡。于是有了下面这张不太好看的联系人列表滑动的Systrace图,下面我们将分析如何查处问题并提高列表滑动时的性能。
 
 从上图可以看到com.android.contacts进程对应的图不是很均匀,有时中间空白区域很长,这个图也说明了滑动时有卡顿,因为这时contacts进程没有工作,屏幕上是 
 没有刷新的。将上图放大一点,我们可以看到下面这张图:
 
 点击contacts进程上方的灰色长条,可以看到此时该进程处于Sleeping状态,并且sleep了83.6ms! 将trace图拉到最上面,再看看这段时间CPU都在做什么,如下图所
 示: 
 
 点击棕色区域,从箭头所指的数据可以看到整段时间CPU0主要被一个叫Binder_3的线程在占用,其对应的线程号为10582,接下来我们要查出这个线程属于哪个进程,运
 行命令:adb shell ps -t | grep 10582,得到以下数据,如下图所示:
 
 可以看到Binder_3属于android.process.acore进程,这是系统的关键进程,存放着联系人的数据库。Binder_3在做什么呢,这已经超出了Systrace的能力,我们需要借
 助Traceview这个强大的进程分析工具。(可以在Eclipse的DDMS中使用Traceview工具,基本操作方法可以参考本文最后的链接)
 我们用Traceview工具抓取了一段android.process.acore进程在列表滑动时的状态,如下图所示:
 
 上图中有四个Binder线程,每个Binder线程运行在不同的时段。我们在图五Systrace图中看到的Binder_3线程就对应着上图中的一小块区域。
 选取Binder_3线程对应的一块区域进行放大,可以看到下图:
 
 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进程状态,  
 如下图所示:
 
 可以看到这个进程的状态不是很好,因为contacts的UI线程(main线程)在运行过程中有一些空白,而且空白区域很大,空白区域表示该线程没有获得CPU。对于UI线程而  
 言,如果在列表滑动等需要执行刷新操作的时候出现空白,就意味着有卡顿现象。
 选取一个空白区域进行放大,可以看到下图:
 
 图中的桃红色区域,也就是放大前的空白区域,将鼠标移到它上面可以看到它是一个BinderProxy的transact调用,而我们更关心的是它之前的调用流程。点击亮蓝色的
 矩形区域,我们可以看到它对应着ContentProviderProxy的query函数。看到query,我们大概可以猜出这里在执行数据库的查询操作,继续点击Parents中的函数调用, 
 直到找到源头,这里的源头指的是当前的跟踪进程,这里是Contacts进程。如下图所示:
 
 从圈出的部分我们看到了执行query操作的源头是在ContactListActivity这个类的getView里。原来在每次更新Contacts的ListView时,Contacts进程都要进行数据库查
 询的操作。
 接下来就是查看问题代码,我们发现在bindView里加入了一段查询数据库的代码,由于查询数据库的操作是阻塞的,而这个操作又不幸在UI线程中,也就是UI线程必须
 等数据库查询完了才能进行数据填充,UI刷新等操作,由此导致了列表滑动的卡顿。
 据此我们可以对这段代码进行优化,最常见的方式是新建一个线程用来查询数据库。当然也可以有其他方法,关键是避免在UI线程里执行耗时操作。优化后滑动列表的  
 Systrace截图如下:
 
 从这个图也能看出无论是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
 
 在左边的面板选择进程com.example.android.hcgallery,然后在 工具条上边点击Show heap updates按钮。这个时候切换到DDMS的VM Heap分页。它会显示每次gc后
 heap内存的一些基本数据。要看第一次gc后的数据内容,点击Cause GC按钮:
 
 我们可以看到现在的值(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(所有类实例被分配的内存总  
 和,里面也包括他们所有引用的对象)。
 
 如果我们按照shallow heap排序,我们可以看到byte[]实例在顶端。自从Android3.0(Honeycomb),Bitmap的像素数据被存储在byte数组里 (之前是被存储在Dalvik  
 的heap里),所以基于这个对象的大小来判断,不用说它一定是我们泄露掉的bitmap。
 右击byte[]类然后选择List Objects > with incoming references。它会生成一个heap上的所有byte数组的列表,在列表里,我们可以按照Shallow Heap的使用情况来  
 排序。
 选择并展开一个比较大的对象,它将展示从根到这个对象的路径--就是一条保证对象有效的链条。注意看,这个就是我们的bitmap缓存!
 
 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,对象未关闭
 ,线程泄露等。

猜你喜欢

转载自blog.csdn.net/zhiwenwei/article/details/68921788