LeakCanary源码解析

网上LeakCanary源码解析的很多,此篇文章不粘贴满屏的代码,只从个人理解角度去选择性的展示核心代码来阐述LeakCanary的原理

一、监控Activity的销毁

LeakCanary既然要监控内存泄露,那么肯定需要一个时机来监控Activity执行了destroy方法,进而再继续观察是否被正常回收,那么Activity销毁是如何得知的呢?Application.ActivityLifecycleCallbacks

//该段代码可在ActivityRefWatcher类里找到
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        /** 省略其他无用生命周期的观察 **/
        @Override public void onActivityDestroyed(Activity activity) {
          //当某个activity执行到了destroy方法,那么就开始监控
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

二、Activity执行destroy后的处理

1.准备一个一个弱引用指向当前activity,并为当前activity生成一个随机数key作为身份标识,该标识也传给弱引用
2.准备一个引用队列,将步骤一的弱引用进行关联,当弱引用被收回时,那么该弱引用会自动添加到引用队列里
3.准备一个集合只存步骤一中为activity生成的key,当activity被正常回收时,我们可以遍历引用队列,如果引用队列里有弱引用,我们可以拿到步骤一中传入的key,同时将步骤三的集合里的key也移除掉,如果没有移除掉说明发生了内存泄露

  //该代码在RefWatcher.java
  //存activity对应的随机数
  private final Set<String> retainedKeys;
  //引用队列 若弱引用被收回时,我们通过poll方法可以拿到这个弱引用
  private final ReferenceQueue<Object> queue;

  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);
    //创建一个弱引用 并与引用队列进行关联  KeyedWeakReference其实很简单就继承了WeakReference
    //只是为了能传入一个key
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
  }

三、借助系统自动触发的GC机制去尝试分析是否回收

//该代码在RefWatcher.java
 private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
      	//这个回调是IdleHandler执行的,为啥要通过IdleHandler呢,因为IdleHandler是主线程消息队列空闲的时候
      	//才执行的,这时候大概率是在GC之后执行,这次只是碰下运气,看看回收了没
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

我们可以看下上面的IdleHandler是如何执行的

//AndroidWatchExecutor.java
  void waitForIdle(final Retryable retryable, final int failedAttempts) {
    //往主线程队列里的mIdleHandlers添加事件处理,当消息队列空闲时会回调queueIdle方法
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        //return false意味着这个事件只会被执行一次 执行完毕会从mIdleHandlers移除掉
        return false;
      }
    });
  }
  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    //由主线程切换到子线程,避免在主线程进行分析
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
      	//此处执行了run方法
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

四、GC发生后的泄露分析

//RefWatcher.java
 Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    //IdleHandler回调的第一次尝试,这时候系统可能发生了GC
    removeWeaklyReachableReferences();
    //判断是否回收掉了
    if (gone(reference)) {
      return DONE;
    }
    //走到这里说明activity没有被回收,这里有两种情况,一种是系统没有发生GC因此没被回收,一种是发生GC了没有被回收
    //所以我们需要手动GC(GC后延迟了100ms,目的是为了确保弱引用入引用队列)
    gcTrigger.runGc();
    //再次尝试移除key
    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;
  }

  //通过判断retainedKeys是否有key来判断是否发生了泄露,若有key说明了发生泄露了
  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

  //该方法就是遍历引用队列,如果activity被回收,那么队列里会有弱引用,会拿到对应的key,并从retainedKeys移除
  private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

五、总结

LeakCanary采用了弱引用、引用队列的特性来判断对象是否被回收。对于GC的时机,首先根据IdleHandler的特点来尝试系统的GC,IdleHandler的执行,大概率是系统GC执行完了,若发生没有被回收可能发生了内存泄露也可能系统没有GC过,因此再次手动GC,确保一定发生了GC再去分析内存泄露。

你可能感兴趣的:(安卓,java,开发语言)