LeakCanary源码阅读

一、相关概念

1. 弱引用与引用队列

在WeakReference指向的对象在GC时被回收后,WeakReference本身其实也就没有用了,系统会把该弱引用对象加入到与之关联的ReferenceQueue中(当然前提是你创建该WeakReference的时候给其关联了ReferenceQueue)。


摘自 零度anngle 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/zmx729618/article/details/54093532?utm_source=copy

二、使用

  1. gradle中添加依赖
dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
}

leakcanary-android-no-op是用于release版本的,因为LeakCanary会不断GC,造成stop the world 这在release版本中时不允许的,因此内存泄漏检测我们只能用在debug版本。

  1. 在Application中进行初始化操作
public class ExampleApplication extends Application {

  @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);
    // Normal app init code...
  }
}

LeakCanary在主进程中检测内存泄漏,在另外的进程中分析hprof文件。多进程下Application被多次创建,为了防止在 Application中出现无用的重复初始化,在Application中可通过processName来过滤,只有在指定进程才做相应的初始化

  1. 在Manifest中配置Application

....

三、源码分析

3.1LeakCanary的准备工作

LeakCanary检测内存泄漏的入口:Application中的

LeakCanary.install(this);

该方法的主要作用是通过Builder模式构建RefWatcher实例,并做一些初始化的准备工作(为application注册ActivityLifecycleCallback)

public final class LeakCanary{
    public static RefWatcher install(Application application){
        // 采用构造器模式构造RefWatcher实例,链式调用
            return ((AndroidRefWatcherBuilder)refWatcher(application)
            .listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()))
            .buildAndInstall();
    }
public static AndroidRefWatcherBuilder refWatcher(Context context) {
        return new AndroidRefWatcherBuilder(context);
    }
}

1)得到AndroidRefWatcherBuilder

上述方法采用构建者模式,(AndroidRefWatcherBuilder extends RefWatcherBuilder)最终构造出一个RefWatcher实例,RefWatcher用来监视一个引用的可达情况

2).listenerServiceClass(DisplayLeakService.class)

public final class AndroidRefWatcherBuilder{
    
    public AndroidRefWatcherBuilder listenerServiceClass(Class listenerServiceClass) {
        return (AndroidRefWatcherBuilder)this.heapDumpListener(new ServiceHeapDumpListener(this.context, listenerServiceClass));
    }
}

上述AbstractAnalysisResultService是一个抽象类,继承自IntentServicepublic abstract class AbstractAnalysisResultService extends IntentService),用于处理heapDump结果。
LeakCanary#install()中传入的参数DiaplayLeakService继承自AbstractAnalysisResultServicepublic class DisplayLeakService extends AbstractAnalysisResultService),运行在独立的进程中

 

3)excludedRefs(...)

有时候我们需要忽略一些特殊情况下的内存泄漏比如SDK或者制造商导致的内存泄漏,这些内存泄漏我们并不需要显示出来,那么刚好AndroidExcludedRefs这个枚举就派上用场了。

4)buildAndInstall()

public final class AndroidRefWatcherBuilder{

    public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = this.build();
        if(refWatcher != RefWatcher.DISABLED) {
            LeakCanary.enableDisplayLeakActivity(this.context);
            // 调用ActivityRefWatcher.install()方法,将Application的上下文和RefWatcher传入
            ActivityRefWatcher.install((Application)this.context, refWatcher);
        }
        return refWatcher;
    }
}

构建出RefWatcher实例,紧接着调用ActivityRefWatcher.install((Application)this.context, refWatcher);这是重点

public final class ActivityRefWatcher{
    // ActivityLifecycleCallbacks:应用内Activity生命周期发生变化时会回调相应函数【该方法在API14后才有】
    private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        public void onActivityStarted(Activity activity) {
        }

        public void onActivityResumed(Activity activity) {
        }

        public void onActivityPaused(Activity activity) {
        }

        public void onActivityStopped(Activity activity) {
        }

        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        public void onActivityDestroyed(Activity activity) {
            ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
    };
    void onActivityDestroyed(Activity activity) {
        this.refWatcher.watch(activity);
    }
    // install
    public static void install(Application application, RefWatcher refWatcher) {
        (new ActivityRefWatcher(application, refWatcher)).watchActivities();
    }
    // 构造函数
    public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
        this.application = (Application)Preconditions.checkNotNull(application, "application");
        this.refWatcher = (RefWatcher)Preconditions.checkNotNull(refWatcher, "refWatcher");
    }
    public void watchActivities() {
        this.stopWatchingActivities();
        // 为application注册ActivityLiftcycleCallbacks,监听应用内所有Activity的生命周期
        this.application.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }

    public void stopWatchingActivities() {
        // 为application取消注册ActivityLiftcycleCallbacks监听
        this.application.unregisterActivityLifecycleCallbacks(this.lifecycleCallbacks);
    }
}

上面的代码逻辑其实很简单,就是为application注册ActivityLifecycleCallback,监听应用内所有Activity的生命周期,LeakCanary只关注onDestroy(),在Activity的onDestroy()回调时去做一些事情。

3.2 LeakCanary检测是否发生了内存泄漏

当Activity回调了生命周期中的onDestory()时,LeakCanary就运行下面的代码检测是否发生内存泄漏

public final class  RefWatcher{
    
    private final Set retainedKeys;
    // 引用队列
    private final ReferenceQueue queue;
    
    public void watch(Object watchedReference) {
        this.watch(watchedReference, "");
    }

    public void watch(Object watchedReference, String referenceName) {
        if(this != DISABLED) {
            // 判空,如果是null的话抛出NullPointerException异常
            Preconditions.checkNotNull(watchedReference, "watchedReference");
            Preconditions.checkNotNull(referenceName, "referenceName");
            // 得到当前时间:作为监视开始时间
            long watchStartNanoTime = System.nanoTime();
            // 随机生成UUID,并添加到Set中
            String key = UUID.randomUUID().toString();
            this.retainedKeys.add(key);
            // 传入引用队列:监听GC后的情况
            KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
            // 这个方法是LeakCanary检测对象是否被回收的核心代码
            this.ensureGoneAsync(watchStartNanoTime, reference);
        }
    }
}


final class KeyedWeakReference extends WeakReference {
    public final String key;
    public final String name;

    /**
    * @param referent :当前被监视的引用
    * @param key:当前被监视对象的key(唯一UUID)
    * @param name:引用name
    * @param referenceQueue:引用队列
    */
    KeyedWeakReference(Object referent, String key, String name, ReferenceQueue referenceQueue) {
        super(Preconditions.checkNotNull(referent, "referent"), (ReferenceQueue)Preconditions.checkNotNull(referenceQueue, "referenceQueue"));
        this.key = (String)Preconditions.checkNotNull(key, "key");
        this.name = (String)Preconditions.checkNotNull(name, "name");
    }
}

/**
* 弱引用
*/
public class WeakReference extends Reference {

    /**
     * Creates a new weak reference that refers to the given object.  The new
     * reference is not registered with any queue.
     *
     * @param referent object the new weak reference will refer to
     */
    public WeakReference(T referent) {
        super(referent);
    }

    /**
     * Creates a new weak reference that refers to the given object and is
     * registered with the given queue.
     *
     * @param referent object the new weak reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or null if registration is not required
     * 弱引用可以和引用队列结合起来使用
     */
    public WeakReference(T referent, ReferenceQueue q) {
        super(referent, q);
    }
}
 
 

首先

  1. 将被监视的对象用弱引用KeyedWeakReference包装起来,包括
  • String key:唯一的UUID
  • String name:被监视的对象的referenceName
  • Object reference:被监视的对象
  • ReferenceQueue queue:引用队列
    1. 将该引用对应的唯一的UUID放入Set retainedKey

    然后:再调用ensureGoneAsync检查是否发生内存泄漏

    public final class  RefWatcher{
        // 真正实现WatchExecutor接口的是AndroidWatchExecutor类,
        // 在这个类里面给主线程的消息队列添加一个IdleHandler,
        // 当主线程空闲没有消息的时候就执行execute()方法,
        // 而在上面的execute()方法里面则调用ensureGone()方法。
        private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
            this.watchExecutor.execute(new Retryable() {
                public Result run() {
                    return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
                }
            });
        }
        Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
            long gcStartNanoTime = System.nanoTime();
            long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
            // 将引用尝试从queue中poll出来
            this.removeWeaklyReachableReferences();
            if(this.debuggerControl.isDebuggerAttached()) {
                // 规避调试模式
                return Result.RETRY;
            } else if(this.gone(reference)) {
                // 检测引用是否已经被回收
                return Result.DONE;
            } else {
                // 手动GC
                this.gcTrigger.runGc();
                // 再次尝试poll,检测是否被回收
                this.removeWeaklyReachableReferences();
                if(!this.gone(reference)) {
                    // 还没有被回收,则dump堆信息,调起分析进程分析
                    long startDumpHeap = System.nanoTime();
                    long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
                    File heapDumpFile = this.heapDumper.dumpHeap();
                    if(heapDumpFile == HeapDumper.RETRY_LATER) {
                        return Result.RETRY;
                    }
    
                    long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
                    this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
                }
    
                return Result.DONE;
            }
        }
    
        private boolean gone(KeyedWeakReference reference) {
            return !this.retainedKeys.contains(reference.key);
        }
    
        // 检查队列中是否已包含该object的弱引用对象
        private void removeWeaklyReachableReferences() {
            KeyedWeakReference ref;
            while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
                // 引用从引用队列中出队的同时,也将retainedKeys中引用对应的key值移除
                this.retainedKeys.remove(ref.key);
            }
    
        }
    }
    

    需要注意的点:

    1. 真正实现WatchExecutor接口的是AndroidWatchExecutor类,执行WatchExecutor的execute()方法

    上面代码执行WatchExecutor的execute()方法,真正实现WatchExecutor接口的是AndroidWatchExecutor类,在这个类里面给主线程的消息队列添加一个IdleHandler,当主线程空闲没有消息的时候就执行execute()方法,而在上面的execute()方法里面则调用ensureGone()方法。

    2. 如何判断该对象是否被回收

    通过判断retainedKey中是否存在该引用对应的key。不存在,说明该对象已被回收【因为在Activity回调onDestroy()时,LeakCanary将该对象的keyUUID添加到了retainedKey中,在removeWeaklyReachableReferences()方法中尝试将引用队列中的所有引用出队,同时把retainedKey中对应的key移除】

    如何检测内存泄漏

    3. 当判断发生内存泄漏后所做的操作

    dump heap并生成一个hprof文件并解析这个文件,得到泄漏对象到GC根的最短路径,得到这个路径后发一个Notification展示出来即可。

    未完待续............

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

    参考文献

    Java内存问题 及 LeakCanary 原理分析
    利用 LeakCanary 来检查 Android 内存泄漏
    三叶梧桐_LeakCanary原理详解
    校招指南

    你可能感兴趣的:(LeakCanary源码阅读)