LeakCanary源码分析(1)

LeakCanary是在Android上一款开源的内存泄露分析工具,通过这款工具你可以实时的观察到当前app中出现的内存泄露情况,并且提供泄露路径方便用户来查找内存泄露的根源,[链接]是GitHub上的项目1

LeakCanary 项目中,包括了五个模块,分别是

  • leakcanary-analyser(为解析hprof文件,找到泄露的对象)
  • leakcanry-android(主要包含了暴漏给android使用的接口)
  • leakcanary-anroid-no-op(没有用到)
  • leakcanary-smaple(示例代码)
  • leakcanary-watcher(监视对象,触发GC并且生成hprof文件)

下面从示例代码中开始入手看LeakCanary是如何发挥作用的

leakcanary-smaple

在这个模块当中只有两个类,一个是自定义的ExampleApplication,继承于Application,一个是MainActivity.

@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;
    }
    enabledStrictMode();
    LeakCanary.install(this);
}

在onCreate方法当中,首先判断当前的进程是不是分析进程,在LeakCanary当中为了避免在用户程序的进程当中占用太多的资源,所以整个分析过程是在一个单独的进程当中。

这个方法最终是调用了LeakCanaryInternals这个类的isInServiceProcess方法,这个方法比较简单,主要是通过比较当前Application所在进程的名字是不是HeapAnalyzerService在AndroidManifest.xml中所指定的名字来判断当前的进程是不是应用所在的进程,如果是应用所在的进程 LeakCanary.isInAnalyzerProcess(this) 最终会返回一个false, 这个时候将会继续往下执行,最后执行到LeakCanary.install(this); 如果当前进程的名字就是HeapAnalyzerService在AndroidManifest.xml中所指定的名字,那么表示当前进程是分析hprof的进程,这个时候就直接返回。

接下来进入到 LeakCanary.install(this) 当中,方法的代码如下

  /**
  * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
  * references (on ICS+).
*/
 public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
    .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
    .buildAndInstall();
    }

整个方法中都是用调用了通过调用 LeakCanary中的静态方法refWather返回了一个AndroidRefWatcherBuilder对象,这个对象中采用了建造者模式,来设置我们内存泄露中我们设置的一些参数

  • listenerServiceClass()解析结果处理的IntentService. 这个Service需要继承AbstractAnalysisResultService 并且重写onHeapAnalyzed(HeapDump, AnalysisResult)方法来处理得到的分析结果。
  • excludedRefs 用来方便用户去掉一些特定的引用,当进行内存泄露分析的时候这些引用将不会被考虑在内。
  • buildAndInstall() 创建了RefWatcher实例,并且开始监听Activity的引用。

    public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
            LeakCanary.enableDisplayLeakActivity(context);
            ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
        }
        return refWatcher;
    }
    

从上面代码不难看出其中最重要的方法调用是ActivityRefWatcher.installOnIcsPlus。

@TargetApi(ICE_CREAM_SANDWICH)
public final class ActivityRefWatcher {

    public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
        if (SDK_INT < ICE_CREAM_SANDWICH) {
            // If you need to support Android < ICS, override onDestroy() in your base activity.
            return;
        }
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        activityRefWatcher.watchActivities();
    }

    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
            new Application.ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                }

                @Override
                public void onActivityStarted(Activity activity) {
                }

                @Override
                public void onActivityResumed(Activity activity) {
                }

                @Override
                public void onActivityPaused(Activity activity) {
                }

                @Override
                public void onActivityStopped(Activity activity) {
                }

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

                @Override
                public void onActivityDestroyed(Activity activity) {
                    ActivityRefWatcher.this.onActivityDestroyed(activity);
                }
            };

    private final Application application;
    private final RefWatcher refWatcher;

    /**
     * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
     * after they have been destroyed.
     */
    public ActivityRefWatcher(Application application, final RefWatcher refWatcher) {
        this.application = checkNotNull(application, "application");
        this.refWatcher = checkNotNull(refWatcher, "refWatcher");
    }

    void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
    }

    public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
    }

    public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
    }
}

看到 lifecycleCallbacks 变量的类型的类型,就应该恍然大悟了,LeakCanary就是通过在Application当中注册了Activity 生命周期的监听器来实现对Acitivity 引用泄露的分析。 , 每当一个调用一个Activity onDestroy方法之前会调用lifecycleCallbacks的onActivityDestroyed方法。
最终会进入到RefWatcher实例的watch(Object ref, String reference Name)方法当中,代码如下:

/**
 * Watches the provided references and checks if it can be GCed. This method is non blocking,
 * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
 * with.
 *
 * @param referenceName An logical identifier for the watched object.
 */
public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
        return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
}

在解释这段代码之前简单的说一下判断一个对象有没有被回收的原理:

在Java中,当我们使用WeakReference指向一个对象的时候,如果这个对象已经被回收了,那么WeakReference 将会被添加到一个指定的队列当中去

在这个方法中,LeakCanary 创建了一个指向Activity的弱引用KeyedWeakReference 对象,KeyedWeakReference继承于WeakReference 但是做了一定的扩展就是添加了两个字符串类型的字段name 和 key。这个key 也就是通过UUID生成的字符串。然后把这个key 添加到了 retainedKeys 代表的字符串集合当中去,只要key还在retainedKeys 当中就表示和 key 绑定的weakreference所指向的对象还没有被回收。

ensureGoneAsync()只是把分析方法通过异步的方式来处理(里面还有很多内容可以分析),在里面最终的调用的方法是 ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) 方法

放代码

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    ....

    removeWeaklyReachableReferences();

    ...
    if (gone(reference)) {
        return DONE;
    }
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
        }

        heapdumpListener.analyze(
                new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                        gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
}

首先进入removeWeaklyReachableReferences() 方法

private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
    }
}

在方法的注释当中我们可以清楚的明白方法的含义:
一个对象如果变得只有一个弱引用指向它,那么它就会被添加到指定的队列当中去,就是在前面提到过的queue,这个时候我们可以通过KeyedWeakReference 来找出当初匹配的weakference , name , key,然后将key 从集合中移除,表明key对应引用所指向的对象已经被回收。

接下来这个时候我们在判断一下引用的所指向的对象是不是被回收了,这个判断还是通过判断key 在不在集合当中,如果不在表示这个对象已经完全被回收,可以直接返回。

接下来会触发一次GC. 之所以这样做是为了尽可能的避免进行内存的dump和分析操作。有些对象在这个时候本应该是只有weakreference 可达,但是却不在对队列当中,这个时候触发一次GC并且再进行一次引用分析可以有效地避免一些误判。

这里触发GC的代码如下

// System.gc() does not garbage collect every time. Runtime.gc() is
  // more likely to perfom a gc.
  Runtime.getRuntime().gc();

GC完成之后,接下来再进行一次引用的检查,如果这个时候对象还是没有回收那么接下来就要开始内存的dump 操作了。