LeakCanary源码学习

来源:互联网 发布:程序员怀疑手游被抄袭 编辑:程序博客网 时间:2024/05/23 15:47

最近一段时间眼睛干涉,所以下班后基本不动电脑了,就看看书,练练字,所以这个月写的博客很少很少,在7月的最后一天,补上一篇。


前言

OOM是开发中很常见的一个异常,其凶手就是内存泄漏。square公司为我们开源了一个神兵利器——LeakCanary,在开发中为我们提供了很大的方便,让我们能够及时发现和处理代码中的内存泄漏问题。

作为一个程序员,本着知其然及只其所以然的态度,就很有必要学习下这款开源框架的思想了。

LeakCanary 使用方式

为了将 LeakCanary 引入到我们的项目里,我们只需要做以下两步:

dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'}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);  }}

可以看出,最关键的就是 LeakCanary.install(this); 这么一句话,正式开启了 LeakCanary 的大门,未来它就会自动帮我们检测内存泄漏,并在发生泄漏是弹出通知信息。

LeakCanary.install(this);

  public static RefWatcher install(Application application) {    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())        .buildAndInstall();  }

我们先看listenerServiceClass(DisplayLeakService.class)方法:

/** * Logs leak analysis results, and then shows a notification which will start {@link * DisplayLeakActivity}. * * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult, * String)} to add custom behavior, e.g. uploading the heap dump. */public class DisplayLeakService extends AbstractAnalysisResultService {    ...}

看注释就知道DisplayLeakService的功能:“记录内存泄漏分析的结果,并以通知的形式展示分析结果”。
我们看下他的父类是什么作用:

public abstract class AbstractAnalysisResultService extends IntentService {  private static final String HEAP_DUMP_EXTRA = "heap_dump_extra";  private static final String RESULT_EXTRA = "result_extra";  public static void sendResultToListener(Context context, String listenerServiceClassName,      HeapDump heapDump, AnalysisResult result) {    Class<?> listenerServiceClass;    try {      listenerServiceClass = Class.forName(listenerServiceClassName);    } catch (ClassNotFoundException e) {      throw new RuntimeException(e);    }    Intent intent = new Intent(context, listenerServiceClass);    intent.putExtra(HEAP_DUMP_EXTRA, heapDump);    intent.putExtra(RESULT_EXTRA, result);    context.startService(intent);  }  public AbstractAnalysisResultService() {    super(AbstractAnalysisResultService.class.getName());  }  @Override protected final void onHandleIntent(Intent intent) {    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);    try {      onHeapAnalyzed(heapDump, result);    } finally {      //noinspection ResultOfMethodCallIgnored      heapDump.heapDumpFile.delete();    }  }  protected abstract void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result);}

我们都知道IntentService是一个在子线程运行的后台服务,AbstractAnalysisResultService的作用就是对 dump 的内存情况进行分析。

接着我们来看 buildAndInstall()方法:

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

先生成了一个 RefWatcher,这个东西非常关键,从名字可以看出,它是用来 watch Reference 的,也就是用来一个监控引用的工具。

首先看最重要的install()方法:

public static void install(Application application, RefWatcher refWatcher) {   new ActivityRefWatcher(application, refWatcher).watchActivities(); }

创建了一个 ActivityRefWatcher,大家应该能感受到,这个东西就是用来监控我们的 Activity 泄漏状况的,它调用watchActivities() 方法,就可以开始进行监控了。下面就是它监控的核心原理:

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

它向 application 里注册了一个 ActivitylifecycleCallbacks 的回调函数,可以用来监听 Application 整个生命周期所有 Activity 的 lifecycle 事件。再看下这个 lifecycleCallbacks 是什么?

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);    }};

原来它只是监听了onActivityDestroyed(),那么可以猜测onActivityDestroyed()方法就是用来判断activity是否为null了,如果不为null,表示activity没被系统回收,也就是发生了内存泄漏。

是否就是如同我们猜测的那样呢?请继续看:

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

可以看出,这个函数把目标 activity 对象传给了 RefWatcher,让它去监控这个 activity 是否被正常回收了,若未被回收,则意味着发生了内存泄漏。

RefWatcher.watch(activity);

我们继续跟进上面的RefWatcher#watch(activity)方法:

public static final RefWatcher DISABLED = (new RefWatcherBuilder()).build();... 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);  }
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {    watchExecutor.execute(new Retryable() {      @Override public Retryable.Result run() {        return ensureGone(reference, watchStartNanoTime);      }    });}

可以看到,它首先把我们传入的 activity 和key包装成了一个 KeyedWeakReference(可以暂时看成一个普通的 WeakReference),在一个 activity 传给 RefWatcher 时会创建一个唯一的 key 对应这个 activity,该 key 存入一个集合 retainedKeys 中。也就是说,所有我们想要观测的 activity 对应的唯一 key 都会被放入 retainedKeys 集合中。然后 watchExecutor 会去执行一个 Retryable,这个 Retryable的run()方法里会调用 ensureGone(reference, watchStartNanoTime) 函数(Retryable与Runnable的功能大致相同,不同的是Retryable是有返回值的)。

看这个函数之前猜测下,我们知道 watch 函数本身就是用来监听 activity 是否被正常回收,这就涉及到两个问题:

  • 何时去检查它是否回收?
  • 如何有效地检查它真的被回收?

所以我们觉得 ensureGone 函数本身要做的事正如它的名字,就是确保 reference 被回收掉了,否则就意味着内存泄漏。

ensureGone()的张良计

看看他是怎么实现的:

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {    long gcStartNanoTime = System.nanoTime();    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);    removeWeaklyReachableReferences();    if (debuggerControl.isDebuggerAttached()) {      // The debugger can create false leaks.      return RETRY;    }    if (gone(reference)) {      return DONE;    }    gcTrigger.runGc();    removeWeaklyReachableReferences();    if (!gone(reference)) {      long startDumpHeap = System.nanoTime();      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);      File heapDumpFile = heapDumper.dumpHeap();      if (heapDumpFile == RETRY_LATER) {        // Could not dump the heap.        return RETRY;      }      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);      heapdumpListener.analyze(          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,              gcDurationMs, heapDumpDurationMs));    }    return DONE;  } private boolean gone(KeyedWeakReference reference) {      return !this.retainedKeys.contains(reference.key);  } private void removeWeaklyReachableReferences() {     KeyedWeakReference ref;     //遍历整个队列,获取并移除此队列的头,如果此队列为空,则返回null     while((ref = (KeyedWeakReference)this.queue.poll()) != null) {         //从retainedKeys集合中移除         this.retainedKeys.remove(ref.key);     } }

这里先来解释下 WeakReference 和 ReferenceQueue 的工作原理。

弱引用 WeakReference

被强引用的对象就算发生 OOM 也永远不会被垃圾回收机回收;被弱引用的对象,只要被垃圾回收器发现就会立即被回收;被软引用的对象,具备内存敏感性,只有内存不足时才会被回收,常用来做内存敏感缓存器;虚引用则任意时刻都可能被回收,使用较少。

引用队列 ReferenceQueue

我们常用一个 WeakReference reference = new WeakReference(activity);,这里我们创建了一个 reference 来弱引用到某个 activity,当这个 activity 被垃圾回收器回收后,这个 reference 会被放入内部的 ReferenceQueue 中。也就是说,从队列 ReferenceQueue 取出来的所有 reference,它们指向的真实对象都已经成功被回收了。

然后再回到上面的代码。

基于我们对 ReferenceQueue 的了解,只要把队列中所有的 reference 取出来,并把对应 retainedKeys 里的 key 移除,retainedKeys集合中剩下的 key 对应的对象都没有被回收。

1.ensureGone 首先调用 removeWeaklyReachableReferences 把已被回收的对象的 key 从retainedKeys 移除,剩下的 key 都是未被回收的对象;

2.if (gone(reference)) 用来判断某个 reference 的 key 是否仍在 retainedKeys 里,若不在,表示已回收,否则继续;

3.gcTrigger.runGc(); 手动出发 GC,立即把所有 WeakReference 引用的对象回收;

4.removeWeaklyReachableReferences(); 再次清理 retainedKeys,如果该 reference 还在 retainedKeys 里 (if (!gone(reference))),表示泄漏;

5.利用 heapDumper 把内存情况 dump 成文件,并调用 heapdumpListener 进行内存分析,进一步确认是否发生内存泄漏。

6.如果确认发生内存泄漏,调用 DisplayLeakService发送通知。

内存泄漏检测小结

从上面我们大概了解了内存泄漏检测机制,大概是以下几个步骤:

1.利用 application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
来监听整个生命周期内的 Activity onDestoryed 事件;

2.当某个 Activity 被 destory 后,将它传给 RefWatcher 去做观测,确保其后续会被正常回收;

3.RefWatcher 首先把 Activity 使用 KeyedWeakReference 引用起来,并使用一个 ReferenceQueue 来记录该 KeyedWeakReference 指向的对象是否已被回收;

4.AndroidWatchExecutor 会延迟5秒后,再开始检查这个弱引用内的 Activity 是否被正常回收。判断条件是:若 Activity 被正常回收,那么引用它的 KeyedWeakReference 会被自动放入 ReferenceQueue 中。

5.判断方式是:先看 Activity 对应的 KeyedWeakReference 是否已经放入 ReferenceQueue 中;如果没有,则手动 GC:gcTrigger.runGc();;然后再一次判断 ReferenceQueue 是否已经含有对应的KeyedWeakReference。若还未被回收,则认为可能发生内存泄漏。

6.利用 HeapAnalyzer 对 dump 的内存情况进行分析并进一步确认,若确定发生泄漏,则利用 HeapAnalyzerService发送通知。

探讨一些有趣的问题

在学习了 LeakCanary 的源码之后,我想再提几个有趣的问题做些探讨。

一、LeakCanary 项目目录结构为什么这样分?

下面是整个 LeakCanary 的项目结构:

这里写图片描述

对于开发者而言,只需要使用到 LeakCanary.install(this); 这一句即可。那整个项目为什么要分成这么多个 module 呢?

实际上,这里面每一个 module 都有自己的角色。

  • leakcanary-watcher: 这是一个通用的内存检测器,对外提供一个 RefWatcher#watch(Object watchedReference),可以看出,它不仅能够检测 Activity,还能监测任意常规的 Java Object 的泄漏情况。

  • leakcanary-android: 这个 module 是与 Android 世界的接入点,用来专门监测 Activity 的泄漏情况,内部使用了 application#registerActivityLifecycleCallbacks 方法来监听 onDestory 事件,然后利用 leakcanary-watcher 来进行弱引用+手动 GC 机制进行监控。

  • leakcanary-analyzer: 这个 module 提供了 HeapAnalyzer,用来对 dump 出来的内存进行分析并返回内存分析结果 AnalysisResult,内部包含了泄漏发生的路径等信息供开发者寻找定位。

  • leakcanary-android-no-op: 这个 module 是专门给 release 的版本用的,内部只提供了两个完全空白的类 LeakCanary 和 RefWatcher,这两个类不会做任何内存泄漏相关的分析。为什么?因为 LeakCanary 本身会由于不断 gc 影响到 app 本身的运行,而且主要用于开发阶段的内存泄漏检测。因此对于 release 则可以 disable 所有泄漏分析。

  • leakcanary-sample: 这个很简单,就是提供了一个用法 sample。

二、当 Activity 被 destory 后,LeakCanary 多久后会去进行检查其是否泄漏呢?

在源码中可以看到,LeakCanary 并不会在 destory 后立即去检查,而是让一个 AndroidWatchExecutor 去进行检查。它会做什么呢?

//传入延迟的时长//创建一个主线程和一个子线程 handler    public AndroidWatchExecutor(long initialDelayMillis) {        HandlerThread handlerThread = new HandlerThread("LeakCanary-Heap-Dump");        handlerThread.start();        this.backgroundHandler = new Handler(handlerThread.getLooper());        this.initialDelayMillis = initialDelayMillis;        this.maxBackoffFactor = 9223372036854775807L / initialDelayMillis;    }   //判断是否在主线程   //如果在io线程,则先切换到主线程,再执行waitForIdle(retryable, 0)方法   public void execute(Retryable retryable) {       if(Looper.getMainLooper().getThread() == Thread.currentThread()) {           this.waitForIdle(retryable, 0);       } else {           this.postWaitForIdle(retryable, 0);       }   }   private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {       this.mainHandler.post(new Runnable() {           public void run() {               AndroidWatchExecutor.this.waitForIdle(retryable, failedAttempts);           }       });   }   //它首先会向主线程的 MessageQueue 添加一个 IdleHandler。   //执行postToBackgroundWithDelay()方法   void waitForIdle(final Retryable retryable, final int failedAttempts) {       Looper.myQueue().addIdleHandler(new IdleHandler() {           public boolean queueIdle() {               AndroidWatchExecutor.this.postToBackgroundWithDelay(retryable, failedAttempts);               return false;// 只执行一次           }       });   }  //先切换到io线程  //如果result == Result.RETRY,再执行postWaitForIdle,并且failedAttempts的次数加一    private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {       long exponentialBackoffFactor = (long)Math.min(Math.pow(2.0D, (double)failedAttempts), (double)this.maxBackoffFactor);       long delayMillis = this.initialDelayMillis * exponentialBackoffFactor;       this.backgroundHandler.postDelayed(new Runnable() {           public void run() {               Result result = retryable.run();               if(result == Result.RETRY) {                   AndroidWatchExecutor.this.postWaitForIdle(retryable, failedAttempts + 1);               }           }       }, delayMillis);   }

什么是 IdleHandler?我们知道 Looper 会不断从 MessageQueue 里取出 Message 并执行。当没有新的 Message 执行时,Looper 进入 Idle 状态时,就会取出 IdleHandler 来执行。

换句话说,IdleHandler就是 优先级别较低的 Message,只有当 Looper 没有消息要处理时才得到处理。而且,内部的 queueIdle() 方法若返回 true,表示该任务一直存活,每次 Looper 进入 Idle 时就执行;反正,如果返回 false,则表示只会执行一次,执行完后丢弃。

那么,这件优先级较低的任务是什么呢?backgroundHandler.postDelayed(runnable, delayMillis);,runnable 就是之前 ensureGone()。

也就是说,当主线程空闲了,没事做了,开始向后台线程发送一个延时消息,告诉后台线程,再过几秒(delayMillis)后开始检查 Activity 是否被回收了。

那么到底是延迟几秒呢?我们去看看是哪个类调用了AndroidWatchExecutor(long initialDelayMillis)方法:

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {  private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);  ...   @Override protected WatchExecutor defaultWatchExecutor() {    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);}

所以,当 Activity 发生 destory 后,首先要等到主线程空闲,然后再延时5秒,才开始执行泄漏检查。

从上面的代码可以学习的知识点

1、如何创建一个优先级低的主线程任务,它只会在主线程空闲时才执行,不会影响到 app 的性能?

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {      @Override public boolean queueIdle() {        // do task        return false; // only once      }    });

2、如何快速创建一个主/子线程 handler?

// 主线程handlermainHandler = new Handler(Looper.getMainLooper());// 子线程handlerHandlerThread handlerThread = new HandlerThread(“子线程任务”);handlerThread.start();Handler backgroundHandler = new Handler(handlerThread.getLooper());

3、如何快速判断当前是否运行在主线程?

Looper.getMainLooper().getThread() == Thread.currentThread();

System.gc() 可以触发立即 gc 吗?如果不行那怎么才能触发即时 gc 呢?

在 LeakCanary 里,需要立即触发 gc,并在之后立即判断弱引用是否被回收。这意味着该 gc 必须能够立即同步执行。

常用的触发 gc 方法是 System.gc(),那它能达到我们的要求吗?

我们来看下其实现方式:

/** * Indicates to the VM that it would be a good time to run the * garbage collector. Note that this is a hint only. There is no guarantee * that the garbage collector will actually be run. */public static void gc() {    boolean shouldRunGC;    synchronized(lock) {        shouldRunGC = justRanFinalization;        if (shouldRunGC) {            justRanFinalization = false;        } else {            runGC = true;        }    }    if (shouldRunGC) {        Runtime.getRuntime().gc();    }}

“There is no guarantee that the garbage collector will actually be run.”

从代码也能看出,必须先判断 shouldRunGC 才能决定是否真的要 gc。

那要怎么实现 即时 GC 呢?

看看LeakCanary是怎么写的:

public interface GcTrigger {    GcTrigger DEFAULT = new GcTrigger() {        public void runGc() {            Runtime.getRuntime().gc();            this.enqueueReferences();            System.runFinalization();        }        private void enqueueReferences() {            try {                Thread.sleep(100L);            } catch (InterruptedException var2) {                throw new AssertionError();            }        }    };    void runGc();}

怎样忽略某些已知泄漏的类或 Activity

LeakCanary 提供了 ExcludedRefs 类,可以向里面添加某些主动忽略的类。比如已知 Android 源代码里有某些内存泄漏,不属于我们 App 的泄漏,那么就可以 exclude 掉。

另外,如果不想监控某些特殊的 Activity,那么可以在 onActivityDestroyed(Activity activity) 里,过滤掉特殊的 Activity,只对其它 Activity 调用 refWatcher.watch(activity) 监控。

到此本博文就结束了,欢迎指教。

原创粉丝点击