Android 内存泄漏检测之Profiler

说到Android的内存泄漏,很多人下意识想到:LeakCanary  ,导入依赖,运行后直接看通知栏结果。

但是,你们有没有想过,LeakCanary   检查内存泄漏的范围?其实,LeakCanary   这家伙能且只能检测Activity的内存泄漏

划重点:LeakCanary只能检测Activity的内存泄漏

为什么呢?

LeakCanary的原理

因为LeakCanary 通过 ActivityLifecycleCallbacks 是监听了整个应用的Activity的创建和销毁,当一个Activity销毁后,

就对这个Activity赋上弱引用,然后手动GC,去促使垃圾回收器回收垃圾。然后在垃圾队列里查看是否有刚才销毁的Activity,

如果没有,就说明这个Activity泄漏了。

这个设计到很多源码的跟踪,还有Java垃圾回收器的机制,在这就不详解了。我之前的博客说过垃圾回收机制的。可以看去。

但是问题来了,如果老子不是 Activity泄漏呢?是一个Fragment泄漏呢?是一个实体对象泄漏了呢?怎么检测呢?

这个LeakCanary  都是检测不出来的。。。。。。所以,这个时候需要另外的工具,

噔噔瞪!!!!!!Profiler 出场了。Android studio 自带的工具。

特别说明一下,Profiler 的样式每个版本都有差异的,这个帖子以Android studio3.4为基准

1:Profiler在哪里? 

运行了项目,就可以在底部栏看到 Profiler了

2:Profiler怎么用? 

好了,我们的应用运行起来了。我们点一下Profiler,看到的效果

可以看到 Profiler栏目有三个指数,CPU,MEMORY,NETWORK,分别指:CPU使用状况,内存使用状况和网络状况

现在我们是要分析内存。所以我们点一下 MEMORY,他会单独展示 内存使用,如下面:

这个界面,是应用运行时的内存状态。看截图即可。。

通过Profiler分析内存一般有两种方法,

分析内存方法一 ,进出页面,强制手动触发GC,Dump Heap分析

好了,我们先构造一个会内存泄漏的页面,比如:

class LeakActivity : AppCompatActivity() {

    companion object {
        fun launch(context: Context) {
            context.startActivity<LeakActivity>()
        }

        lateinit var leakActivity: LeakActivity
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_leak)
        leakActivity = this
    }
}

静态变量引用了当前的Activity。

然后我们的步骤是:

1>进入当前泄漏的页面,然后退出去

2>手动点击垃圾桶按钮,触发GC,触发回收垃圾对象。(这样保证需要回收的对象都不在内存里面)

3> 过3秒下载点击heap堆内存数据,(为什么过3秒呢?因为保证垃圾回收器有足够时间回收完所有需要被回收的对象)

4>等待几秒后,Android studio 会自动生成堆内存数据。比如下面截图:

会生成这么一个内存数据分析图。只要在这个分析目录里面的,就是说明在垃圾回收器回收后的内存数据。如果你的对象按理应该在内存中消失的,但却在这个目录里出现了,就说明你的对象无法被垃圾回收器回收,泄露了。

这个目录,选择按照我们包名目录去展示数据。

比如我现在应用的包名叫:com.leo.dicaprio.myutilapp 我们就会展开com目录去找。

其他目录可能是系统或者其他一些模块,我们基本上没必要去分析。我们只需要分析我们的应用有没有泄露即可。

然后,从上面代码看出,我们怀疑  LeakActivity 泄露,因为从代码上看他被静态变量引用。

所以我们一直依循这个 LeakActivity所在包路径找下去,看他是否还在内存里?看下截图

果不其然,他还在内存里面。没有被回收,泄露了。我们双击一个这个泄露的LeakActivity,

他会 弹出 instance View,然后再点击里面的 LeakActivity,会看到 Reference窗口,

Reference 列表就是展示所有对LeakActivity的引用列表,我们就要冲这个列表找出那个引用导致LeakActivity无法被回收。

来,上一波图:

看到了把。一个名字叫 leakActivity 变量(看清楚大小写),引用了LeakActivity(看清楚大小写),

然后再继续点击去,发现 leakActivity 变量 是在 LeakActivity里面的。这样就非常清晰了。。。。

在这里说一下,heap Dump右边有四个参数,具体是:

Alloc Count:Java堆中的实例个数
Native Size:native层分配的内存大小。
Shallow Size:Java堆中分配实际大小
Retained Size:这个类的所有实例保留的内存总大小(并非实际大小)

分析内存方法二,进出页面,手动触发GC分析,Record分析 

前面一种是手动触发gc,然后看内存。但是,你们有没有发现,引用队列有很多乱七八糟的系统引用。这个需要一定得功底才能查找出那些是系统里面的,那些是自己应用里面的引用。

这个时候,我们想要看下第二种办法。好,我们来第二种分析:就是内存录制。录制某一时间内存的状况。然后也是通过 

引用队列去定位分析,但是他有一个好处,什么好处呢?先看下面,先构造一个内存泄漏的例子:

public class Leak2Activity extends AppCompatActivity {

    public static void launch(Context context) {
        Intent intent = new Intent(context, Leak2Activity.class);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak2);
        postHandler();
    }

    private void postHandler() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        },999999999);
    }
}

然后操作步骤是:

1:点击record按钮,开始录制内存

2:进入Leak2Activity,然后退出

3:手动GC,3秒后点击按钮停止录制。

4:等待生成内存数据,分析。。。。

即:

【1:点击record按钮,开始录制内存】

【2:进入Leak2Activity,然后退出

此处省略截图

【3:手动GC,3秒后点击按钮停止录制

【4:等待生成内存数据,分析

看分析图,录制出来的内存数据分析图。

还是选择包名去分配目录

然后看到我们的 Leak2Activity 还在内存,说明泄漏。然后我们点击Leak2Activity&1,弹开InstanceView

然后再点击Leak2Activity&1,看Allocation Call Back,神奇的一幕出现了,

看到Leak2Activity 类里面的方法名。没错,就说明这两个类型里的这两个方法 对 Leak2Activity引用了。

然后刚才不说了这个方法有好处吗,说的好处就是它给你精确定位到那个方法进行对这个类引用了

然后我们双击这其中一个方法名,Android Studio 直接跳到这个方法的代码处。

然后看代码就知道这个方法是为什么会造成内存泄漏了。

总结

所以最后总结一下,这两种方法的却别:

方式1:强制GC,生成heap Dump,分析内存引用。

好处:不需要知道对象创建时间,任何时候可以查看堆内存数据

缺点:定位不到相关代码,太多系统的混淆引用。

方式2:录制内存数据,进出页面,分析内存数据

好处:可以定位到相关代码,不用看太多系统的混淆引用。

缺点:需要知道对象创建时间,只能在内存录制时间内可以查看

当然,现在展示的是Activity,其实同样可以检测任何类的实例是否泄漏。原理都是一样的。

以上代码亲测没问题,有问题请留言谢谢!

猜你喜欢

转载自blog.csdn.net/Leo_Liang_jie/article/details/93871361