LeakCanary解析

github地址:LeakCanary

使用很简单
In your build.gradle

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

In your Application class:

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);
    // Normal app init code...
  }
}

检查内存泄露的流程
由于在Android api 14的以上,才提供ActivityLifecycleCallbacks类,所以在14以下可以通过在Activity的onDestory方法中,调用ReWatch对象的watch方法来实现内存检查。

 ((ExampleApplication)getApplicationInfo()).getRefWatcher().watch(this);

简单说下流程

  • 通过监听activity 的生命周期。
  • 创建关联activity的KeyedWeakReference和ReferenceQueue对象,并且创建一个集合retainedKeys,该集合用来中保存当前activity的key值(key值是指区分每个activity的标识符),判断当前activity是否发生泄漏的关键,KeyedWeakReference,ReferenceQueue这俩个对象的主要作用是当activity销毁时,相关连的KeyedWeakReference引用也将消失,那么在ReferenceQueue中可以找到activity的引用对象。然后在retainedKeys中移除该activity,如果在retainedKeys中找不到当前activity的软引用对象,说明有内存泄露,然后通过分析hprof 文件 。
  • 分析完成之后发送通知到通知栏,
    下面分析下具体的实现:
  public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

通过链式调用,refWatcher(application) 方法返回一个AndroidRefWatcherBuilder对象。

 public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

AndroidRefWatcherBuilder ,是RefWatcherBuilder的子类,再看listenerServiceClass(DisplayLeakService.class),

 /**
   * 设置一个自定义的AbstractAnalysisResultService 去监听分析结果,
   * Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
   * overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
   */
  public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
      @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    //listenerServiceClass 是不是DisplayLeakService 的子类或者接口
    enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

enableDisplayLeakActivity表示是否默认显示泄露的activity,ServiceHeapDumpListener用来分析生成的hprof文件。excludedRefs(AndroidExcludedRefs.createAppDefaults().build()),排除掉Android系统层面导致的引用,这里有一些是由于厂商导致的,还有一些事系统本身就存在的,这个我们不用管的泄露问题。
最后再来看看 buildAndInstall()方法,

  /**
   * 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() {
    //表示buildAndInstall 这个方法只能调用一次,因为下面已经重新赋值了的,
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //构建RefWatcher
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      //开启一个组件,(动态注册一个DisplayLeakActivity)
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      //将activity的生命周期和refWatcher对象关联起来
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }

    }
    //重新赋值
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

主要看构建 build()方法

 /** Creates a {@link RefWatcher}. */
  public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }

//排除可以忽略的泄漏路径
    if (heapDumpBuilder.excludedRefs == null) {
      heapDumpBuilder.excludedRefs(defaultExcludedRefs());
    }
//解析完 hprof 文件,进行回调,并通知 DisplayLeakService 弹出提醒
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
//判断是否处于调试模式,调试模式中不会进行内存泄漏检测
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
//dump 内存泄漏处的 heap 信息,写入 hprof 文件
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }

//线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }
// 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    if (heapDumpBuilder.reachabilityInspectorClasses == null) {
      heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
    }

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

将RefWatcher 和activity关联起来ActivityRefWatcher.install(context, refWatcher);
看看具体的实现

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

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

可以看到在生命周期onActivityDestroyed 时,回调refwatcher调用watch方法。查看watch方法


  /**
   * Identical to {@link #watch(Object, String)} with an empty string reference name.
   *
   * @see #watch(Object, String)
   */
  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();
    //保证key 的唯一性
    String key = UUID.randomUUID().toString();
    //添加到set集合
    retainedKeys.add(key);
    //创建KeyedWeakReference对象
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
  }

这就是前面说的KeyedWeakReference和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);
      }
    });
  }

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  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;
    }
      // 判断 reference,即 activity 是否内回收了,若被回收了,直接返回
    if (gone(reference)) {
      return DONE;
    }
      // 调用 gc 方法进行垃圾回收
    gcTrigger.runGc();

      // 移除已经被回收的引用
    removeWeaklyReachableReferences();
      // activity 还没有被回收,证明发生内存泄露
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
        // dump heap,并生成相应的 hprof 文件
      File heapDumpFile = heapDumper.dumpHeap();
        // dump the heap 失败的时候
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

这里的 watchExecutor是在AndroidRefWatcherBuilder生产的。

  @Override protected @NonNull WatchExecutor defaultWatchExecutor() {
    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  }

具体的实现execute()方法

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

最后发现都是调用的postToBackgroundWithDelay()这个方法,postToBackgroundWithDelay()这个方法是一个根据返回结果判断是否重复执行的方法,类似递归。

 // 取 Math.pow(2, failedAttempts), maxBackoffFactor 的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,
    // 第一次执行的时候 failedAttempts 是 0 ,所以 exponentialBackoffFactor 是1
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    // initialDelayMillis 的默认值是 5
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    Log.e("info","exponentialBackoffFactor-->"+exponentialBackoffFactor+"delayMillis-->"+delayMillis);
    // 所以第一次延迟执行的时候是 5s,若
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          // 过 result == RETRY,再次调用 postWaitForIdle,下一次的 delayMillis= 上一次的  delayMillis *2;
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

看下retryable.run()方法 。执行的正是ensureGone(reference, watchStartNanoTime)方法。判断其返回结果,看是否需要重复执行postWaitForIdle(retryable, failedAttempts + 1)。ensureGone()方法的执行原理就是整个leakcarany方法的检查泄漏的主要逻辑点,首先检查将ReferenceQueue˙中的软引用移除,再调用gc方法进行垃圾回收,在此将ReferenceQueue的软引用移除,如果还存在无法销毁的引用,那证明会导致内存泄漏。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343