美文网首页Android
Android/Java内存泄露检测框架 LeakCanary

Android/Java内存泄露检测框架 LeakCanary

作者: 瑟闻风倾 | 来源:发表于2019-11-20 09:28 被阅读0次

LeakCanary:LeakCanary是开源大户Square的一款开源产品,用于检测Android 和 Java 程序中的内存泄露。

1. 配置与使用

(1) 在 build.gradle 中加入引用

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}

(2) 自定义Application中并配置

package comi.example.liy.mytestdemo;

import android.app.Application;
import android.content.Context;

import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;

/**
 * Created by liy on 2019-11-15 14:33
 * 检测内存泄漏:如果检测到activity或Fragment有内存泄露,LeakCanary 就会自动地显示一个通知
 * 监控activity:LeakCanary在调用install方法时会自动启动一个ActivityRefWatcher类(用于自动监控Activity执行onDestroy方法之后是否发生内存泄露),所以在activity不需再手动添加RefWatcher类来监控是否发生内存泄露
 * 说明:除Activity类外的其他类的内存泄漏,需要使用RefWatcher来进行监控
 * 监控Fragment:需在Fragment中的onDestroy方法中手动添加RefWatcher类来监控是否发生内存泄露
 */
public class ExampleApplication extends Application {

    private RefWatcher refWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);//(1)LeakCanary做初始化:install方法会返回RefWatcher用来监控对象
    }

    //(2)提供getRefWatcher静态方法来返回全局RefWatcher:使用RefWatcher监控那些本该被回收的对象
    public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication) context.getApplicationContext();
        return application.refWatcher;
    }

}

注意:别忘了在manifest.xml中注册自定义的Application


注册自定义的Application.png

(3) 监控Activity

/**
 * Created by liy on 2019-11-15 9:01
 */
public class MemoryLeakTestActivity extends AppCompatActivity {

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

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //检测内存泄漏:在activity不需手动添加RefWatcher类来监控是否发生内存泄露(如果检测到activity有内存泄露,LeakCanary 就会自动地显示一个通知)
        /*RefWatcher refWatcher = ExampleApplication.getRefWatcher(this);
        refWatcher.watch(this);*/

    }


}

(4) 监控Fragment

package comi.example.liy.mytestdemo;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;

import com.squareup.leakcanary.RefWatcher;

/**
 * Created by liy on 2019-11-15 15:20
 */
public class MemoryLeakTestFragment extends Fragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        //检测内存泄漏:需在Fragment中的onDestroy方法中手动添加RefWatcher类来监控是否发生内存泄露
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);

    }
}

2. 完整示例

(1) 在Activity中检测内存泄漏

package comi.example.liy.mytestdemo;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;

import java.lang.ref.WeakReference;

/**
 * Created by liy on 2019-11-15 9:01
 */
public class MemoryLeakTestActivity extends AppCompatActivity {

    //private MemoryLeakTestActivity activity;

    private TextView textView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //activity = this;
        textView = findViewById(R.id.text);

        /**
         * 1.单例模式造成的内存泄漏
         */
        UserManager userManager = UserManager.getInstance(this);

        LoadData();

    }

    /**
     * 2.非静态匿名内部类的静态实例造成的内存泄漏
     * 内存泄漏原因分析:匿名内部类Runnable持有对当前Activity的隐式引用
     *                  当Activity销毁的时候(如返回键),若Runable内部的任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏。
     */
    private void LoadData(){
        /*new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);
                Log.v("LoadData","非静态匿名内部类的静态实例造成的内存泄漏");
            }
        }).start();*/

        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();

    }

    private static class MyRunnable implements Runnable{//自定义静态内部类MyRunnable并实现Runnable
        @Override
        public void run() {
            SystemClock.sleep(2000);
            Log.v("LoadData","成功解决非静态匿名内部类的静态实例造成的内存泄漏问题");
        }
    }

    /**
     * 3.非静态内部类的静态实例造成的内存泄漏
     * 内部类:当外部类activity启动时,都会使用内部类中的数据;虽然避免了资源的重复创建,但这种非静态内部类写法会造成内存泄漏
     * 内存泄漏原因分析:在java中非静态内部类默认持有外部类的引用,
     *                  若在非静态内部类中创建静态实例,则该实例的生命周期和应用一样长,
     *                  导致该静态实例一直持有acticity,使activity无法被释放
     * 解决内存泄漏:在java中静态内部类不会持有外部类的引用,故可将内部类设为静态内部类
     */
    /*class Resource{//非静态内部类默认持有外部类的引用,导致内存泄漏问题
        private static final String des1 = "";//在非静态内部类中创建静态实例,则该实例的生命周期和应用一样长,导致该静态实例一直持有acticity,使activity无法被释放
    }*/
    static class Resource{//静态内部类不会持有外部类的引用,避免了内存泄漏问题
        private static final String des2 = "";
    }

    @Override
    protected void onResume() {
        super.onResume();
        new MyThread().start();
    }

    private class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            try{
                sleep(5000);
            }catch (Exception e){
                e.printStackTrace();
            }
            Message message = new Message();
            message.what = 1;
            myHandler.sendMessage(message);
        }
    }

    /**
     * 3.1 Handler造成的内存泄漏:myHandler其实是非静态内部类Handler的实例,所以它持有外部类acticity的引用
     *  分析:myHandler和activity的生命周期不一样,当activity退出需释放activity时,如果消息队列中还有消息未处理,那么将导致myHandler所持有的activity无法被回收
     */
    /*private Handler myHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //Toast.makeText(MemoryLeakTestActivity.this,"更新UI",Toast.LENGTH_SHORT).show();
                    Toast.makeText(activity,"更新UI",Toast.LENGTH_SHORT).show();
            }
        }
    };*/
    private MyHandler myHandler = new MyHandler(this);
    private static class MyHandler extends Handler{//(1)静态内部类:自定义静态内部类MyHandler并继承Handler,然后在使用的时候实例化
        private WeakReference<Context> reference;//(2)弱引用:MyHandler内部持有activity的弱引用
        public MyHandler(Context context){
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MemoryLeakTestActivity activity = (MemoryLeakTestActivity) reference.get();
            if (activity != null){
                activity.textView.setText("成功解决Handler造成的内存泄漏问题");
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        
        //检测内存泄漏:在activity不需手动添加RefWatcher类来监控是否发生内存泄露(如果检测到activity有内存泄露,LeakCanary 就会自动地显示一个通知)
        /*RefWatcher refWatcher = ExampleApplication.getRefWatcher(this);
        refWatcher.watch(this);*/

        myHandler.removeCallbacksAndMessages(null);//(3)资源回收:在activity的onDestory()生命周期函数中调用handler.removeCallbacks()方法

    }


}

(2) 运行程序界面生成一个名为Leaks的应用图标


image.png

(3) 查看内存泄漏信息
若不断的切换横竖屏(会造成内存泄漏),这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”,再稍等片刻,内存泄漏信息就会通过Notification展示出来,点击Notification就可以进入内存泄漏详细页;除此之外也可以通过Leaks应用的列表界面进入,如下图所示:


内存泄漏列表界面.png
内存泄漏详情界面.png

相关文章

网友评论

    本文标题:Android/Java内存泄露检测框架 LeakCanary

    本文链接:https://www.haomeiwen.com/subject/atajictx.html