LeakCanary源码解析之检测篇

源码分析基于:1.6.3

对于Android开发者而言,内存泄漏是一种很常见的问题。LeakCanary就是捕获内存泄漏的一把利器。我们在这里就分析一下它的工作原理。

 

一、使用方法

使用方法就是我们在Application中添加代码:

if (LeakCanary.isInAnalyzerProcess(this)) {

            // This process is dedicated to LeakCanary for heap analysis.

            // You should not onStart your app in this process.

            return;

        }

        LeakCanary.install(this);

 

 

看到这些估计你也会和我一样,这样我们就能捕获到内存泄露了么?不禁会产生这样的疑问。我们就带着疑问阅读一下源码寻找答案吧。

 

二、内存泄漏的检测过程:

LeakCanary这个类是一个工具类,我们先看一下LeakCanary.install(this)这个方法的源码:


/**

   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity

   * references (on ICS+).

   */

  public static @NonNull RefWatcher install(@NonNull Application application) {

    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)

        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())

        .buildAndInstall();

  }

 

该方法中refWatcher(application)方法获取AndroidRefWatcherBuilder实例对象;“.listenerServiceClass(DisplayLeakService.class)”方法是对内存泄漏分析结果的监听;“.buildAndInstall()”方法用于生成RefWatcher对象,关于该方法的详细代码如下:


  /**

   * Creates a {@link RefWatcher} instance and makes it available through {@link

   * LeakCanary#installedRefWatcher()}.

   *

   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.

   *

   * @throws UnsupportedOperationException if called more than once per Android process.

   */

  public @NonNull RefWatcher buildAndInstall() {

  //保证RefWatcher的唯一性

    if (LeakCanaryInternals.installedRefWatcher != null) {

      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");

    }

//如果没有创建过RefWatcher,则创建一个;

    RefWatcher refWatcher = build();

//非空置对象(内存分析相关工具对象都是空壳对象)

    if (refWatcher != DISABLED) {

//是否允许使用内置展示页面展示内存泄露信息

      if (enableDisplayLeakActivity) {

        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);

      }

  //是否允许监测activity

      if (watchActivities) {

        ActivityRefWatcher.install(context, refWatcher);

      }

  //是否允许检测fragment

      if (watchFragments) {

        FragmentRefWatcher.Helper.install(context, refWatcher);

      }

    }

//RefWatcher对象赋值

    LeakCanaryInternals.installedRefWatcher = refWatcher;

    return refWatcher;

  }

 

在这里RefWatcher就是内存泄漏的监控器。该方法就是生成RefWatcher对象,该框架默认允许检测Activity和Fragment的内存泄漏,那么它又是怎么监控的呢?原理由是啥?以什么为判断内存泄漏的标准呢?

我们看一下ActivityRefWatcherinstall()方法的逻辑:


  public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {

    Application application = (Application) context.getApplicationContext();

    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

//Application注册生命周期回调

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);

  }

 

 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =

      new ActivityLifecycleCallbacksAdapter() {

        @Override public void onActivityDestroyed(Activity activity) {

          refWatcher.watch(activity);

        }

      };

 

lifecycleCallbacks是该类中的成员变量,通过注册生命周期回调RefWatcher就可以通过页面销毁的方法回调触发RefWatcherwatch()方法。我们先不管watch()方法中的具体逻辑,下面在分析一下fragment中的监测机制,下面我们分析一下FragmentRefWatcher.Helper这个内部类的源码:

  final class Helper {

 

    private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =

        "com.squareup.leakcanary.internal.SupportFragmentRefWatcher";

 

    public static void install(Context context, RefWatcher refWatcher) {

      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

 

  //大于26的Android版本使用AndroidOFragmentRefWatcher

      if (SDK_INT >= O) {

        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));

      }

 

  //否则使用SupportFragmentRefWatcher

      try {

        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);

        Constructor<?> constructor =

            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);

        FragmentRefWatcher supportFragmentRefWatcher =

            (FragmentRefWatcher) constructor.newInstance(refWatcher);

        fragmentRefWatchers.add(supportFragmentRefWatcher);

      } catch (Exception ignored) {

      }

 

      if (fragmentRefWatchers.size() == 0) {

        return;

      }

 

      Helper helper = new Helper(fragmentRefWatchers);

 

      Application application = (Application) context.getApplicationContext();

  //注册生命周期回调

      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);

    }

 

    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =

        new ActivityLifecycleCallbacksAdapter() {

          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            for (FragmentRefWatcher watcher : fragmentRefWatchers) {

//对应的FragmentManager注册回调

              watcher.watchFragments(activity);

            }

          }

        };

 

    private final List<FragmentRefWatcher> fragmentRefWatchers;

 

    private Helper(List<FragmentRefWatcher> fragmentRefWatchers) {

      this.fragmentRefWatchers = fragmentRefWatchers;

    }

  }

 

该类中主要就是通过Activity的生命周期回调的onActivityCreated()方法,来获取activity实例对象,具体需要分析watcher.watchFragments(activity)中的逻辑,由于存在两种fragment对应的watcher,我们在这里只分析SupportFragmentRefWatcher中对应的代码逻辑。


  @Override public void watchFragments(Activity activity) {

    if (activity instanceof FragmentActivity) {

      FragmentManager supportFragmentManager =

          ((FragmentActivity) activity).getSupportFragmentManager();

      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);

    }

  }

 

方法中获取FragmentManager实例对象,并注册生命周期方法。对于Android版本大于26的,请阅读AndroidOFragmentRefWatcher中的相关源码,原理是一致的。通过上面的源码我们分析得知,对于Activity而言,是在activityonDestory()方法内进行监测的;对于fragment而言,在生命周期回调方法内的onFragmentViewDestroyed()onFragmentDestroyed()方法内进行监测,在这里onFragmentViewDestroyed()方法对应于Fragment的onDestroyView()方法,onFragmentDestroyed()方法对应于Fragment#onDestroy()方法。

 

我们知道了LeakCanary的监测位置了,我们下面就通过源码分析一下它是怎么做到监测内存泄漏的。下面是RefWatcher类中的watch()方法的源码:


/**

   * 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();

//生成随机的UUID作为该引用对象的key值

    String key = UUID.randomUUID().toString();

//添加的集合中

    retainedKeys.add(key);

//保存引用信息

    final KeyedWeakReference reference =

        new KeyedWeakReference(watchedReference, key, referenceName, queue);

 

//判断引用是否存在

    ensureGoneAsync(watchStartNanoTime, reference);

  }

 

在该方法中,将之前生命周期中的方法中拿到的activity对象实例或者fragment对象实例进行保存并进行相关的处理,在保存时会生成一个唯一的key,后面会通过key值进行相应的业务逻辑。下面我们看一下ensureGoneAsync()方法相关的逻辑,源码如下:


private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

    watchExecutor.execute(new Retryable() {

      @Override public Retryable.Result run() {

        return ensureGone(reference, watchStartNanoTime);

      }

    });

  }

 

该方法中watchExecutorAndroidWatchExecutor的一个具体实例对象,最终是通过该实现类的execute()方法去执行引用相关的逻辑,这部分后面会分析。同时需要注意在excute()方法的内部类中run()方法会通过ensureGone()方法返回Retryable.Result的值。这个值会在后面的分析中用到。我们在这里先看一下AndroidWatchExecutor的具体实现。AndroidWatchExecutor是WatchExecutor接口的具体实现类,该接口主要用来执行Rertyable对象,并根据需要进行重试操作。我们看一下AndroidWatchExecutor的源码:


/**

 * {@link WatchExecutor} suitable for watching Android reference leaks. This executor waits for the

 * main thread to be idle then posts to a serial background thread with the delay specified by

 * {@link AndroidRefWatcherBuilder#watchDelay(long, TimeUnit)}.

 */

 

public final class AndroidWatchExecutor implements WatchExecutor {

 

  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";

 

  private final Handler mainHandler;

  private final Handler backgroundHandler;

  private final long initialDelayMillis;

  private final long maxBackoffFactor;

 

  public AndroidWatchExecutor(long initialDelayMillis) {

    mainHandler = new Handler(Looper.getMainLooper());

    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);

    handlerThread.start();

    backgroundHandler = new Handler(handlerThread.getLooper());

    this.initialDelayMillis = initialDelayMillis;

    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

  }

 

  @Override public void execute(@NonNull Retryable retryable) {

  //当前线程是否为主线程

    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

      waitForIdle(retryable, 0);

    } else {

      postWaitForIdle(retryable, 0);

    }

  }

 

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

    mainHandler.post(new Runnable() {

      @Override public void run() {

        waitForIdle(retryable, failedAttempts);

      }

    });

  }

 

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {

    // This needs to be called from the main thread.

//主线程中进行调用,当主线程的消息队列空闲时调用

    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

      @Override public boolean queueIdle() {

  //转移到后台线程进行

        postToBackgroundWithDelay(retryable, failedAttempts);

        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() {

        Retryable.Result result = retryable.run();

        if (result == RETRY) {

          postWaitForIdle(retryable, failedAttempts + 1);

        }

      }

    }, delayMillis);

  }

}

 

AndroidWatchExecutor 是Android平台中内存泄漏的监测工具类。该类的主要方法就是execute()方法,其主要逻辑是判断当前正在执行的线程是否为主线程,如果是主线程,则通过IdleHandler监测主线程是否空闲,当空闲的时候,执行postToBackgroundWithDelay方法,在此方法中,先通过计算exponentialBackoffFactor(补偿因子),并通过该因子计算出需要延迟的时间(delayMillis ),说实话为什么这么算没想明白。最后通过backgroundHandler延迟执行一条线程去获取Retryable.Result的结果,通过是否需要重试也就是“result == RETRY”来判断是否需要继续postWaitForIdle方法,同时在重试的时候failedAttempts参数会加1。那么我们不禁会思考,retryable.run()的结果,也就是Retryable.Result是在什么时候改变的呢?这个我们在RefWatcher中的ensureGoneAsync()方法的分析中提到,result的值是在ensureGone()方法返回的。好了就是那么清晰。当正在执行的线程非主线程的的时候postWaitForIdle()方法被执行,这里就不再说明了。

 

下面我们看一下RefWatcher中ensureGone()方法的源码:


  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.

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

    //GC开始检测时间

    long gcStartNanoTime = System.nanoTime();

    //从开始watch到GC开始检测的时间差

    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

 

    //移除已经被GC的引用信息

    removeWeaklyReachableReferences();

 

    //如果调试模式,则后续重试

    if (debuggerControl.isDebuggerAttached()) {

      // The debugger can create false leaks.

      return RETRY;

    }

 

    //如果不包含应用信息,则表示监测已经完成,没有发生泄漏;

    if (gone(reference)) {

      return DONE;

    }

    //触发GC

    gcTrigger.runGc();

    //再次确认移除已经被GC的引用信息

    removeWeaklyReachableReferences();

    //如果队列中还存在引用信息,

    if (!gone(reference)) {

      long startDumpHeap = System.nanoTime();

      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

 

      //使用Dumper类dump当前堆内存中的对象信息

      File heapDumpFile = heapDumper.dumpHeap();

      if (heapDumpFile == RETRY_LATER) {

        // Could not dump the heap.

        return RETRY;

      }

      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

 

      //将hprof文件和reference引用信息构造HeapDump对象

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)

          .referenceName(reference.name)

          .watchDurationMs(watchDurationMs)

          .gcDurationMs(gcDurationMs)

          .heapDumpDurationMs(heapDumpDurationMs)

          .build();

      //分析dump信息

      heapdumpListener.analyze(heapDump);

    }

    return DONE;

  }

 

要理解LeackCanary是怎么定义内存泄漏的呢?此方法就是关键。同时不了解Java中的弱引用,强引用的概念的可以查阅一下这方面的资料,这里就不在进行过多的解释了。首先该方法先调用了removeWeaklyReachableReferences()方法,该方法就是移除已经被GC的引用信息,在该方法里就是看queue队列中是否还存在引用的信息KeyedWeakReference,如果存在就从retainedKeys中进行移除。在RefWatcher类中的watch()方法中已经将观察对象watchReference对应的key保存在retainedKeys中,同时关联到了ReferenceQueue的实例对象queue。当弱引用持有的对象被GC之后,与之关联的引用信息KeyedWeakReference的对象reference就会被添加到关联的队列中queue.所以可以通过queue中是否存在对应的引用信息来判断是否发生内存泄漏。下面说一下为什么在开启Debug模式下需要返回RETRY,那是因为在该模式下对象引用的时间会变长。我们继续向下分析,gone(refreence)方法就是看retainedKeys是否包含相应的引用的信息,如果不存在则说明就完成了GC,本次检测也告一段落。否则, 通过gcTrigger.runGc()手动触发一次GC,并在此通过removeWeaklyReachableReferences()方法进行引用信息的移除。如果还存在该引用信息那么说明app当前发生了内存泄漏。后续就是通过heapDumper.dumpHeap()将当前堆内存中的信息保存下来,并通过heapdumpListener.analyze(heapDump)分析获取的dump信息。到此,相信大家就清楚了我们Android开发中内存泄漏检测神器LeakCanary的检测原理了吧。

 

后续将在下一篇文章中分析该框架是怎么分析内存泄漏的,敬请期待~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心灵行者

你的鼓励是我最大的创作动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值