安卓项目实战之内存泄漏检测神器LeakCanary

为什么会产生内存泄漏?

Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但有另外一个正在使用的对象持有它的引用,从而导致它不能回收停留在堆内存中,这就产生了内存泄漏。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。

内存泄露对程序产生的影响?

内存泄漏是造成应用程序OOM的主要原因之一。Android系统为每个应用程序分配有限的内存,当应用中内存泄漏较多时,就难免会导致应用所需要的内存超出系统分配限额,从而导致OOM应用Crash;

Android常见的内存泄露

1,单例造成:由于单例静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象(如Context)已经不使用了,而单例对象还持有对象的引用造成这个对象不能正常被回收;
2,非静态内部类创建静态实例造成:在Acitivity内存创建一个非静态内部类单例,避免每次启动资源重新创建。但是因为非静态内部类默认持有外部类(Activity)的引用,并且使用该类创建静态实例。造成该实例和应用生命周期一样长,导致静态实例持有引用的Activity和资源不能正常回收;
3,Handler造成:子线程执行网络任务,使用Handler处理子线程发送消息。由于handler对象是非静态匿名内部类的对象,持有外部类(Activity)的引用。在Handler-Message中Looper线程不断轮询处理消息,当Activity退出还有未处理或者正在处理的消息时,消息队列中的消息持有handler对象引用,handler又持有Activity,导致Activity的内存和资源不能及时回收;
4,线程造成:匿名内部类Runnalbe和AsyncTask对象执行异步任务,对当前Activity隐式引用。当Activity销毁之前,任务还没有执行完,将导致Activity的内存和资源不能及时回收;
5,资源未关闭造成的内存泄露:对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄露;

内存泄漏与内存溢出的区别是什么?

内存泄漏: 垃圾对象依旧占据内存,如水龙头的泄漏,水本来是属于水源的, 但是水龙头没关紧,那么泄漏到了水池;再来看内存,内存本来应 该被回收,但是依旧在内存堆中;总结一下就是内存存在于不该存在的地方(没用的地方)

内存溢出: 内存占用达到最大值,当需要分配内存时,已经没有内存可以分配了,就是溢出;依旧以水池为例, 水池的水如果满了,那么如果继 续需要从水龙头流水的话,水就会溢出。总结一下就是,内存的分配超出最大阀值,导致了一种异常

明白了两者的概念,那么两者有什么关系呢?

内存的溢出是内存分配达到了最大值,而内存泄漏是无用内存充斥了内存堆;因此内存泄漏是导致内存溢出的元凶之一,而且是很大的元凶;因为内存分配完后,哪怕占用再大,也会回收,而泄漏的内存则不然;当清理掉无用内存后,内存溢出的阀值也会相应降低。

LeakCanary简介

LeakCanary是Square公司开源的一个检测内存的泄露的函数库,可以方便地和你的项目进行集成,在Debug版本中监控Activity、Fragment等的内存泄露;
LeakCanary集成到项目中之后,在检测到内存泄露时,会发送消息到系统通知栏。点击后打开名称DisplayLeakActivity的页面,并显示泄露的跟踪信息,Logcat上面也会有对应的日志输出。同时如果跟踪信息不足以定位时,DisplayLeakActivity还为开发者默认保存了最近7个dump文件到App的目录中,可以使用MAT等工具对dump文件进行进一步的分析;

如何使用leakcanary检测内存泄露

1,在android studio的build.gradle中引用leakcanary

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

2,自定义Application,并在onCreate方法中执行以下代码:

public class QAplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        ... ...
        //初始化LeakCanary
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
        }
        LeakCanary.install(this);
    }
}

3,AndroidManifest.xml中添加权限

<!--SDCard中创建与删除文件权限--> 
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 
<!--向SDCard写入数据权限--> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

已经结束了!通过以上配置,你就可以轻松使用LeakCanary检测内存泄漏了,当然这种简单的配置仅限于在Activity中进行检测,当然还存在其他类的内存泄漏,如Fragment等,这时我们就需要使用RefWatcher来进行监控了,具体见下面在非Activity的其它类中使用RefWatcher来进行内存泄漏的监控的讲解。

单例内存泄露模拟

1.单例如下:

public class TestManager {
    //单例静态特性使得单例的生命周期和应用的生命周期一样长
    private static TestManager instance;
    private Context context;
 
    /**
     * 传入的Context的生命周期很重要:
     *   如果传入的是Application的Context,则生命周期和单例生命周期一样长;
     *   如果传入的是Activity的Context,由于该Context和Activity的生命周期一样长,当Activity退出的时候它的内存不会被回收,因为单例对象持有它的引用;
     */
    private TestManager(Context context) {
        this.context = context;
    }
 
    public static TestManager getInstance(Context context) {
        if (instance == null) {
            instance = new TestManager(context);
        }
        return instance;
    }
}

2,在activity中创建该单例对象:

public class LeakCanaryActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leakcanary);
        //获取单例对象,退出Activity即可模拟出内存泄露
        TestManager testManager = TestManager.getInstance(this);
    }
}

运行App到LeakCanaryActivit页面并退出,在检测到内存泄露的时候,会发送消息到系统通知栏;
在这里插入图片描述 在这里插入图片描述
点击通知消息,打开名为DisplayLeakActivity的页面,并显示泄漏的跟踪信息;
在这里插入图片描述

在非Activity的其它类中使用RefWatcher来进行内存泄漏的监控

第二节的例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示。

public class LeakApplication extends Application { 

    private RefWatcher refWatcher; 
    
    @Override 
    public void onCreate() { 
        super.onCreate(); 
        refWatcher= setupLeakCanary(); 
    } 
    
    private RefWatcher setupLeakCanary() {
         if (LeakCanary.isInAnalyzerProcess(this)) {
         return RefWatcher.DISABLED; 
         } 
     return LeakCanary.install(this); 
    } 
    
     public static RefWatcher getRefWatcher(Context context) { 
         LeakApplication leakApplication = (LeakApplication) context.getApplicationContext(); 
         return leakApplication.refWatcher; 
     } 
     
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。
最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。

public class MainActivity extends AppCompatActivity { 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        LeakThread leakThread = new LeakThread(); 
        leakThread.start();
 } 
 
 class LeakThread extends Thread { 
     @Override 
     public void run() { 
         try { 
             Thread.sleep(6 * 60 * 1000); 
         } catch (InterruptedException e) {
             e.printStackTrace(); 
         } 
     } 
 } 
 
     @Override 
     protected void onDestroy() { 
         super.onDestroy(); 
         RefWatcher refWatcher = LeakApplication.getRefWatcher(this); //1  
         refWatcher.watch(this); 
     } 
 }

MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。
在注释1处得到RefWatcher,并调用它的watch方法,watch方法的参数就是要监控的对象。当然,在这个例子中onDestroy方法是多余的,因为LeakCanary在调用install方法时会启动一个ActivityRefWatcher类,它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露。这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。
运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来,比如三星S8的通知栏如下所示。
在这里插入图片描述
Notification中提示了MainActivity发生了内存泄漏, 泄漏的内存为787B。点击Notification就可以进入内存泄漏详细页,除此之外也可以通过Leaks应用的列表界面进入,列表界面如下图所示。
在这里插入图片描述
内存泄漏详细页如下图所示。
在这里插入图片描述
点击加号就可以查看具体类所在的包名称。整个详情就是一个引用链:MainActiviy的内部类LeakThread引用了LeakThread的this$0,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏。

猜你喜欢

转载自blog.csdn.net/gpf1320253667/article/details/83316969
今日推荐