全解系列:内存泄漏定位工具LeakCanary!

2020-09-17 14:31:05 浏览数 (1)

本文来自我的同事 梅贤兵 的投稿,分析了内存泄漏定位工具LeakCanary的原理。

在日常开发中,不可避免的会遇到内存泄漏的问题,从而导致App的内存使用紧张,严重的情况还会导致App的卡顿甚至是奔溃,所以需要开发人员解决这些内存泄漏的问题。

要解决内存泄漏的问题,首先就需要定位内存泄漏。这里可以借助Android Studio自带的内存检测工具Profile分析内存。反复进入退出同一个页面,dump一份内存快照,就可以分析出是否有内存泄漏的问题。但这样做的效率比较低,也不够全面,如果开发者忘记检测了,可能就把内存泄漏的问题给忽略掉了。这时候我们自然会想着要是有什么工具,能够自动的帮我们查找内存泄漏就好了,这样的工具自然是有的,这就是我们今天要介绍的主角:LeakCanary。只要项目中集成了这个sdk,LeakCanary就可以自动的帮我们查找内存泄漏。

一、LeakCanary的简单使用

LeakCanary是一个开源的第三方库,可以用于检测内存泄漏,并简单的分析内存泄漏的对象的引用链,帮助开发者定位内存泄漏的问题。

LeakCanary 开源库地址:https://github.com/square/leakcanary

在项目中集成LeakCanary,步骤非常的简单:

1. 在module的build.gradle文件中,依赖LeakCanary:

代码语言:javascript复制
dependencies {
    
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
   // 可选,使用support库中的Fragment时,检测内存泄漏
    debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

com.squareup.leakcanary:leakcanary-support-fragment:1.6.3这个是要支持support库下的Fragment,如果只检测android.app.*包下的Fragment,则可以不用引入这个。

2. 在自定义的Application中,安装LeakCanary:

代码语言:javascript复制
public class LeakApplication extends Application {
    @Override public void onCreate() {
    super.onCreate();
// 判断当前进程是否是Leakcanary专门用于分析heap内存的而创建的那个进程,即HeapAnalyzerService所在的进程,如果是的话,则不进行Application中的初始化功能
    if (LeakCanary.isInAnalyzerProcess(this)) {//1
      return;
    }
    LeakCanary.install(this);
  }
}

注释1:检测当前进程是否是运行:HeapAnalyzerService服务的进程,即分析内存快照的进程。

经过上面两步,LeakCanary就集成成功了,在debug环境中,LeakCanary就可以帮我们检测内存的泄漏。如果检测到某个Activity或者Fragment 有内存泄露,LeakCanary 就会以通知的形式提示我们,点开通知就可以看到对象的引用链了。

如果LeakCanary分析的引用链比较简单,还是无法定位内存泄漏的位置的话,则可以把LeakCanary保存的hprof文件导出来,通过mat工具详细的分析内存泄漏。

LeakCanary 截图

二、LeakCanary原理简单分析:

2-1、LeakCanary原理简述

经过上面的介绍,我们对LeakCanary的使用已经了解了。那么LeakCanary是如何起作用的呢,这里就对LeakCanary的原理做一个简单的阐述,为后面的源码分析做一个理论支持。

LeakCannary 的主要原理,其实很简单,大概可以分为以下几步:

  • (1) 监测Activity 的生命周期的 onDestroy() 的调用。
  • (2) 当某个 Activity 的 onDestroy() 调用后,便对这个 activity 创建一个带 ReferenceQueue 的弱引用,并且给这个弱引用创建了一个 key 保存在 Set集合 中。
  • (3) 如果这个 activity 可以被回收,那么弱引用就会被添加到 ReferenceQueue 中。
  • (4) 等待主线程进入 idle(即空闲)后,通过一次遍历,在 ReferenceQueue 中的弱引用所对应的 key 将从 retainedKeys 中移除,说明其没有内存泄漏。
  • (5) 如果 activity 没有被回收,先强制进行一次 gc,再来检查,如果 key 还存在 retainedKeys 中,说明 activity 不可回收,同时也说明了出现了内存泄漏。
  • (6) 发生内存泄露之后,dump内存快照,分析 hprof 文件,找到泄露路径(使用 haha 库分析),发送到通知栏

2-2、ActivityLifecycleCallbacks使用

在上面LeakCanary的原理简述中,我们知道,第一步就是要监听Activity的生命周期。但我们在安装LeakCanary的时候,就只调用了LeakCanary的install方法,其它啥都没有操作,LeakCanary是如何监听Activity的生命周期的呢?

所以在分析LeakCanary的源码之前,我们先了解一个基础知识:ActivityLifecycleCallbacks。借助ActivityLifecycleCallbacks,就可以全局监听Activity的生命周期。

2-2-1、ActivityLifecycleCallbacks的使用:

ActivityLifecycleCallbacksApplication中声明的一个内部接口,定义如下:

代码语言:javascript复制
public interface ActivityLifecycleCallbacks {
      void onActivityCreated(Activity activity, Bundle savedInstanceState);
      void onActivityStarted(Activity activity);
      void onActivityResumed(Activity activity);
      void onActivityPaused(Activity activity);
      void onActivityStopped(Activity activity);
      void onActivitySaveInstanceState(Activity activity, Bundle outState);
      void onActivityDestroyed(Activity activity);
}

在Application中通过registerActivityLifecycleCallbacks()方法,注册ActivityLifecycleCallbacks回调。这样就可以全局监听Activity的生命周期了。如下:

代码语言:javascript复制
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {

            @Override
            public void onActivityStopped(Activity activity) {
                Log.v("MyApplication", "onActivityStopped");
            }

            @Override
            public void onActivityStarted(Activity activity) {
                Log.v("MyApplication", "onActivityStarted");
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
                Log.v("MyApplication", "onActivitySaveInstanceState");
            }

            @Override
            public void onActivityResumed(Activity activity) {
                Log.v("MyApplication", "onActivityResumed");
            }

            @Override
            public void onActivityPaused(Activity activity) {
                Log.v("MyApplication", "onActivityPaused");
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.v("MyApplication", "onActivityDestroyed");
            }

            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.v("MyApplication", "onActivityCreated");
            }
        });
    }
}

打印日志如下:

代码语言:javascript复制
2020-08-25 20:36:44.553 28323-28323/? V/MyApplication: onActivityPaused
2020-08-25 20:36:44.589 28323-28323/? V/MyApplication: onActivityCreated
2020-08-25 20:36:44.645 28323-28323/? V/MyApplication: onActivityStarted
2020-08-25 20:36:44.649 28323-28323/? V/MyApplication: onActivityResumed
2020-08-25 20:36:45.076 28323-28323/? V/MyApplication: onActivityStopped
2020-08-25 20:36:45.079 28323-28323/? V/MyApplication: onActivitySaveInstanceState
2020-08-25 20:36:47.485 28323-28323/? V/MyApplication: onActivityPaused
2020-08-25 20:36:47.503 28323-28323/? V/MyApplication: onActivityStarted
2020-08-25 20:36:47.506 28323-28323/? V/MyApplication: onActivityResumed
2020-08-25 20:36:47.895 28323-28323/? V/MyApplication: onActivityStopped
2020-08-25 20:36:47.898 28323-28323/? V/MyApplication: onActivityDestroyed

2-2-2、ActivityLifecycleCallbacks源码分析:

下面就看看源码,看看注册的这个回调是在什么时机调用的。Application的registerActivityLifecycleCallbacks()方法:

代码语言:javascript复制
public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
    synchronized (mActivityLifecycleCallbacks) {
        mActivityLifecycleCallbacks.add(callback);
    }
}

这里把ActivityLifecycleCallbacks回调放到了一个集合当中。

既然是分析Activity的生命周期,那么就进入Activity的源码看看生命周期方法,有没有回调我们注册的监听。ActivityonCreate源码如下:

代码语言:javascript复制
protected void onCreate(@Nullable Bundle savedInstanceState) {
    if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate "   this   ": "   savedInstanceState);

    ..............................................
    mFragments.dispatchCreate();
    dispatchActivityCreated(savedInstanceState);
    if (mVoiceInteractor != null) {
        mVoiceInteractor.attachActivity(this);
    }
    mRestoredFromBundle = savedInstanceState != null;
    mCalled = true;

}

ActivityonCreate方法,又执行了dispatchActivityCreated方法:

代码语言:javascript复制
private void dispatchActivityCreated(@Nullable Bundle savedInstanceState) {
  // 获取Application对象,执行dispatchActivityCreated方法
    getApplication().dispatchActivityCreated(this, savedInstanceState);
    Object[] callbacks = collectActivityLifecycleCallbacks();
    if (callbacks != null) {
        for (int i = 0; i < callbacks.length; i  ) {
            ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(this,
                    savedInstanceState);
        }
    }
}

在这个方法中可以看出,在dispatchActivityCreated方法中,Activity调用了ApplicationdispatchActivityCreated方法:

代码语言:javascript复制
void dispatchActivityCreated(@NonNull Activity activity,
        @Nullable Bundle savedInstanceState) {
    Object[] callbacks = collectActivityLifecycleCallbacks();// 获取所有注册的ActivityLifecycleCallbacks
    if (callbacks != null) {
        for (int i=0; i<callbacks.length; i  ) {
            ((ActivityLifecycleCallbacks)callbacks[i]).onActivityCreated(activity,
                    savedInstanceState);// 回调onActivityCreated方法
        }
    }
}

private Object[] collectActivityLifecycleCallbacks() {
    Object[] callbacks = null;
    synchronized (mActivityLifecycleCallbacks) {
      if (mActivityLifecycleCallbacks.size() > 0) {
        callbacks = mActivityLifecycleCallbacks.toArray();
      }
    }
    return callbacks;
}

从这里看出,最终就会回调到注册的ActivityLifecycleCallbacksonActivityCreated方法。其它生命周期的方法回调与onCreate方法类似,这里就不一一分析了。

了解了这些基本知识之后,下面就从LeakCanary的install()方法为入口,看看LeakCanary具体是怎么检测到内存泄漏并弹出通知的。

三、LeakCanary源码分析:

对LeakCanary的源码分析,我们就从它的初始化入口开始分析,即在Application的调用的LeakCanary的install方法:

代码语言:javascript复制
LeakCanary.install(this);

3-1、LeakCanary#install()

代码语言:javascript复制
public static @NonNull RefWatcher install(@NonNull Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}

在install()方法中的处理,可以分解为如下四步:

  • 1、refWatcher(application),创建:AndroidRefWatcherBuilder对象。
  • 2、链式调用listenerServiceClass(DisplayLeakService.class)
  • 3、链式调用excludedRefs(AndroidExcludedRefs.createAppDefaults().build()),创建ExcludedRefs对象,排除的内存泄漏。
  • 4、链式调用uildAndInstall()

3-1-1、第一步,调用LeakCanary的refWatcher方法:

代码语言:javascript复制
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
  return new AndroidRefWatcherBuilder(context);
}

// AndroidRefWatcherBuilder 类
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {

  private final Context context;

  AndroidRefWatcherBuilder(@NonNull Context context) {
    this.context = context.getApplicationContext();
  }
}

// RefWatcherBuilder 
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {

  private final HeapDump.Builder heapDumpBuilder;

  public RefWatcherBuilder() {
    // 新建了一个HeapDump的构造器对象,其中HeapDump就是一个保存heap dump信息的数据结构。
    heapDumpBuilder = new HeapDump.Builder();
  }
}

这个方法很简单,就是创建了一个AndroidRefWatcherBuilder对象,并保存Application类型的Context。

AndroidRefWatcherBuilder是一个适配Android平台的引用观察者构造器对象,它继承了RefWatcherBuilder,RefWatcherBuilder是一个负责建立引用观察者RefWatcher实例的基类构造器,在其默认的构造方法中,新建了一个HeapDump的构造器对象,其中HeapDump就是一个保存heap dump信息的数据结构。

在new AndroidRefWatcherBuilder对象的时候,做了两件事:

  • (1)、保存Application类型的Context
  • (2)、创建HeapDump.Builder对象。

3-1-2、AndroidRefWatcherBuilder的listenerServiceClass方法:

代码语言:javascript复制
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
    @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
  return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

 /** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
  this.heapDumpListener = heapDumpListener;
  return self();
}

// ServiceHeapDumpListener 类
public final class ServiceHeapDumpListener implements HeapDump.Listener {

  private final Context context;
  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

  public ServiceHeapDumpListener(@NonNull final Context context,
      @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    this.listenerServiceClass = checkNotNull(listenerServiceClass,"listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }

}

从上面代码可以看出,listenerServiceClass方法主要是保存了一个ServiceHeapDumpListener对象,ServiceHeapDumpListener里面存储着DisplayLeakService的Class对象和application对象。

DisplayLeakService:显示通知。

3-1-3、链式调用excludedRefs(AndroidExcludedRefs.createAppDefaults().build())

代码语言:javascript复制
// AndroidRefWatcherBuilder的excludedRefs方法
public final T excludedRefs(ExcludedRefs excludedRefs) {
  heapDumpBuilder.excludedRefs(excludedRefs); // 保存ExcludedRefs对象到HeapDump.Builder对象中
  return self();
}

下面看看AndroidExcludedRefs.createAppDefaults()方法:

代码语言:javascript复制
 public static @NonNull ExcludedRefs.Builder createAppDefaults() {
    return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
  }

  public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
    ExcludedRefs.Builder excluded = ExcludedRefs.builder();
    for (AndroidExcludedRefs ref : refs) {
      if (ref.applies) {
        ref.add(excluded);
        ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
      }
    }
    return excluded;
  }

AndroidExcludedRefs这个类,它是一个enum类,它声明了Android SDK和厂商定制的SDK中存在的内存泄露的案例,这些内存泄漏的情况都会被Leakcanary的监测过滤掉。

目前这个版本是有46种这样的case被包含在内,后续可能会一直增加。然后EnumSet.allOf(AndroidExcludedRefs.class)这个方法将会返回一个包含AndroidExcludedRefs元素类型的EnumSet。Enum是一个抽象类,在这里具体的实现类是通用正规型的RegularEnumSet,如果Enum里面的元素个数大于64,则会使用存储大数据量的JumboEnumSet。最后,在createBuilder这个方法里面构建了一个排除引用的建造器excluded,将各式各样的case分门别类地保存起来再返回出去。

3-1-4、链式调用AndroidRefWatcherBuilder#uildAndInstall()

代码语言:javascript复制
private boolean watchActivities = true;
private boolean watchFragments = true;

public @NonNull RefWatcher buildAndInstall() {
  // 1.判断LeakCanaryInternals.installedRefWatcher是否已经被赋值,如果被赋值了,则会抛出异常,警告buildAndInstall()这个方法应该仅仅只调用一次
  if (LeakCanaryInternals.installedRefWatcher != null) {
    throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
  }
  RefWatcher refWatcher = build();// 2. 构建引用观察者RefWatcher对象
  if (refWatcher != DISABLED) {
    if (enableDisplayLeakActivity) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
    }
    if (watchActivities) {// 3. watchActivities默认为true
      ActivityRefWatcher.install(context, refWatcher);// 监听Activity
    }
    if (watchFragments) {// 4. 观察Fragment
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  // 5.为LeakCanaryInternals.installedRefWatcher赋值
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;

从上面代码可以看出,buildAndInstall方法主要做了如下几件事:

  • (1)、检测LeakCanaryInternals.installedRefWatcher是否已经被赋值,如果被赋值了,则会抛出异常,警告buildAndInstall()这个方法应该仅仅只调用一次
  • (2)、根据前面三步提供的配置对象,构建一个RefWatcher对象。
  • (3)、观察Activity是否泄漏
  • (4)、观察Fragment是否泄漏
  • (5)、为LeakCanaryInternals.installedRefWatcher赋值
(1)、构建一个RefWatcher对象。

这里再去看看注释2处的build()方法具体做了什么操作:

代码语言:javascript复制
public final RefWatcher build() {
  // 1. 是否监听泄漏
  if (isDisabled()) {
    return RefWatcher.DISABLED;
  }

  // 2. 可以被忽略的内存泄漏
  if (heapDumpBuilder.excludedRefs == null) {
    heapDumpBuilder.excludedRefs(defaultExcludedRefs());
  }

  // 3. 转储堆信息到hprof文件,并在解析完 hprof 文件后进行回调,最后通知 DisplayLeakService 弹出泄漏提醒。
  HeapDump.Listener heapDumpListener = this.heapDumpListener;
  if (heapDumpListener == null) {
    heapDumpListener = defaultHeapDumpListener();
  }

  // 4. 判断是否处于调试模式,调试模式中不会进行内存泄漏检测。为什么呢?因为在调试过程中可能会保留上一个引用从而导致错误信息上报。
  DebuggerControl debuggerControl = this.debuggerControl;
  if (debuggerControl == null) {
    debuggerControl = defaultDebuggerControl();
  }

  // 5. 堆信息转存者,dump 内存泄漏处的 heap 信息到 hprof 文件
  HeapDumper heapDumper = this.heapDumper;
  if (heapDumper == null) {
    heapDumper = defaultHeapDumper();
  }

  // 6. 线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
  WatchExecutor watchExecutor = this.watchExecutor;
  if (watchExecutor == null) {
    watchExecutor = defaultWatchExecutor();
  }

  // 7. 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,最后根据heapDump信息生成相应的泄漏引用链。
  GcTrigger gcTrigger = this.gcTrigger;
  if (gcTrigger == null) {
    gcTrigger = defaultGcTrigger();
  }

  // 8. 用于要进行可达性检测的类列表
  if (heapDumpBuilder.reachabilityInspectorClasses == null) {
    heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
  }

  // 9.创建RefWatcher对象
  return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
      heapDumpBuilder);
}

从上面代码可以看出,build方法主要是根据配置的信息,构建一个RefWatcher对象。主要信息有:

  • (1)、excludedRefs:记录可以被忽略的泄漏路径,这个在链式调用第三步的excludedRefs(AndroidExcludedRefs.createAppDefaults().build())方法中提供了。
  • (2)、heapDumpListener:保存内存信息到hprof文件后的回调接口,这个在链式调用的listenerServiceClass方法中提供了。
  • (3)、heapDumper:dump 内存信息并保存到 hprof 文件,在链式调用的第一步创建AndroidRefWatcherBuilder对象的时候,在RefWatcherBuilder的默认构造函数中有创建。
  • (4)、watchExecutor:创建一个默认的线程池,AndroidWatchExecutor对象。
  • (5)、gcTrigger:执行GC的对象

最后,根据这些对象,创建一个新的RefWatcher并将其返回。

(2)观察Activity:

这里主要是根据前面三步提供的配置对象,构建一个引用观察者:RefWatcher对象。通过这个对象,去观察对象是否泄漏。接下来看看ActivityRefWatcher#install方法的操作:

代码语言:javascript复制
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
  // 1.创建ActivityRefWatcher对象
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
  // 2. 为Application注册Activity生命周期监听回调
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

// 3. 创建一个Application.ActivityLifecycleCallbacks对象
 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          // 4.把Activity对象交给RefWatcher进行监控
          refWatcher.watch(activity);// refWatcher
        }
      };

从上面代码可以看出,ActivityRefWatcher的静态方法:**install()**主要做了两件事:

  • 创建一个ActivityRefWatcher对象
  • Application对象注册一个Activity生命周期监听的回调。

在这个Activity生命周期的回调中,监听了Activity的onDestory方法,并在onActivityDestroyed方法中,把Activity对象交给ActivityRefWatcher对象监管。

(3)、观察Fragment:

在AndroidRefWatcherBuilder#uildAndInstall()方法中,除了会监测Activity之外,默认也会监测Fragment的内存泄漏。

代码语言:javascript复制
// watchFragments默认为true
if (watchFragments) {// 4. 观察Fragment
      FragmentRefWatcher.Helper.install(context, refWatcher);
}

接下来看看Helper的install方法:

代码语言:javascript复制
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<>();

  // 1. Android 8.0以上,创建一个android.app包下的Fragment观察者
  if (SDK_INT >= O) {// android 8.0
    fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
  }

  try {
    // 2.反射创建监测support包下的Fragment观察者
    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);

  // 3. 注册Activity生命周期回调
  Application application = (Application) context.getApplicationContext();
  application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}

// ActivityLifecycleCallbacks回调对象
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
  new ActivityLifecycleCallbacksAdapter() {
  @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    for (FragmentRefWatcher watcher : fragmentRefWatchers) {
      watcher.watchFragments(activity);
    }
  }
};

从上面代码可以看出,一共做了三件事:

  • Android 8.0及以上,创建android.app*包下的Fragment观察者:AndroidOFragmentRefWatcher
  • 通过反射,创建support包下的Fragment观察者:"com.squareup.leakcanary.internal.SupportFragmentRefWatcher"
  • 监听Activity生命周期,进而监听Fragment的生命周期。

所以对于:android.app.* 包下的Fragment,在android8.0以下,是没有对Fragment进行监测的,对于:support包下的Fragment,在没有引入:

代码语言:javascript复制
   // 可选,使用support库中的Fragment时,检测内存泄漏
    debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

库的情况下,也是没有对support包下的Fragment进行监测的。因为如果没有引用这个库的话,通过反射是找不到类:"com.squareup.leakcanary.internal.SupportFragmentRefWatcher",这样就没办法创建监测support包下的Fragment的观察者。

下面通过**AndroidOFragmentRefWatcher#watchFragments()**方法分析一下Fragment是如何被监测的:

代码语言:javascript复制
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
    new FragmentManager.FragmentLifecycleCallbacks() {

      @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
        View view = fragment.getView();
        if (view != null) {// 3. 监测Fragment的view是否内存泄漏
          refWatcher.watch(view);
        }
      }

      @Override
      public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
        refWatcher.watch(fragment);// 4. 检测Fragment是否内存泄漏
      }
    };

@Override public void watchFragments(Activity activity) {
  // 1. 获取到FragmentManager对象
  FragmentManager fragmentManager = activity.getFragmentManager();
  // 2. 为FragmentManager注册Fragment生命周期回调
  fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}

从上面代码可以看出,Fragment的生命周期监听跟Activity类似,只不过这里是通过为FragmentManager注册Fragment生命周期回调,从而全局监听Activity中所有的Fragment的生命周期。

在FragmentManager.FragmentLifecycleCallbacks的回调中,跟Activity类似,通过ActivityRefWatcher去检测Fragment和Fragment的View是否内存泄漏。

3-2、ActivityRefWatcher#watch()方法

ActivityRefWatcher类是继承与RefWatcher类的,在ActivityRefWatcher类中,没有实现watch()方法,在其父类RefWatcher中有具体的实现,RefWatcher类中的watch()方法:

代码语言:javascript复制
public void watch(Object watchedReference) {
  watch(watchedReference, "");
}

/**
 * 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();
  String key = UUID.randomUUID().toString();// 1. 生成一个唯一的Key
  retainedKeys.add(key);// 2.把生成的key保存在Set集合中
  // 3. 用一个弱引用:KeyedWeakReference包裹监听对象(如:Activity),弱引用关联一个引用队列:queue
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);
 // 4. 创建一个字任务,分析是否有内存泄漏
  ensureGoneAsync(watchStartNanoTime, reference);
}

RefWatcher的watch方法逻辑很简单,主要做了4件事:

  • (1)、为监听对象生成一个唯一的key值
  • (2)、保存key值到一个CopyOnWriteArraySet集合中
  • (3)、创建一个KeyedWeakReference类型的弱引用,包裹Activity对象,并与一个引用队列进行关联
  • (4)、创建一个字任务,分析是否有内存泄漏

这里我们在看一下自定义的弱引用:KeyedWeakReference类:

代码语言:javascript复制
// KeyedWeakReference类
final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;// 保存对象的key值
  public final String name;// 保存名称

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    // 1
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

可以看到,在KeyedWeakReference内部,使用了key和name标识了一个被检测的WeakReference对象。在注释1处,将弱引用和引用队列 ReferenceQueue 关联起来,如果弱引用referent持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyedWeakReference 持有的 Activity 对象如果被GC回收,该弱引用对象就会加入到引用队列 referenceQueue 中。

3-3、RefWatcher#的ensureGoneAsync方法

代码语言:javascript复制
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

在这个方法中,调用了watchExecutor对象的execute方法,执行了一个异步任务。watchExecutor对象是AndroidWatchExecutor类型。

3-4、AndroidWatchExecutor的execute方法:

代码语言:javascript复制
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) {
    // 1. 创建一个主现场的Handler对象
    mainHandler = new Handler(Looper.getMainLooper());
    // 2. 创建一个字线程
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    // 3.基于字线程handlerThread创建一个关联的Handler对象backgroundHandler
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }

  @Override public void execute(@NonNull Retryable retryable) {
    // 1. 如果是主现场,则调用waitForIdle方法
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      // 2. 通过mainHandler 执行一个异步任务
      postWaitForIdle(retryable, 0);
    }
  }
}

private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
  mainHandler.post(new Runnable() {
    @Override public void run() {
      waitForIdle(retryable, failedAttempts);
    }
  });
}

在execute()方法中,会进行主线程的判断,如果是主线程,则直接执行waitForIdle()方法,如果不是,则会通过handler发送一个消息,最终还是会调用waitForIdle()方法。

3-5、AndroidWatchExecutor的waitForIdle()方法:

代码语言:javascript复制
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;
    }
  });
}

这里的逻辑非常简单,就是往主线程的MessageQueue中,增加一个主线程任务都执行完成的回调。则在主线程任务都执行完成之后,即主线程空闲的时候,就会执行MessageQueue.IdleHandler的**queueIdle()**方法,从上代码可以看出,**queueIdle()方法执行了postToBackgroundWithDelay()**方法:

代码语言:javascript复制
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
  long delayMillis = initialDelayMillis * exponentialBackoffFactor;
  // 在主线程空闲的时候,开始分析内存,backgroundHandler是一个与字线程关联的Handler,所以通过backgroundHandler发出的任务,都运行在字线程中
  backgroundHandler.postDelayed(new Runnable() {
    @Override public void run() {
      Retryable.Result result = retryable.run();// 字线程运行
      if (result == RETRY) {
        postWaitForIdle(retryable, failedAttempts   1);
      }
    }
  }, delayMillis);// 延时处理,默认是5s钟
}

在这个方法中,通过backgroundHandler发送一个延时任务到子线程中,默认延时5s,最终执行Retryable的run方法。这就回到了:3-3、RefWatcher#的ensureGoneAsync方法的Retryable的run()方法。这里再次贴一下代码:

代码语言:javascript复制
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

AndroidWatchExecutor的waitForIdle()方法,是在等主线程空闲的时候,开始分析内存泄漏,分析内存泄漏的逻辑又通过一个字线程的handler转到了字线程中,因此,内存泄漏的分析逻辑在字线程中运行。

在执行Retryable的**run()**方法的时候,会执行RefWatcher#ensureGone()方法。

3-6、RefWatcher#ensureGone()方法

代码语言:javascript复制
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

  // 1. 先移除被回收的弱引用对象
  removeWeaklyReachableReferences();

  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }
  // 2. 如果没有内存泄漏
  if (gone(reference)) {
    return DONE;
  }
  
  // 3. 执行GC操作
  gcTrigger.runGc();
  
  // 4. 再次移除被回收的弱引用对象
  removeWeaklyReachableReferences();
  
  // 5. 如果有内存泄漏
  if (!gone(reference)) {
    long startDumpHeap = System.nanoTime();
    long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

    // 6. 通过heapDumper对象,保存hprof文件
    File heapDumpFile = heapDumper.dumpHeap();
    if (heapDumpFile == RETRY_LATER) {
      // Could not dump the heap.
      return RETRY;
    }
    long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

    // 7. 构建一个代表hprof文件的HeapDump对象
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();

    // 8. 分析这个hprof文件
    heapdumpListener.analyze(heapDump);
  }
  return DONE;
}

通过上面的代码注解,我们可以知道,ensureGone()方法分为8个步骤来处理业务:

  1. 先移除被回收的弱引用对象
  2. 判断是否有内存泄漏,没有内存泄漏就直接退出
  3. 如果有内存泄漏,则先执行GC操作
  4. 再次移除被回收的弱引用对象
  5. 再次判断是否有内存泄漏
  6. 如果有内存泄漏,则保存内存快照到hprof文件中
  7. 构建一个代表hprof文件的HeapDump对象
  8. 分析这个hprof文件

首先,我们来看看removeWeaklyReachableReferences方法的处理逻辑。

3-6-1、RefWatcher#removeWeaklyReachableReferences()方法

代码语言:javascript复制
private void removeWeaklyReachableReferences() {
  // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
  // reachable. This is before finalization or garbage collection has actually happened.
  KeyedWeakReference ref;
  while ((ref = (KeyedWeakReference) queue.poll()) != null) { // queue是与弱引用关联的引用队列
    retainedKeys.remove(ref.key); // 在Set集合中移除代表对象的key值
  }
}

在**# 3-2、ActivityRefWatcher#watch()方法**中,创建弱引用对象的时候,把弱引用与一个引用队列进行了关联,则被弱引用包裹的对象被回收的时候,这个弱引用对象就会被放入到这个引用队列中来。

在这里,我们遍历这个引用队列,遍历出来的弱引用对象,其引用的对象都是被回收了的对象,所以这里把代表被回收的对象的唯一key值从Set集合中移除。

3-6-2、RefWatcher#gone()方法

所以我们要判断一个对象是否内存泄漏了,就可以拿代表这个对象的唯一key值到Set集合中去找,看是否存在,如果存在,说明这个key的对象还没有被回收。下面,我们就看看gone()方法中的逻辑是否如我们猜想的这样:

代码语言:javascript复制
/**
* 判断对象是否被回收
* @return true:对象已经被回收了,false:对象还没有被回收
*/
private boolean gone(KeyedWeakReference reference) {
  return !retainedKeys.contains(reference.key); // 这里就是判断代表对象的唯一key值是否在Set集合中存在
}

若果key值在Set集合中存在,则gone方法返回false,则表示对象还没有被回收;如果key值不在集合中,则gone方法返回true,表示对象已经被回收了。

执行GC操作,再次从Set集合中移除被回收的对象的key值,再次判断对象是否被回收,如果对象还是没有被回收,则保存内存快照,生产hprof文件。

3-6-3、AndroidHeapDumper# dumpHeap()方法

代码语言:javascript复制
public File dumpHeap() {
  // 1. 创建一个用于保存hprof文件的file对象
  // hprof文件保存的路径为sd卡下的:Download/leakcanary-包名
  File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

  if (heapDumpFile == RETRY_LATER) {// 2.如果这个文件等于RETRY_LATER则表示生成失败,直接返回RETRY进行延时重试检测的操作
    return RETRY_LATER;
  }

  FutureResult<Toast> waitingForToast = new FutureResult<>();
  showToast(waitingForToast);

  if (!waitingForToast.wait(5, SECONDS)) {
    CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
    return RETRY_LATER;
  }

  Notification.Builder builder = new Notification.Builder(context)
      .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
  Notification notification = LeakCanaryInternals.buildNotification(context, builder);
  NotificationManager notificationManager =
      (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  int notificationId = (int) SystemClock.uptimeMillis();
  notificationManager.notify(notificationId, notification);

  Toast toast = waitingForToast.get();
  try {
    // 2. 保存hprof文件
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    cancelToast(toast);
    notificationManager.cancel(notificationId);
    return heapDumpFile;
  } catch (Exception e) {
    CanaryLog.d(e, "Could not dump heap");
    // Abort heap dump
    return RETRY_LATER;
  }
}

这个方法里面的代码有很多,我们只需要重点关注注释处的代码即可:

  • (1)、创建一个用于保存hprof文件的file对象,保存路径为sd卡下的:“Download/leakcanary-包名” hprof文件的保存路径: 如果app有存储权限,则为:“Download/leakcanary-包名” 如果没有存储权限,则为:"data/data/包名/files/leakcanary"
  • (2)、如果这个文件等于RETRY_LATER则表示生成失败,直接返回RETRY进行延时重试检测的操作
  • (3)、调用系统api:Debug.dumpHprofData(),保存hprof文件到指定的目录

如果hprof文件生成成功,就会调用heapDumpBuilder建造者创建一个代表hprof文件的HeapDump对象。通过前面的分析我们知道,heapDumpBuilder对象是HeapDump.Builder类型,在RefWatcherBuilder的默认构造函数中创建。

3-6-4、通过建造者:HeapDump.Builder构建一个代表hprof文件的HeapDump对象

代码语言:javascript复制
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  // 。。。。。。。。。。。。。。。。。。。。。。。。
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

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

// HeapDump.Builder的build方法
public HeapDump build() {
      checkNotNull(excludedRefs, "excludedRefs");
      checkNotNull(heapDumpFile, "heapDumpFile");
      checkNotNull(referenceKey, "referenceKey");
      checkNotNull(reachabilityInspectorClasses, "reachabilityInspectorClasses");
      return new HeapDump(this); // 创建一个HeapDump对象
    }

// HeapDump的构造函数
  HeapDump(Builder builder) {
    this.heapDumpFile = builder.heapDumpFile;
    this.referenceKey = builder.referenceKey;
    this.referenceName = builder.referenceName;
    this.excludedRefs = builder.excludedRefs;
    this.computeRetainedHeapSize = builder.computeRetainedHeapSize;
    this.watchDurationMs = builder.watchDurationMs;
    this.gcDurationMs = builder.gcDurationMs;
    this.heapDumpDurationMs = builder.heapDumpDurationMs;
    this.reachabilityInspectorClasses = builder.reachabilityInspectorClasses;
  }

第六步执行完后,就生成了一个HeapDump对象。

最后会执行heapdumpListener的analyze()对新创建的HeapDump对象进行泄漏分析。由前面对AndroidRefWatcherBuilderlistenerServiceClass()的分析可知,heapdumpListener的实现就是ServiceHeapDumpListener,接着看到ServiceHeapDumpListener的**analyze()**方法。

3-6-4、ServiceHeapDumpListener#analyze()方法:

代码语言:javascript复制
@Override public void analyze(@NonNull HeapDump heapDump) {
  checkNotNull(heapDump, "heapDump");
  HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}

这里的逻辑很简单,就是把处理委托给了HeapAnalyzerServicerunAnalysis方法去执行:

代码语言:javascript复制
public static void runAnalysis(Context context, HeapDump heapDump,
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  setEnabledBlocking(context, HeapAnalyzerService.class, true);
  setEnabledBlocking(context, listenerServiceClass, true);
  Intent intent = new Intent(context, HeapAnalyzerService.class);// 
  intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());// 携带DisplayLeakService的名字
  intent.putExtra(HEAPDUMP_EXTRA, heapDump);
  ContextCompat.startForegroundService(context, intent); // 启动服务:HeapAnalyzerService
}

HeapAnalyzerServicerunAnalysis方法的逻辑也很简单,就是启动一个:HeapAnalyzerService服务。

3-7、HeapAnalyzerService服务启动

HeapAnalyzerService继承与ForegroundService服务,而ForegroundService服务又继承与IntentService,IntentService在启动的时候,会执行:onHandleIntent()方法。

ForegroundService中重写了onHandleIntent()方法:

代码语言:javascript复制
@Override protected void onHandleIntent(@Nullable Intent intent) {
  onHandleIntentInForeground(intent);
}

protected abstract void onHandleIntentInForeground(@Nullable Intent intent);

接下来看HeapAnalyzerService的onHandleIntentInForeground()方法

代码语言:javascript复制
protected void onHandleIntentInForeground(@Nullable Intent intent) {
  if (intent == null) {
    CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
    return;
  }
  // listenerClassName就是DisplayLeakService的名字
  String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
  HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

  // 1. 首先会新建一个HeapAnalyzer对象,它会根据RefWatcher生成的heap dumps信息来分析被怀疑的泄漏是否是真的泄漏
  HeapAnalyzer heapAnalyzer =
      new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

  // 2. 然后会调用它的checkForLeak()方法去使用haha库解析 hprof文件
  AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
      heapDump.computeRetainedHeapSize);
  
  // 3. 把分析结果显示出来
  AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

从上面代码可以看出,注释1处,首先会新建一个HeapAnalyzer对象,顾名思义,它就是根据RefWatcher生成的heap dumps信息来分析被怀疑的泄漏是否是真的。在注释2处,然后会调用它的checkForLeak()方法去使用haha库解析 hprof文件。

3-8、HeapAnalyzer#checkForLeak()方法:

代码语言:javascript复制
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
    @NonNull String referenceKey,
    boolean computeRetainedSize) {
  long analysisStartNanoTime = System.nanoTime();

  if (!heapDumpFile.exists()) { // 判断hprof文件是否存在
    Exception exception = new IllegalArgumentException("File does not exist: "   heapDumpFile);
    return failure(exception, since(analysisStartNanoTime));
  }

  try {
    listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
    // 1. 新建一个内存映射缓存文件buffer
    HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
    // 2. 创建一个hprof文件解析器
    HprofParser parser = new HprofParser(buffer);
    listener.onProgressUpdate(PARSING_HEAP_DUMP);
    Snapshot snapshot = parser.parse();
    listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
    // 3. 删除重复的GC root对象
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
    
    // 4. 查找泄漏的引用
    Instance leakingRef = findLeakingReference(referenceKey, snapshot);

    // False alarm, weak reference was cleared in between key check and heap dump.
    // 5. 如果没有找到泄漏的引用
    if (leakingRef == null) {
      String className = leakingRef.getClassObj().getClassName();
      return noLeak(className, since(analysisStartNanoTime)); // 返回一个没有泄漏的分析结果AnalysisResult对象
    }
    // 6. 返回一个有泄漏分析结果的AnalysisResult对象
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
  } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
  }
}

这个方法执行的逻辑有点复杂,主要分为如下6个步骤:

  1. 新建一个内存映射缓存文件buffer对象
  2. 根据第一步生成的buffer对象,创建一个hprof文件解析器。
  3. 删除重复的GC Root对象
  4. 查找泄漏的引用
  5. 如果没有找到泄漏的引用对象,则返回一个没有泄漏的AnalysisResult对象
  6. 如果有泄漏,则返回一个有泄漏的AnalysisResult对象。

最后,我们来分析下HeapAnalyzerServiceonHandleIntentInForeground方法注释3处的AbstractAnalysisResultService.sendResultToListener()方法,很明显,这里AbstractAnalysisResultService的实现类就是我们刚开始分析的用于展示泄漏路径信息得DisplayLeakService对象。在里面直接创建一个由PendingIntent构建的泄漏通知用于供用户点击去展示详细的泄漏界面DisplayLeakActivity。

3-9、AbstractAnalysisResultService.sendResultToListener()方法:

代码语言:javascript复制
public static void sendResultToListener(@NonNull Context context,
    @NonNull String listenerServiceClassName,
    @NonNull HeapDump heapDump,
    @NonNull AnalysisResult result) {
  Class<?> listenerServiceClass;
  try {
    // listenerServiceClassName是DisplayLeakService
    listenerServiceClass = Class.forName(listenerServiceClassName);
  } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
  }
  Intent intent = new Intent(context, listenerServiceClass);

  File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);
  if (analyzedHeapFile != null) {
    intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());
  }
  ContextCompat.startForegroundService(context, intent);// 这里就是启动DisplayLeakService
}

这里的处理逻辑很简单,就是启动DisplayLeakService服务。

3-10、DisplayLeakService启动之后,就会执行onHeapAnalyzed方法:

代码语言:javascript复制
protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
  HeapDump heapDump = analyzedHeap.heapDump;
  AnalysisResult result = analyzedHeap.result;

  String leakInfo = leakInfo(this, heapDump, result, true);
  CanaryLog.d("%s", leakInfo);

  heapDump = renameHeapdump(heapDump);
  // 1. 保存HeapDump对象和内存泄漏分析结果对象保存到指定的文件
  boolean resultSaved = saveResult(heapDump, result);

  String contentTitle;
  if (resultSaved) {// 保存成功
    // 2. 创建一个跳转到DisplayLeakActivity的Intent对象
    PendingIntent pendingIntent =
        DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
    if (result.failure != null) {
      contentTitle = getString(R.string.leak_canary_analysis_failed);
    } else {
      String className = classSimpleName(result.className);
      if (result.leakFound) {
        if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
          if (result.excludedLeak) {
            contentTitle = getString(R.string.leak_canary_leak_excluded, className);
          } else {
            contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
          }
        } else {
          String size = formatShortFileSize(this, result.retainedHeapSize);
          if (result.excludedLeak) {
            contentTitle =
                getString(R.string.leak_canary_leak_excluded_retaining, className, size);
          } else {
            contentTitle =
                getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
          }
        }
      } else {
        contentTitle = getString(R.string.leak_canary_class_no_leak, className);
      }
    }
    String contentText = getString(R.string.leak_canary_notification_message);
    // 3. 创建一个供用户点击跳转到DisplayLeakActivity的延时通知
    showNotification(pendingIntent, contentTitle, contentText);
  } else {
    onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
  }

  afterDefaultHandling(heapDump, result, leakInfo);
}

// DisplayLeakActivity的createPendingIntent方法
public static PendingIntent createPendingIntent(Context context, String referenceKey) {
  setEnabledBlocking(context, DisplayLeakActivity.class, true);
  Intent intent = new Intent(context, DisplayLeakActivity.class);
  intent.putExtra(SHOW_LEAK_EXTRA, referenceKey);
  intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
  return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
}

到此,LeakCanary源码分析完成。

0 人点赞