Android进阶7:内存优化——LeakCanary原理分析

好的项目离不开健壮的代码,对于想要写出健壮的代码,解决内存泄漏是必须的。

对于LeakCanary,对于大多人是不陌生的,也就是检测内存泄漏的工具。可能在代码中我们会这样引入LeakCanary:

        //检查leakCanary和APP是否在同一个进程,如果是同一个进程就返回,不在同一个进程,就注册。
        //因为不再同一个进程,不会对APP进程造成消极影响(如:APP进程变慢或者out of memory)
        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);

简简单单的两句话,背后的原理却是一大堆。
在开始源码之前,先说几个知识点:

  1. 强引用,软引用,弱引用,GC线程扫描它所管辖的内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
  2. 垃圾回收器是一个优先级很低的线程,即使有弱引用的存在,也不一定会执行。
  3. 我们手动调用GC,不一定成功调用垃圾回收器,因为我们仅仅是建议JVM执行GC,最终执不执行,还是得看JVM的最终决策。
  4. LeakCanary是在主进程进行内存检测,并且是在主进程空闲的时候进行的。 这就是为什么会出现我们关闭Activity了,过了一段时间,才显示内存泄漏了。
  5. Android4.0之后,新加了ActivityLifecycleCallbacks,作用就是,同一进程下,每个Activity的的生命周期都会进入到ActivityLifecycleCallbacks回调的相应方法中。

以上知识点是我们需要知道的。接下来看源码:

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

方法返回的是RefWatcher ,RefWatcher 是分析内存泄露的核心类,里面提供了相应的方法用户分析内存泄漏。其中refWatcher(application)返回的是AndroidRefWatcherBuilder对象,listenerServiceClass方法里面的DisplayLeakService参数是:通知给开发者显示内存泄漏的详细信息,其继承自IntentService, 不理解的可以看这个:
Android进阶2:线程和线程池(3)—— IntentService原理解析
然后就是链式调用了excludedRefs方法,作用是忽略系统底层的内存泄漏,也就是说不是我们开发者造成的内存泄漏。最后调用了buildAndInstall方法,这个方法最终返回的是RefWatcher对象:

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

执行ActivityRefWatcher.install,看下:

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

调用了watchActivities(), 继续深究:

  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    //给Application注册监听事件;
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

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

到这里我们就明白了原来就是LeakCanary内部实际上用的也就是ActivityLifecycleCallbacks,那么,想一下lifecycleCallbacks对象的实现,既然我们需要检测内存泄漏,那么肯定是在Activity ‘销毁’ 之后,才能够检测的,那么名义上的销毁,调用的方法也就是onActivityDestroyed,所以我们猜想LeakCanary的检测内存泄漏的逻辑是写在onActivityDestroyed中的。
接下来看下源码,验证一下:

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        @Override public void onActivityStarted(Activity activity) {
        }

        @Override public void onActivityResumed(Activity activity) {
        }

        @Override public void onActivityPaused(Activity activity) {
        }

        @Override public void onActivityStopped(Activity activity) {
        }

        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

	//检测内存泄漏逻辑
        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

通过源码看,和我们的猜想一样的,检测内存泄漏的逻辑确实是在onActivityDestroyed方法中的。
继续,看下onActivityDestroyed方法,注意传递的参数是当前销毁的Activity对象。其最终调用的还是RefWatcher的watch方法,

   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中
    retainedKeys.add(key);
    //将Activity和唯一标识以及ReferenceQueue作为参数,生成弱引用KeyedWeakReference 
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //开始内存泄漏检测
    ensureGoneAsync(watchStartNanoTime, reference);
  }

watchedReference 也就是我们传入的Activity对象,上面源码主要是先生成了唯一标识key,然后将Activity和唯一标识以及ReferenceQueue作为参数生成弱引用对象reference 。ReferenceQueue的作用是:在适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。后面代码里使用一个弱引用连接到你需要检测的对象,然后使用ReferenceQueue来监测这个弱引用可达性的改变;说白了ReferenceQueue就是一个弱引用队列。然后垃圾回收器会自动的回收此队列中的对象。KeyedWeakReference 本质也就是将我们的Activity封装了一层,变成了唯一的一个弱引用对象。

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    //开启子线程进行内存分析
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

可以看出,实际上LeakCanary分析内存是在子线程中的。


  @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();
    //是否处于debugger状态,如果是就返回
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 看是否此弱引用对象还存在
    if (gone(reference)) {
    	//如果不存在,就返回,表明没有内存泄漏
      return DONE;
    }
    //如果还存在,就手动调用GC,
    gcTrigger.runGc();
    //再次删除弱引用
    removeWeaklyReachableReferences();
    //再次判断此弱引用对象是否存在
    if (!gone(reference)) {
    	//如果还存在,就分析Dump内存快照。
      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);
      //分析analyze方法;
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

从上述代码可以看出,LeakCanary检测是否内存泄漏的步骤是:

  1. 先删除弱引用
  2. 判断弱引用是否还存在,如果不存在,说明已经没有内存泄漏;如果存在,就执行3步骤。
  3. 手动调用GC ,然后再次删除弱引用
  4. 再次判断弱引用是否还存在,如果不存在,就表明没有内存泄漏;如果存在,就开始深层次的分析,这里还是疑似内存泄漏,因为手动触发GC,仅仅是建议JVM触发垃圾回收,真正执不执行还是得看JVM。

注意这里判断弱引用是否存在的标识就是:上述源码生成的唯一标识key。

最后如果进行到第四步骤,也就是深度分析,此时升读分析的也就是Hprof文件。 这里的heapdumpListener也就是ServiceHeapDumpListener对象。关于为什么是ServiceHeapDumpListener对象,可以看下上面源码的install方法内的listenerServiceClass方法:

  public AndroidRefWatcherBuilder listenerServiceClass(
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

继续深入:

  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    //单独开一个进程,开始深度分析
    //HeapAnalyzerService类的注释:此服务在单独的进程中运行,以避免放慢应用程序进程或使其运行
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

也就是说heapdumpListener.analyze最终调用的也就是ServiceHeapDumpListener的analyze方法。HeapAnalyzerService继承自IntentService, runAnalysis方法的内部做的也就是开启Service(也就是它自己),那么我们知道对于IntentService,暴露了onHandleIntent抽象方法,共开发者处理相关的业务逻辑,所以看下onHandleIntent内部的代码:

 @Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
   //创建一个堆分析对象
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
   //然后调用checkForLeak方法分析,并返回分析结果
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

上述代码流程是先创建了一个堆分析对象,然后开始分析,最后返回分析结果,并调用AbstractAnalysisResultService.sendResultToListener发送结果,展示给开发者。值得注意的是listenerClassName参数也就是上面源码的DisplayLeakService对象,然后在AbstractAnalysisResultService的sendResultToListener方法内,通过反射获取DisplayLeakService对象:

  public static void sendResultToListener(Context context, String listenerServiceClassName,
      HeapDump heapDump, AnalysisResult result) {
    Class<?> listenerServiceClass;
    try {
    //通过反射获取ServiceHeapDumpListener对象
      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);
  }

通过反射获取到DisplayLeakService对象,然后开启自己,

 @Override protected final void onHandleIntent(Intent intent) {
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
    try {
    //最终调用的还是DisplayLeakService的方法。
      onHeapAnalyzed(heapDump, result);
    } finally {
      //noinspection ResultOfMethodCallIgnored
      heapDump.heapDumpFile.delete();
    }
  }

看下DisplayLeakService的onHeapAnalyzed方法:

 @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    .......
    //展示通知
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }

至此LeakCanary流程分析大致完成了。

接下来说下开发中对于ActivityLifecycleCallbacks的使用,在实际开发中,常常会需要埋点,我们项目中用的是友盟,后来公司运营需要一套自己的经营分析系统,也就是说友盟一套埋点数据,我们自己一套埋点数据,埋点需求:点击,页面停留时长等,这时候使用ActivityLifecycleCallbacks监听Activity的状态,就非常方便了。

如有错误,欢迎指正。

Android学习交流群:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lmq121210/article/details/85204165