前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >全解系列:内存泄漏定位工具LeakCanary!

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

作者头像
胡飞洋
发布2020-09-17 14:31:05
5.3K0
发布2020-09-17 14:31:05
举报
文章被收录于专栏:胡飞洋的Android进阶
本文来自我的同事 梅贤兵 的投稿,分析了内存泄漏定位工具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源码分析完成。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 胡飞洋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、LeakCanary的简单使用
  • 二、LeakCanary原理简单分析:
    • 2-1、LeakCanary原理简述
      • 2-2、ActivityLifecycleCallbacks使用
        • 2-2-1、ActivityLifecycleCallbacks的使用:
        • 2-2-2、ActivityLifecycleCallbacks源码分析:
    • 三、LeakCanary源码分析:
      • 3-1、LeakCanary#install()
        • 3-1-1、第一步,调用LeakCanary的refWatcher方法:
        • 3-1-2、AndroidRefWatcherBuilder的listenerServiceClass方法:
        • 3-1-3、链式调用excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        • 3-1-4、链式调用AndroidRefWatcherBuilder#uildAndInstall()
      • 3-2、ActivityRefWatcher#watch()方法
        • 3-3、RefWatcher#的ensureGoneAsync方法
          • 3-4、AndroidWatchExecutor的execute方法:
            • 3-5、AndroidWatchExecutor的waitForIdle()方法:
              • 3-6、RefWatcher#ensureGone()方法
                • 3-6-1、RefWatcher#removeWeaklyReachableReferences()方法
                • 3-6-2、RefWatcher#gone()方法
                • 3-6-3、AndroidHeapDumper# dumpHeap()方法
                • 3-6-4、通过建造者:HeapDump.Builder构建一个代表hprof文件的HeapDump对象
                • 3-6-4、ServiceHeapDumpListener#analyze()方法:
              • 3-7、HeapAnalyzerService服务启动
                • 3-8、HeapAnalyzer#checkForLeak()方法:
                  • 3-9、AbstractAnalysisResultService.sendResultToListener()方法:
                    • 3-10、DisplayLeakService启动之后,就会执行onHeapAnalyzed方法:
                    相关产品与服务
                    检测工具
                    域名服务检测工具(Detection Tools)提供了全面的智能化域名诊断,包括Whois、DNS生效等特性检测,同时提供SSL证书相关特性检测,保障您的域名和网站健康。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档