当前位置: 首页 > 工具软件 > LeakCanary > 使用案例 >

LeakCanary 原理分析

巫马劲
2023-12-01

LeakCanary 以1.5版本为例子,简单分析其中的原理。 LeakCanary 可以检测App的内存泄漏,在我们自定义的 Application 的 onCreate() 方法中执行  LeakCanary.install(this); 这行代码即可。代码很简单,我们看看它做了什么

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

这个方法前两行是作一些参数配置,重点看 buildAndInstall() 方法

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

通过  build() 创建一个 RefWatcher 对象,这是个build模式,看看它的代码

 public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }

    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      excludedRefs = defaultExcludedRefs();
    }

    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }

    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }

    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }

    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }

    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }

其中 DebuggerControl 对应 AndroidDebuggerControl,HeapDumper 对应 AndroidHeapDumper,WatchExecutor 对应 AndroidWatchExecutor,GcTrigger 对应它内部的DEFAULT 对象,这几个类都比较简单,但比较有意思。

第一个就不说了,看 AndroidHeapDumper,这里面有几个知识点:FutureResult 的成员变量 AtomicReference 和 CountDownLatch,这是为了并发而准备的,CountDownLatch 是一个线程等待其他线程各自执行完毕后再执行本身,AtomicReference 则是保证原子性的安全;在showToast()方法中设置toast时,这里用了Looper队列MessageQueue的 addIdleHandler() 方法,它是UI空闲时执行,返回值为false则表示执行一次,true则是UI一旦空闲,就执行,没次数限制。
AndroidWatchExecutor 类是个执行功能类,它的构造方法中创建一个 HandlerThread 的子线程,然后把它对应的Looper作为参数,传入Handler的构造方法中,此时这个Handler就一直是在子线程中执行各种逻辑了,它里面通过代码执行延迟操作。 GcTrigger 这个里面执行的是GC操作, Runtime.getRuntime().gc() 比着 System.gc() 的优先级要高,所以优先使用这个。

继续回到 buildAndInstall() 方法,LeakCanary.enableDisplayLeakActivity(context) 的意思是打开显示内存泄漏的页面,这个页面把泄漏的数据以列表的形式展示出来;ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher) 这行代码则是对Activity执行监听的入口,看看对应的方法

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

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

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


  
首先是个版本判断,要大于或等于14版本,这里其实就是给 application 注册一个Activity的生命周期回调,注册之前先取消之前的,防止重复注册,看看 lifecycleCallbacks 的方法

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        ...
        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

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

在Activity销毁时,执行了 refWatcher.watch(activity),看看它里面的代码

  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

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

通过 randomUUID() 方法创建随机的字符串,然后把它存入set中;KeyedWeakReference 是个然引用,这里面传入的也有 ReferenceQueue 队列,看到这里,是不是很熟悉,Glide 的图片缓存中的软引用缓存,也是用的这种技术。看到这里,了解软引用和队列知识点的童鞋,就可以猜测出下面的步骤了

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

对象的引用,如果强引用还有链接,则软引用中对象也会一直存在,如果强引用的链接都被切断了,那么软引用中很容易就被gc回收了,并且会把它放入传入软引用构造中的队列里面,所以 removeWeaklyReachableReferences() 的意思就是检查队列中是否有对象,如果有,说明正常回收,没有内存泄漏,因为此时 Activity 是执行了onDestroy() 方法,如果有内存泄漏,它被别的对象持有,那么它是无法释放资源。 gone() 方法是判断key是否在set中,还是判断Activity是否被回收,如果没被回收,为了保险,这里执行了 gcTrigger.runGc(),也就是主动触发 gc 回收机制,睡眠100毫秒,重新执行 removeWeaklyReachableReferences() 和 gone() 方法,如果此时 Activity 还没被回收,那就说明是内存泄漏了,heapDumper 是 AndroidHeapDumper,在它里面去读取对象的引用路径等信息。读取分析数据用的是 haha 三方库。


在 LeakCanary 1.6 版本中,有对 Fragment 的监听:在手机系统O版本以上,Fragment功能中也新增了对它的声明周期监听回调,

  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

AndroidOFragmentRefWatcher:

  private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }

        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

  @Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }

这里是根据声明周期,把 rootview 和 fragment 添加到 RefWatcher 的监听方法中,关键就是 watchFragments(Activity activity) 方法是如何触发的,继续看 FragmentRefWatcher,创建了 Helper 对象,把 fragmentRefWatchers 作为参数传入构造方法中,用 ActivityLifecycleCallbacks 包裹了一层,ActivityLifecycleCallbacks 被添加到Application 的回调中,activity销毁时执行 ActivityLifecycleCallbacks 的 onActivityCreated() 方法,此时就触发了 AndroidOFragmentRefWatcher 的 watchFragments()方法。
 

如果我们想对其他类型的对象进行内存泄漏检测怎么办?Application中的 LeakCanary.install(this) 返回的是 RefWatcher 对象,此时可以做一个单例的Application对象,对外暴露 RefWatcher,这样其他地方就可以直接使用 RefWatcher 的 watch(Object watchedReference) 方法了,但这里需要自己控制时机,因为这里不会根据Activity的声明周期来触发。

LeakCanary 的2.0版本,做了部分修改,使用它时不需要手动LeakCanary.install(this)初始化了,应为它自己做了。里面有个继承 ContentProvider 的内容观察者,观察者的初始化方法居然比 Application 的初始化方法还要早;用了新的内存解析库代替了haha库,极大的减少了内存占用,提高了n被的速度。

 

 类似资料: