LeakCanary,由Square开源的一款轻量第三方内存走漏检测工具。能够在不影响程序正常运转的状况下,动态搜集程序存在的内存走漏问题。小的内存走漏可能不会直接导致程序溃散,但跟着数量增多,量变引起质变,形成内存溢出,程序溃散。由于LeakCanary功用强大且部署简略的特点,深受大家喜爱。

简略运用

新版别2.x比较1.x的差异不只仅是开发言语改为kotlin,也不需求手动进行初始化了,只需求在主模块中的build.gradle中添加如下依靠,即可完结初始化。

dependencies {
    // debugImplementation because LeakCanary should only run in debug builds.
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}

经过这儿的引证办法可见,LeakCanary只对debug版别apk起作用,考虑到其对内存的检测就很耗费系统资源,默许只允许debug包运用也很正常,当然,不只仅是这单个原因,后续会细讲。添加完依靠再运转程序后,就会一起运转leakCanary。退出后,手机桌面会自动生成leakcanary的图标。此时点开图标,假如有内存走漏状况产生,里边就会提示相应信息。界面如下:

LeakCanary原理解析

为什么会产生内存走漏

长生命周期的目标持有了短生命周期的引证导致本应该被收回的内存无法收回。为了维持多使命环境的正常运转,Android系统会为每个运用的堆大小设置硬性上限。跟着内存走漏的不断累积,APP会逐渐耗费完内存,导致内存溢出,终究引发OOM。

常见内存走漏场景

单例

public class SingletonActivityContext {
    private static SingletonActivityContext instance;
    private Context context;
    private SingletonActivityContext(Context context){
        this.context = context;
    }
    public static SingletonActivityContext getInstance(Context context){
        if (instance == null ){
            instance = new SingletonActivityContext(context);
        }
        return instance;
    }
}

单例的static变量由于static特性,使得其生命周期跟咱们运用生命周期是相同长的,这时分假如运用不当,很简单形成内存走漏。例如上述代码,假如传入Context是Activity的话,则当Activity退出的时分,其内存并不会被收回。由于代码中的单例目标持有了activity的引证,会导致activity想被内存收回的时分无法被收回。其处理办法,便是留意传入Context是Application即可:

LeakCanary原理解析
这样单例的生命周期跟咱们运用的生命周期相同长,就不会有内存走漏问题。

非静态内部类创立静态实例形成的内部走漏

public class StaticLeakActivity extends Activity {
    public static innerStaticClass mData = null;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (mData == null){
            mData = new innerStaticClass();
        }
    }
    private class innerStaticClass{
    }
}

例如,上述代码,能够防止innerStaticClass目标的重复创立,但这样也会形成内存走漏。由于在这儿,innerStaticClass默许会持有外部StaticLeakActivity的引证,由于其被static修饰,导致innerStaticClass这个变量的生命周期跟咱们运用的生命周期相同长,这就致使StaticLeakActivity想被内存收回的时分无法被收回。

Handler

handler形成内存走漏的场景非常普遍,许多时分,咱们为了防止ANR,不在主线程做耗时操作,这时分处理网络使命或许处理网络回调的时分,咱们都需求借助handler来处理。handler内部跟Message、MessageQueue相互关联在一起,假如handler发送音讯的时分,Message没有被处理完结,这个Message以及发送该Message的目标就将被咱们的线程所一向持有,这儿的handler实际上便是个TLS变量(生命周期与整个Activity生命周期不共同),因而这种完结办法很难保证跟Activity生命周期共同,这样就很简单导致内存走漏。

public class HandlerLeakActivity extends Activity {
    private final Handler mLeakHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mLeakHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
            }
        },1000*60);
        finish();
    }
}

对应处理办法很简略:1、将Handler声明为静态的(防止在Activity内运用静态类),这样Handler的生命周期就与activity生命周期无关了;2、经过弱引证的办法引入Activity,在Handler内部持有外部类HandlerLeakActivity弱引证,防止将Activity作为Context直接传进去。

线程所形成的内存走漏

在平常开发过程中,线程形成的内存走漏是常见的,由于平常开发工作中,经常会有敞开线程操作,假如这个时分你界说了其间的AsyncTask或许Runnable,界说成非静态内部类的话,则当Activity想要被收回的时分,由于线程使命持有了Activity的隐式引证,会使得Activity被毁掉的时分由于使命没有完结而导致无法被收回,由此导致内存走漏。

public class ThreadLeakActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        testThreadLeak();
    }
    private void testThreadLeak() {
        new Thread((Runnable) () -> {
            SystemClock.sleep(1000);
        }).start();
    }
}

处理办法:能够将Runnable界说为静态内部类,这样能够防止Activity内存资源走漏,一起,能够在onDestory毁掉时,调用AsyncTask的cancel()等。

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        SystemClock.sleep(1000);
    }
}

此状况与上述Handler形成的内存走漏状况相似,原因相同,因而都是把Handler或许Runnable界说成静态内部类,然后不持有外部类的引证,这样就能使咱们的外部类Activity被收回。

WebView

webView形成内存走漏的原因也很简略:webView内部的一些线程持有activity目标,导致activity无法开释。反映在正常用户的操作上便是,用户反复进出webView页面,页面占用内存不断升高,终究触发GC。 有以下两种防止办法:

一、不在xml中界说Webview(这样会引证Activity),而是在需求的时分在Activity中创立,并且上下文目标运用getApplicationgContext();

二、给运用WebView的Activity独自敞开一个新进程,经过AIDL来做数据交互。

检测内存走漏的计划

有问题,就有处理办法。对于内存走漏问题,也是八仙过海、各显神通。字节有Liko、快手有KOOM,也有人独自运用profile等工具。而LeakCanary便是很多计划中的一种:

LeakCanary: It automatically watches destroyed activities and fragments, triggers a heap dump, runs Shark Android and then displays the result.

LeakCanary的图标是一只鸟,也是其单词的直接含义—金丝雀(金丝雀对有害气体具有必定程度的敏感性,因而常常在矿场里检测矿井中气体)。

发动机遇

LeakCanary在更新至2.x版别后,最大的一个不同便是不用在Application的onCreate中对其进行初始化处理了。

LeakCanary原理解析
调查其Manifest.xml文件可见端倪,此处有一个ContentProvider注册。对应ContentProvider为:
LeakCanary原理解析
ContentProvier一般会在Application被创立之前被加载,LeakCanary在其onCreate()办法中调用了AppWatcher.manualInstall(application)进行初始化。这种办法的确是方便了开发者,可是细心想想坏处还是很大的,假如一切第三方库都如此操作,开发者就无法控制运用发动时间了。现在许多APP为了用户体会对发动时间有严格限制,包括按需推迟初始化第三方库。但在LeakCanary中,这个问题并不存在,由于它自身便是一个只在debug版别中运用的库,并不会对release版别有任何影响。(这儿知道为什么只允许debugImplementation了吧)

Watcher和Activity的监测机遇

LeakCanary原理解析
在这儿AppWatcher.manualInstall(application)调用的是InternalAppWatcher.install(application),并在其间进行初始化:

LeakCanary原理解析
在这儿,①是判别是否是在主线程中调用,假如不是就会抛出异常;②担任监听Activity的onDestory();③担任监听Fragment的onDestory()。

这儿,咱们先来看下ActivityDestroyWatcher.install()的源码

LeakCanary原理解析
可见application.registerActivityLifecycleCallbacks()在这儿是注册Activity生命周期监听,

LeakCanary原理解析
而对应的lifecycleCallbacks中,则是监听到onDestroy()之后,经过objectWatcher监测Activity。

如此,能够说,install()办法中注册了Activity生命周期监听,在监听到onDestroy()时,调用objectWatcher.watch()开端监测Activity。

到这儿了,能够发现,下一步的突破办法在watch()。咱们来调查一下此办法:

LeakCanary原理解析
在这儿,①removeWeaklyReachableObjects()是把gc前ReferenceQueue中的引证铲除;②KeyedWeakReference()是将activity等包装为弱引证,并于ReferenceQueue建立关联;③5s之后进行检测(5秒内gc完结)。

要留意的是③中的checkRetainedExecutor是传入参数,此处由InternalAppWatcher.kt中的checkRetainedExecutor做传入参数:

LeakCanary原理解析

LeakCanary原理解析
可见,5秒钟是这么来的。持续调查③中的moveToRetained():

LeakCanary原理解析
5秒时间内,铲除一切弱引证目标,进行gc操作:

LeakCanary原理解析
假如gc操作完结,则上述变量watchObjects必定清空,则retainedRef必定为null,假如没有清空,则触发内存走漏处理。(当然5秒内也不必定会触发gc,所以之后的内存走漏处理会自动gc再判别一次)

LeakCanary原理解析
这儿的onObjectRetained()完结代码在InternalLeakCanary中:

LeakCanary原理解析

LeakCanary原理解析

LeakCanary原理解析
这一块的逻辑是确认是否存在内存走漏,

LeakCanary原理解析

LeakCanary原理解析

LeakCanary原理解析
能够看到,调用checkRetainedCount()判别当前走漏实例个数假如小于5个,则给用户一个告诉,不会进行heap dump操作,并在5s后再次建议检测(所以,当走漏引证到达5个时才会建议heap dump)ReferenceQueue。这儿终究会履行到HeapDumpTrigger中的dumpheap():

LeakCanary原理解析
这儿的主要流程便是dump hprof文件,然后敞开HeapAnalyzerService来剖析heap文件。

很明显,这儿咱们要先调查heapDumper.dumpHeap()中的内部完结:

LeakCanary原理解析
通读办法能够看出其逻辑是发出特定的Notification,一起:

LeakCanary原理解析
经过Debug.dumpHprofData(heapDumpFile.absolutePath)来dump hprof文件,至此完结dump heap流程,再回到最初dumpHeap()中,完结dump heap后,就会调用HeapAnalyzerService的runAnalysis()来剖析heap文件。

LeakCanary原理解析

LeakCanary原理解析
可见,这儿在服务中经过序列化的办法传输文件目标,最后在analyzeHeap()中来进行剖析:

LeakCanary原理解析
而analyzeHeap()则又是调用了HeapAnalyzer的analyze():

LeakCanary原理解析
此办法中又是经过ConstantMemoryMetricsDualSourceProvider()来读取hprof文件,

LeakCanary原理解析
然后调用FindLeakInput的analyzeGraph()进行剖析:

LeakCanary原理解析
在这儿leakingObjectFinder.findLeakingObjectIds(graph)是从hprof中获取走漏的目标id调集,在这主要是搜集没有被收回的弱引证。随后经过findLeaks()来判别这些没有被收回的弱引证有无内存走漏。

LeakCanary原理解析
其办法是经过计算到gcroot的最短引证路径,来判别是否产生走漏。

LeakCanary原理解析
终究经过调用buildLeakTraces()来构建包含引证链信息的LeakTrace,呈现剖析效果。详细剖析的其他细节,这儿就不赘述了。这儿的一切内存剖析运用了Square的shark库(主要逻辑在moudle leakcanary-analyzer中),大致流程便是读取hprof文件,找到之前标记的走漏目标,寻觅gcroot,然后保存剖析结果至数据库,点击对应告诉跳转后便可看到目标数据的展示。上述检测流程是关于Activity的,而LeakCanary不只能够检测Activity,还能够检测Fragement(AndroidX包下)、ViewModel、RootView、Service目标。

Fragment的监测机遇

LeakCanary原理解析
在最开端的install()中,③处开端便是对Fragment的检测:

LeakCanary原理解析
调查其FragmentDestroyWatcher.install()流程会发现终究会看到AndroidOFragmentDestroyWatcher:

LeakCanary原理解析
终究在onFragmentViewDestroyed()和onFragmentDestroyed()中分别将Fragment中的View和Fragment参加watchedObjects里边等待检测。

ViewModel的检测机遇

关于ViewModel的检测机遇,则要重视ViewModelClearedWatcher。ViewModelClearedWatcher承继自ViewModel,其自身是一个ViewModel,且完结了onCleared(),这个办法相似Activity的onDestroy(),在ViewModel毁掉的时分会履行。

LeakCanary原理解析
调查代码不难发现,当ViewModelClearedWatcher创立的时分,经过反射拿到宿主的mMap,这样便得到宿主中一切的ViewModel。当ViewModelClearedWatcher被毁掉时,会回调onCleared(),这时会遍历宿主中一切的ViewModel,履行reachabilityWatcher的expectWeaklyReachable办法,判别ViewModel是否都现已开释。

RootView的检测

需求留意的是,这儿是RootView,即根View,不是一切View。LeakCanary默许检测一切的根View。详细代码就不贴了,都是一个套路。经过监听View的OnAttachStateChangeListener毁掉监听,当View和Window绑定和取消绑定的时会回调此办法。

流程图

以Activity的检测流程为例,其流程图如下:

LeakCanary原理解析

总结一下

总结一下,LeakCanary流程中最经常被问到的两个问题:

1、怎么确认内存走漏目标 WeakReference,其双参数结构函数支持传入一个ReferenceQueue,当其关联的目标收回时,会将WeakReference参加ReferenceQueue中。LeakCanary承继ReferenceQueue,将每个需求监测的目标WeakReference参加一个map中。在GC过后,经过removeWeaklyReachableObjects()遍历ReferenceQueue,经过key值删除map中已收回的目标,剩下的就能够确认产生了内存走漏。

2、怎么确认从GCroot到走漏目标的引证链 在确认内存走漏的目标后,checkRetainedObjects(),发动前台服务HeapAnalyzerService,这时分就能在告诉中看到了。而HeapAnalyzerService中则是调用了HeapAnalyzer的analyze办法进行堆内存剖析,此功用由Shark库完结。