Android LeakCanary使用详细教程

导语

在Android的性能优化中,内存优化是必不可少的点,而内存优化最重要的一点就是解决内存泄漏的问题,在Android的内存泄漏分析工具也不少,比如PC端的有:AndroidStudio自带的Android Profiler、MAT等工具;手机端也有,就是我们今天要介绍的LeakCanary

LeakCanary简介

LeakCanary是Square公司为Android开发者提供的一个自动检测内存泄漏的工具,
LeakCanary本质上是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,我们可以通过集成LeakCanary提供的jar包到自己的工程中,一旦检测到内存泄漏,LeakCanary就好dump Memory信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,而不用每次在开发流程中都抽出专人来进行内存泄漏问题检测,极大地方便了Android应用程序的开发。

LeakCanary显示内存泄漏的页面:
在这里插入图片描述

LeakCanary优点:

1、针对Android Activity组件完全自动化的内存泄漏检查。
2、可定制一些行为(dump文件和leaktrace对象的数量、自定义例外、分析结果的自定义处理等)。
3、集成到自己工程并使用的成本很低。
4、友好的界面展示和通知。

LeakCanary工作机制:

引用LeakCanary中文使用说明,它的基本工作机制如下:

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, 并以通知的形式展示出来。

LeakCanary的Android Studio集成

一、 在build.gradle中添加LeakCanary的依赖包,截止目前leakcanary的最新版本是1.6.1:

dependencies {
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}

在开发中一般同时集成debug和release版本,其中:

  • com.squareup.leakcanary:leakcanary-android:1.6.1 是debug版本,在你的app编译的是debug版本,加载的是该jar包,一旦出现内存泄漏会在通知栏中通知开发者产生了内存泄漏;
  • com.squareup.leakcanary:leakcanary-android-no-op:1.6.1 是release版本,如果你的app编译的是release版本时,加载的是该jar包,no-op是指No Operation Performed,代表不会做任何操作,不会干扰正式用户的使用;

二、 在我们自定义Application的onCreate方法中注册LeakCanary

@Override
public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
        // This process is dedicated to LeakCanary for heap analysis.
        // You should not init your app in this process.
        return;
    }
    LeakCanary.install(this);
}
  • 解释一下为什么要先判断LeakCanary.isInAnalyzerProcess(this)
    在注册之前先判断LeakCanary是否已经运行在手机上,比如你同时有多个APP集成了LeakCanary,其他app已经运行了LeakCanary则不需要重新install
    我们看一下isInAnalyzerProcess方法的源码:
    /**
     * Whether the current process is the process running the {@link HeapAnalyzerService}, which is
     * a different process than the normal app process.
     */
    public static boolean isInAnalyzerProcess(Context context) {
        Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
        // This only needs to be computed once per process.
        if (isInAnalyzerProcess == null) {
            isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
            LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
        }
        return isInAnalyzerProcess;
    }
    
    从源码中可以看到真正调用的是isInServiceProcess方法,在来看下isInServiceProcess的源码:
    public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
        PackageManager packageManager = context.getPackageManager();
        PackageInfo packageInfo;
        try {
            packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
        } catch (Exception e) {
            CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
            return false;
        }
        String mainProcess = packageInfo.applicationInfo.processName;
    
        ComponentName component = new ComponentName(context, serviceClass);
        ServiceInfo serviceInfo;
        try {
            serviceInfo = packageManager.getServiceInfo(component, 0);
        } catch (PackageManager.NameNotFoundException ignored) {
            // Service is disabled.
            return false;
        }
    
        if (serviceInfo.processName.equals(mainProcess)) {
            CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
            // Technically we are in the service process, but we're not in the service dedicated process.
            return false;
        }
    
        int myPid = android.os.Process.myPid();
        ActivityManager activityManager =
            (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.RunningAppProcessInfo myProcess = null;
        List<ActivityManager.RunningAppProcessInfo> runningProcesses;
        try {
            runningProcesses = activityManager.getRunningAppProcesses();
        } catch (SecurityException exception) {
            // https://github.com/square/leakcanary/issues/948
            CanaryLog.d("Could not get running app processes %d", exception);
            return false;
        }
        if (runningProcesses != null) {
            for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
                if (process.pid == myPid) {
                    myProcess = process;
                    break;
                }
            }
        }
        if (myProcess == null) {
            CanaryLog.d("Could not find running process for %d", myPid);
            return false;
        }
    
        return myProcess.processName.equals(serviceInfo.processName);
    }
    
    我们可以看到ComponentName component = new ComponentName(context, serviceClass);
    而这个serviceClass参数是isInAnalyzerProcess方法中调用的HeapAnalyzerServiceHeapAnalyzerService正是用来分析内存泄漏的单独进程,说以说LeakCanary在同一个手机只需要执行一次install就可以了,当然执行多次也是可以的;
  • 回到正题,正常情况下Application调用LeakCanary.install(this)后就可以正常监听该app程序的内存泄漏;

LeakCanary监听指定对象的内存泄漏

如果想让LeakCanary监听指定对象的内存泄漏,我们就需要使用到RefWatcherwatch功能,使用方式如下:

  • ApplicationonCreate中调用install方法,并获取RefWatcher对象:
    private static RefWatcher sRefWatcher;
    
    @Override
    public void onCreate() {
        super.onCreate();
        sRefWatcher = LeakCanary.install(this);
    }
    
    public static RefWatcher getRefWatcher() {
        return sRefWatcher;
    }
    

    注意:因为这时候需要获取sRefWatcher对象,所以sRefWatcher = LeakCanary.install(this)一定需要执行,不需要判断LeakCanary.isInAnalyzerProcess(this)

  • 为了方便演示使用LeakCanary获取和解决内存泄漏的问题,我们先写一个内存泄漏的场景,我们知道最常见的内存泄漏是单列模式使用ActivityContext场景,所以我们也用单列模式来演示:
    public class Singleton {
        private static Singleton singleton;
        private Context context;
        private Singleton(Context context) {
            this.context = context;
        }
    
        public static Singleton newInstance(Context context) {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null){//双重检查锁定
                        singleton = new Singleton(context);
                    }
                }
            }
            return singleton;
        }
    }
    
  • 在需要监听的对象中调用RefWatcherwatch方法进行监听,比如我想监听一个Activity,我们可以在该AcitivityonCreate方法中添加DemoApp.getRefWatcher().watch(this);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DemoApp.getRefWatcher().watch(this);
        setContentView(R.layout.activity_second);
        Singleton singleton = Singleton.newInstance(this);
    }
    

LeakCanary内存泄漏展示页面

同时上面的步骤,当我们在运行app程序的时候,出现内存泄漏后,过一小段时间后就会在通知栏中通知出现内存泄漏的情况:

同时会在桌面上生成一个Leaks的图标,这个就是展示内存泄漏列表的,内存泄漏列表页面如下:
在这里插入图片描述

这是一个内存泄漏的列表,我们可以通过点击进入查看泄漏的内容
在这里插入图片描述
还可以通过点击右边的“+”号查看更详细的信息,内容太长就不截图了,内部有详细介绍调用的流程;

内存泄漏解决

上面的演示列子我们使用的是单列模式来产生内存泄漏,我们当然知道怎样正确的解决这个内存泄漏,
就是Singleton singleton = Singleton.newInstance(this)的调用传入Contxt时使用ApplicationContext

这里总结一下产生内存泄漏的常见场景和常用的解决方案

  • 常见的内存泄漏场景:

    1、单例设计模式造成的内存泄漏
    2、非静态内部类创建的静态实例造成的内存泄漏
    3、Handler造成的内存泄漏
    4、线程造成的内存泄漏
    5、资源未关闭造成的内存泄漏

  • 常见的解决方案(思路)

    1、尽量使用Application的Context而不是Activity的漏
    2、使用弱引用或者软引用漏
    3、手动设置null,解除引用关系漏
    4、将内部类设置为static,不隐式持有外部的实例漏
    5、注册与反注册成对出现,在对象合适的生命周期进行反注册操作。漏
    6、如果没有修改的权限,比如系统或者第三方SDK,可以使用反射进行解决持有关系
    7、在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存,

由于篇幅过长这里就不对此展开介绍。

Demo附件
LeakCanary原理分析

参考链接:
https://github.com/square/leakcanary
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

  • 11
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值