Android性能分析流程表

来源:互联网 发布:手机淘宝2017正式版 编辑:程序博客网 时间:2024/04/30 00:41

Android性能分析很多人不知道究竟怎样来分析,网络上很多文章,但大多都是教怎么用工具,而没实际分析原因,本文总结网络各个大牛来进行一整套有效分析。
Android性能分析不可能一蹴而就,需要反反复复,不段进行,例如有事没事都弄MAT来看看。才能有效使项目性能达到最优。

话不多说开始:
1、可通过在手机上打开 设置->开发者选项->调试GPU过度绘制->显示过度绘制区域。
作用:查看过度绘制的布局进行优化,减少绘制时间。一般减少背景颜色绘制,因为ViewGroup默认情况下,会被设置成WILL_NOT_DRAW,这是从性能考虑,这样一来,onDraw就不会被调用了。

2、打开模拟器,装进app,之后在D:\android_studio\android_studio_sdk\tools打开hierarchyviewer.bat。

查看布局层级:
这里写图片描述
之后删除无用的嵌套,加快绘制。
布局优化:
1、尽量少的嵌套
2、linearLayout少用layout_weight,会使每一个子布局都要测量两次
3、用、、标签

怎么判断不必要的嵌套:
1、如果一个布局没有兄弟层级,父View又不是ScrollView,加上此布局又没设背景,那就可以去掉
2、一般一个布局没有兄弟层级,都可以考虑去掉
3、如果父View完全会被子View挡住的也可考虑去掉

3、对代码进行静态质量分析
1)通过Android Studio中的 Analyze->Inspect Code 对工程代码做静态扫描;找出潜在的问题代码并修改;
2) 0 error & 0 warning,如果确实不能解决,需给出原因。
这里写图片描述
在Inspection窗口的左侧,有提供了一系列快捷按钮用于快速分析、定位、修复代码中的问题:
这里写图片描述

4、Andorid Monitor剖析应用的性能
(1)Log日志,包括系统日志和自定义日志;
(2)实时监控内存、CPU、GPU的使用情况;
(3)实时监控网络流量的消耗(只适合于真机);
(4)采集运行时信息并保存为文件,供工具分析;

一直运行app,跳转不同的页面,看看内存和CPU的变化,如果越来余越大,很有可能发生内存泄漏了。

5、分许内存泄露
反复进入退出app,跳转app的不同界面查看activity和View的数量变化:
这里写图片描述
这里写图片描述

一直增加不减少就是有内存泄漏。

6、用Leakcanary
在build.gradle文件里加入依赖
这里写图片描述
在application文件:
这里写图片描述
一般来说就ok了。

如何使用 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);  }}

工作机制
1. RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2. 然后在后台线程检查引用是否被清除,如果没有,调用GC。
3. 如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4. 在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5. 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6. HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7. 引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

7、打开Android Devices Monitor来具体分析内存
这里写图片描述
或者
这里写图片描述

(1)TraceView文件:分析调用方法占用CPU的时间,调用和回调方法的次数(根据Parent和Children方法调用的时间是否很长)
注意这两种类型:

一类是调用次数不多,但每次调用却需要花费很长时间的函数。
一类是那些自身占用时间不长,但调用却非常频繁的函数。

1.最简单的方式就是直接打开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工具打开就会出现第一幅图了

(添加权限      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

横轴:方法执行的时间
这里写图片描述
纵轴:就是每个方法,包括了JDK的,Android SDK的,也有native方法的,当然最重要的就是app中你自己写的方法
这里写图片描述

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类似,只不过统计单位换成了真实时间

(2)Memory Analyze Tool(MAT分析工具)
1. 选中项目。
2. 点击
这里写图片描述
3. Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。
判断:
● 进入某应用,不断的操作该应用,同时注意观察data object的Total Size值,正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况。
所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;反之如果代码中存在没有释放对象引用的情况,则data object的
Total Size值在每次GC后不会有明显的回落。随着操作次数的增多Total Size的值会越来越大,直到到达一个上限后导致进程被杀掉。
● 利用monkey一直点击看Total Size值会不会增加
4. 点击
这里写图片描述
生成.hprof文件,保存之后要去终端(cmd)转换文件,在命令行下,切换到SDK目录的platform-tools目录下,使用hptof-conv工具帮助我们进行转换,
命令如下:
这里写图片描述
(因为我在环境变量已经配了platform-tools路径,所以这里直接写:hprof-conv D:\Android内存分析\ttt.hprof D:\Android内存分析\bbb.hprof)

  1. 之后用MAT工具打开文件bbb.hprof
    这里写图片描述

actions:
Histogram: 列出每个类型的实例数及大小 。
donimator tree :列出所有对象在整个内存对象中所占百分比。比较有用。
Top Consumers: 根据类名和包名列出开销最大的对象。
Duplicate Classes: 查找出在不同classloader中加载的相同类。

术语
shallow size:对象自身中有的内存大小
retained size:对象自身大小 + 该对象直接或是间接引用对象的shallow size
GC Roots:所有的对象引用refer chains的起点。

List object - With outgoing References 显示选中对象持有那些对象
List object - With incoming References 显示选中对象被那些外部对象所持有
Show object by class - With outgoing References 显示选中对象持有哪些对象, 这些对象按类合并在一起排序
Show object by class - With incoming References 显示选中对象被哪些外部对象持有, 这些对象按类合并在一起排序

总结:
1.首先看retained size最大的那些数据,一般看内存都是想解决内存泄漏问题,可以通过Top Consumers或者是donimator tree等actions。
2.找到最大的数据后,通过list objects – with outgoing references 查看具体持有了哪些对象,或者通过java basics – classloader 。查看这个是因为我们这次因为perm区满了,需要查看这个数据。到底还是哪些classloader加载了数据。

例子:
Histogram(直方图)
我们先来看Histogram, MAT最有用的工具之一,它可以列出任意一个类的实例数。它支持使用正则表达式来查找某个特定的类,还可以计算出该类所有对象的保留堆最小值或者精确值, 我们可以通过正则表达式输入LeakActivity, 看到Histogram列出了与LeakActivity相关的类
这里写图片描述

我们可以看到LeakActivity,和MyThread内部类都存在16个对象,虽然LeakActivity和MyThread存在那么多对象,但是到这里并不能让我们准确的判断这两个对象是否存在内存泄露问题, 选中com.example.memoryleak.LeakActivity,点击右键,如下图
这里写图片描述

Merge Shortest Paths to GC Roots 可以查看一个对象到RC Roots是否存在引用链相连接, 在JAVA中是通过可达性(Reachability Analysis)来判断对象是否存活,这个算法的基本思想是通过一系列的称谓”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走得路径称为引用链,当一个对象到GC Roots没有任何引用链相连则该对象被判定为可以被回收的对象,反之不能被回收,我们可以选择 exclude all phantom/weak/soft etc.references(排查虚引用/弱引用/软引用等)因为被虚引用/弱引用/软引用的对象可以直接被GC给回收.
这里写图片描述
可以看到LeakActivity存在GC Roots链,即存在内存泄露问题,可以看到LeakActivity被MyThread的this$0持有。

Histogram可以列出内存中每个对象的名字、数量以及大小。
Dominator Tree会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。
一般最常用的就是以上两个功能了,那么我们先从Dominator Tree开始学起。
现在点击Dominator Tree,结果如下图所示:
这里写图片描述

这张图包含的信息非常多,我来带着大家一起解析一下。首先Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,因此从上图中看,前两行的Retained Heap是最大的,我们分析内存泄漏时,内存最大的对象也是最应该去怀疑的。
另外大家应该可以注意到,在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,根据上面的讲解,可以被GC Root访问到的对象都是无法被回收的。那么这就说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。我们可以注意到,上图当中所有带红点的对象最右边都有写一个System Class,说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象。
那么上图中就无法看出内存泄漏的原因了吗?确实,内存泄漏本来就不是这么容易找出的,我们还需要进一步进行分析。上图当中,除了带有System Class的行之外,最大的就是第二行的Bitmap对象了,虽然Bitmap对象现在不能被GC Roots访问到,但不代表着Bitmap所持有的其它引用也不会被GC Roots访问到。现在我们可以对着第二行点击右键 -> Path to GC Roots -> exclude weak references,为什么选择exclude weak references呢?因为弱引用是不会阻止对象被垃圾回收器回收的,所以我们这里直接把它排除掉,结果如下图所示:
这里写图片描述

可以看到,Bitmap对象经过层层引用之后,到了MainActivityLeakClassGCRoots访ThreadSystemClassMainActivityLeakClass能被GC Roots访问到导致不能被回收,导致它所持有的其它引用也无法被回收了,包括MainActivity,也包括MainActivity中所包含的图片。
通过这种方式,我们就成功地将内存泄漏的原因找出来了。这是Dominator Tree中比较常用的一种分析方式,即搜索大内存对象通向GC Roots的路径,因为内存占用越高的对象越值得怀疑。
接下来我们再来学习一下Histogram的用法,回到Overview界面,点击Histogram,结果如下图所示:
这里写图片描述

这里是把当前应用程序中所有的对象的名字、数量和大小全部都列出来了,需要注意的是,这里的对象都是只有Shallow Heap而没有Retained Heap的,那么Shallow Heap又是什么意思呢?就是当前对象自己所占内存的大小,不包含引用关系的,比如说上图当中,byte[]对象的Shallow Heap最高,说明我们应用程序中用了很多byte[]类型的数据,比如说图片。可以通过右键 -> List objects -> with incoming references来查看具体是谁在使用这些byte[]。
那么通过Histogram又怎么去分析内存泄漏的原因呢?当然其实也可以用和Dominator Tree中比较相似的方式,即分析大内存的对象,比如上图中byte[]对象内存占用很高,我们通过分析byte[],最终也是能找到内存泄漏所在的,但是这里我准备使用另外一种更适合Histogram的方式。大家可以看到,Histogram中是可以显示对象的数量的,那么比如说我们现在怀疑MainActivity中有可能存在内存泄漏,就可以在第一行的正则表达式框中搜索“MainActivity”,如下所示:
这里写图片描述

可以看到,这里将包含“MainActivity”字样的所有对象全部列出了出来,其中第一行就是MainActivity的实例。但是大家有没有注意到,当前内存中是有11个MainActivity的实例的,这太不正常了,通过情况下一个Activity应该只有一个实例才对。其实这些对象就是由于我们刚才不断地横竖屏切换所产生的,因为横竖屏切换一次,Activity就会经历一个重新创建的过程,但是由于LeakClass的存在,之前的Activity又无法被系统回收,那么就出现这种一个Activity存在多个实例的情况了。
接下来对着MainActivity右键 -> List objects -> with incoming references查看具体MainActivity实例,如下图所示:
这里写图片描述

如果想要查看内存泄漏的具体原因,可以对着任意一个MainActivity的实例右键 -> Path to GC Roots -> exclude weak references,结果如下图所示:
这里写图片描述

可以看到,我们再次找到了内存泄漏的原因,是因为MainActivity$LeakClass对象所导致的。
好了,这大概就是MAT工具最常用的一些用法了,当然这里还要提醒大家一句,工具是死的,人是活的,MAT也没有办法保证一定可以将内存泄漏的原因找出来,还是需要我们对程序的代码有足够多的了解,知道有哪些对象是存活的,以及它们存活的原因,然后再结合MAT给出的数据来进行具体的分析,这样才有可能把一些隐藏得很深的问题原因给找出来。

8、连续48小时monkey不应出现闪退,anr问题。

本文Android性能分析部分用了网络上大牛的原文。如大牛看到有感觉不妥请告知,及时删除

1 0
原创粉丝点击