Android主流三方库源码分析:Leakcanary

Android主流三方库源码分析:Leakcanary,第1张

概述一、原理概述首先,笔者仔细查看了Leakcanary官方的github仓库,最重要的便是对Leakcanary是如何起作用的(即原理)这一问题进行了阐述,我自己把它翻译成了易于理解的文字,主要分为如下7个步骤:1、RefWatcher.watch()创建了一个KeyedWeakReference用于去观察对象。2、然后,在后台线程 一、原理概述

首先,笔者仔细查看了Leakcanary官方的github仓库,最重要的便是对Leakcanary是如何起作用的(即原理)这一问题进行了阐述,我自己把它翻译成了易于理解的文字,主要分为如下7个步骤:

1、RefWatcher.watch()创建了一个KeyeDWeakReference用于去观察对象。2、然后,在后台线程中,它会检测引用是否被清除了,并且是否没有触发GC。3、如果引用仍然没有被清除,那么它将会把堆栈信息保存在文件系统中的.hprof文件里。4、HeapAnalyzerService被开启在一个独立的进程中,并且HeapAnalyzer使用了HAHA开源库解析了指定时刻的堆栈快照文件heap dump。5、从heap dump中,HeapAnalyzer根据一个独特的引用key找到了KeyeDWeakReference,并且定位了泄露的引用。6、HeapAnalyzer为了确定是否有泄露,计算了到GC Roots的最短强引用路径,然后建立了导致泄露的链式引用。7、这个结果被传回到app进程中的displayLeakService,然后一个泄露通知便展现出来了。

官方的原理简单来解释就是这样的:在一个Activity执行完onDestroy()之后,将它放入WeakReference中,然后将这个WeakReference类型的Activity对象与ReferenceQueque关联。这时再从ReferenceQueque中查看是否有没有该对象,如果没有,执行gc,再次查看,还是没有的话则判断发生内存泄露了。最后用HAHA这个开源库去分析dump之后的heap内存。

二、简单示例

下面这段是Leakcanary官方仓库的示例代码:

首先在你项目app下的build.gradle中配置:

dependencIEs {  deBUGImplementation 'com.squareup.leakcanary:leakcanary-androID:1.6.2'  releaseImplementation   'com.squareup.leakcanary:leakcanary-androID-no-op:1.6.2'  // 可选,如果你使用支持库的fragments的话  deBUGImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'}复制代码

然后在你的Application中配置:

public class WanAndroIDApp extends Application {    private RefWatcher refWatcher;    public static RefWatcher getRefWatcher(Context context) {        WanAndroIDApp application = (WanAndroIDApp)     context.getApplicationContext();        return application.refWatcher;    }    @OverrIDe public voID onCreate() {      super.onCreate();      if (LeakCanary.isInAnalyzerProcess(this)) {        // 1        return;      }      // 2      refWatcher = LeakCanary.install(this);    }}复制代码

在注释1处,会首先判断当前进程是否是Leakcanary专门用于分析heap内存的而创建的那个进程,即HeapAnalyzerService所在的进程,如果是的话,则不进行Application中的初始化功能。如果是当前应用所处的主进程的话,则会执行注释2处的LeakCanary.install(this)进行LeakCanary的安装。只需这样简单的几行代码,我们就可以在应用中检测是否产生了内存泄露了。当然,这样使用只会检测Activity和标准Fragment是否发生内存泄漏,如果要检测V4包的Fragment在执行完onDestroy()之后是否发生内存泄露的话,则需要在Fragment的onDestroy()方法中加上如下两行代码去监视当前的Fragment:

RefWatcher refWatcher = WanAndroIDApp.getRefWatcher(_mActivity);refWatcher.watch(this);复制代码

上面的RefWatcher其实就是一个引用观察者对象,是用于监测当前实例对象的引用状态的。从以上的分析可以了解到,核心代码就是LeakCanary.install(this)这行代码,接下来,就从这里出发将LeakCanary一步一步进行拆解。

三、源码分析1、LeakCanary#install()
public static @NonNull RefWatcher install(@NonNull Application application) {  return refWatcher(application).ListenerServiceClass(displayLeakService.class)      .excludedRefs(AndroIDExcludedRefs.createAppDefaults().build())      .buildAndInstall();}复制代码

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

1、refWatcher(application)2、链式调用ListenerServiceClass(displayLeakService.class)3、链式调用excludedRefs(AndroIDExcludedRefs.createAppDefaults().build())4、链式调用buildAndInstall()

首先,我们来看下第一步,这里调用了LeakCanary类的refWatcher方法,如下所示:

public static @NonNull AndroIDRefWatcherBuilder refWatcher(@NonNull Context context) {  return new AndroIDRefWatcherBuilder(context);}复制代码

然后新建了一个AndroIDRefWatcherBuilder对象,再看看AndroIDRefWatcherBuilder这个类。

2、AndroIDRefWatcherBuilder
/** A {@link RefWatcherBuilder} with appropriate AndroID defaults. */public final class AndroIDRefWatcherBuilder extends RefWatcherBuilder<AndroIDRefWatcherBuilder> {...    AndroIDRefWatcherBuilder(@NonNull Context context) {        this.context = context.getApplicationContext();    }...}复制代码

在AndroIDRefWatcherBuilder的构造方法中仅仅是将外部传入的applicationContext对象保存起来了。AndroIDRefWatcherBuilder是一个适配AndroID平台的引用观察者构造器对象,它继承了RefWatcherBuilder,RefWatcherBuilder是一个负责建立引用观察者RefWatcher实例的基类构造器。继续看看RefWatcherBuilder这个类。

3、RefWatcherBuilder
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {    ...    public RefWatcherBuilder() {        heapDumpBuilder = new HeapDump.Builder();    }    ...}复制代码

在RefWatcher的基类构造器RefWatcherBuilder的构造方法中新建了一个HeapDump的构造器对象。其中HeapDump就是一个保存heap dump信息的数据结构。

接着来分析下install()方法中的链式调用的ListenerServiceClass(displayLeakService.class)这部分逻辑。

4、AndroIDRefWatcherBuilder#ListenerServiceClass()
public @NonNull AndroIDRefWatcherBuilder ListenerServiceClass(  @NonNull Class<? extends AbstractAnalysisResultService> ListenerServiceClass) {    return heapDumpListener(new ServiceHeapDumpListener(context, ListenerServiceClass));}复制代码

在这里,传入了一个displayLeakService的Class对象,它的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面displayLeakActivity的通知。在ListenerServiceClass()这个方法中新建了一个ServiceHeapDumpListener对象,下面看看它内部的 *** 作。

5、ServiceHeapDumpListener
public final class ServiceHeapDumpListener implements HeapDump.Listener {    ...    public ServiceHeapDumpListener(@NonNull final Context context,        @NonNull final Class<? extends AbstractAnalysisResultService> ListenerServiceClass) {      this.ListenerServiceClass = checkNotNull(ListenerServiceClass, "ListenerServiceClass");      this.context = checkNotNull(context, "context").getApplicationContext();    }    ...}复制代码

可以看到这里仅仅是在ServiceHeapDumpListener中保存了displayLeakService的Class对象和application对象。它的作用就是接收一个heap dump去分析。

然后我们继续看install()方法链式调用.excludedRefs(AndroIDExcludedRefs.createAppDefaults().build())的这部分代码。先看AndroIDExcludedRefs.createAppDefaults()。

6、AndroIDExcludedRefs#createAppDefaults()
public enum AndroIDExcludedRefs {    ...    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中存在的内存泄露的case,根据AndroIDExcludedRefs这个类的类名就可看出这些case都会被Leakcanary的监测过滤掉。目前这个版本是有46种这样的case被包含在内,后续可能会一直增加。然后EnumSet.allOf(AndroIDExcludedRefs.class)这个方法将会返回一个包含AndroIDExcludedRefs元素类型的EnumSet。Enum是一个抽象类,在这里具体的实现类是通用正规型的RegularEnumSet,如果Enum里面的元素个数大于64,则会使用存储大数据量的JumboEnumSet。最后,在createBuilder这个方法里面构建了一个排除引用的建造器excluded,将各式各样的case分门别类地保存起来再返回出去。

最后,我们看到链式调用的最后一步buildAndInstall()。

7、AndroIDRefWatcherBuilder#buildAndInstall()
private boolean watchActivitIEs = true;private boolean watchFragments = true;public @NonNull RefWatcher buildAndInstall() {    // 1    if (LeakCanaryInternals.installedRefWatcher != null) {      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");    }    // 2    RefWatcher refWatcher = build();    if (refWatcher != Disabled) {      // 3      LeakCanaryInternals.setEnabledAsync(context, displayLeakActivity.class, true);      if (watchActivitIEs) {        // 4        ActivityRefWatcher.install(context, refWatcher);      }      if (watchFragments) {        // 5        FragmentRefWatcher.Helper.install(context, refWatcher);      }    }    // 6    LeakCanaryInternals.installedRefWatcher = refWatcher;    return refWatcher;}复制代码

首先,在注释1处,会判断LeakCanaryInternals.installedRefWatcher是否已经被赋值,如果被赋值了,则会抛出异常,警告 buildAndInstall()这个方法应该仅仅只调用一次,在此方法结束时,即在注释6处,该LeakCanaryInternals.installedRefWatcher才会被赋值。再来看注释2处,调用了AndroIDRefWatcherBuilder其基类RefWatcherBuilder的build()方法,我们它是如何建造的。

8、RefWatcherBuilder#build()
public final RefWatcher build() {    if (isDisabled()) {      return RefWatcher.Disabled;    }    if (heapDumpBuilder.excludedRefs == null) {      heapDumpBuilder.excludedRefs(defaultExcludedRefs());    }    HeapDump.Listener heapDumpListener = this.heapDumpListener;    if (heapDumpListener == null) {      heapDumpListener = defaultHeapDumpListener();    }    DeBUGgerControl deBUGgerControl = this.deBUGgerControl;    if (deBUGgerControl == null) {      deBUGgerControl = defaultDeBUGgerControl();    }    HeapDumper heapDumper = this.heapDumper;    if (heapDumper == null) {      heapDumper = defaultHeapDumper();    }    WatchExecutor watchExecutor = this.watchExecutor;    if (watchExecutor == null) {      watchExecutor = defaultWatchExecutor();    }    GcTrigger gcTrigger = this.gcTrigger;    if (gcTrigger == null) {      gcTrigger = defaultGcTrigger();    }    if (heapDumpBuilder.reachabilityInspectorClasses == null) {      heapDumpBuilder.reachabilityInspectorClasses(defa  ultReachabilityInspectorClasses());    }    return new RefWatcher(watchExecutor, deBUGgerControl, gcTrigger, heapDumper, heapDumpListener,        heapDumpBuilder);}复制代码

可以看到,RefWatcherBuilder包含了以下7个组成部分:

1、excludedRefs : 记录可以被忽略的泄漏路径。

2、heapDumpListener : 转储堆信息到hprof文件,并在解析完 hprof 文件后进行回调,最后通知 displayLeakService d出泄漏提醒。

3、deBUGgerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测。为什么呢?因为在调试过程中可能会保留上一个引用从而导致错误信息上报。

4、heapDumper : 堆信息转储者,负责dump 内存泄漏处的 heap 信息到 hprof 文件。

5、watchExecutor : 线程控制器,在 onDestroy() 之后并且在主线程空闲时执行内存泄漏检测。

6、gcTrigger : 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,最后根据heapDump信息生成相应的泄漏引用链。

7、reachabilityInspectorClasses : 用于要进行可达性检测的类列表。

最后,会使用建造者模式将这些组成部分构建成一个新的RefWatcher并将其返回。

我们继续看回到AndroIDRefWatcherBuilder的注释3处的 LeakCanaryInternals.setEnabledAsync(context, displayLeakActivity.class, true)这行代码。

9、LeakCanaryInternals#setEnabledAsync()
public static voID setEnabledAsync(Context context, final Class<?> componentClass,final boolean enabled) {  final Context appContext = context.getApplicationContext();  AsyncTask.THREAD_POol_EXECUTOR.execute(new Runnable() {    @OverrIDe public voID run() {      setEnabledBlocking(appContext, componentClass, enabled);    }  });}复制代码

在这里直接使用了AsyncTask内部自带的THREAD_POol_EXECUTOR线程池进行阻塞式地显示displayLeakActivity。

然后我们再继续看AndroIDRefWatcherBuilder的注释4处的代码。

10、ActivityRefWatcher#install()
public static voID install(@NonNull Context context, @NonNull RefWatcher refWatcher) {    Application application = (Application) context.getApplicationContext();    // 1    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);    // 2    application.registeractivitylifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);}复制代码

可以看到,在注释1处创建一个自己的activityRefWatcher实例,并在注释2处调用了application的registeractivitylifecycleCallbacks()方法,这样就能够监听activity对应的生命周期事件了。继续看看activityRefWatcher.lifecycleCallbacks里面的 *** 作。

private final Application.ActivitylifecycleCallbacks lifecycleCallbacks =    new ActivitylifecycleCallbacksAdapter() {      @OverrIDe public voID onActivityDestroyed(Activity activity) {          refWatcher.watch(activity);      }};public abstract class ActivitylifecycleCallbacksAdapterimplements Application.ActivitylifecycleCallbacks {}复制代码

很明显,这里实现并重写了Application的ActivitylifecycleCallbacks的onActivityDestroyed()方法,这样便能在所有Activity执行完onDestroyed()方法之后调用 refWatcher.watch(activity)这行代码进行内存泄漏的检测了。

我们再看到注释5处的FragmentRefWatcher.Helper.install(context, refWatcher)这行代码,

11、FragmentRefWatcher.Helper#install()
public interface FragmentRefWatcher {    voID watchFragments(Activity activity);    final class Helper {      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        if (SDK_INT >= O) {          fragmentRefWatchers.add(new AndroIDOFragmentRefWatcher(refWatcher));        }        // 2        try {          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        Application application = (Application) context.getApplicationContext();        application.registeractivitylifecycleCallbacks(helper.activitylifecycleCallbacks);      }    ...}复制代码

这里面的逻辑很简单,首先在注释1处将AndroID标准的Fragment的RefWatcher类,即AndroIDOfFragmentRefWatcher添加到新创建的fragmentRefWatchers中。在注释2处使用反射将leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加进来,如果你在app的build.gradle下没有添加下面这行引用的话,则会拿不到此类,即LeakCanary只会检测Activity和标准Fragment这两种情况。

deBUGImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'复制代码

继续看到注释3处helper.activitylifecycleCallbacks里面的代码。

private final Application.ActivitylifecycleCallbacks activitylifecycleCallbacks =    new ActivitylifecycleCallbacksAdapter() {      @OverrIDe public voID onActivityCreated(Activity activity, Bundle savedInstanceState) {        for (FragmentRefWatcher watcher : fragmentRefWatchers) {            watcher.watchFragments(activity);        }    }};复制代码

可以看到,在Activity执行完onActivityCreated()方法之后,会调用指定watcher的watchFragments()方法,注意,这里的watcher可能有两种,但不管是哪一种,都会使用当前传入的activity获取到对应的FragmentManager/SupportFragmentManager对象,调用它的registerFragmentlifecycleCallbacks()方法,在对应的onDestroyVIEw()和onDestoryed()方法执行完后,分别使用refWatcher.watch(vIEw)和refWatcher.watch(fragment)进行内存泄漏的检测,代码如下所示。

@OverrIDe public voID onFragmentVIEwDestroyed(FragmentManager fm, Fragment fragment) {    VIEw vIEw = fragment.getVIEw();    if (vIEw != null) {        refWatcher.watch(vIEw);    }}@OverrIDepublic voID onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {    refWatcher.watch(fragment);}复制代码

注意,下面到真正关键的地方了,接下来分析refWatcher.watch()这行代码。

12、RefWatcher#watch()
public voID watch(Object watchedReference, String referencename) {    if (this == Disabled) {      return;    }    checkNotNull(watchedReference, "watchedReference");    checkNotNull(referencename, "referencename");    final long watchStartNanoTime = System.nanoTime();    // 1    String key = UUID.randomUUID().toString();    // 2    retainedKeys.add(key);    // 3    final KeyeDWeakReference reference =        new KeyeDWeakReference(watchedReference, key, referencename, queue);    // 4    ensureGoneAsync(watchStartNanoTime, reference);}复制代码

注意到在注释1处使用随机的UUID保证了每个检测对象对应 key 的唯一性。在注释2处将生成的key添加到类型为copyOnWriteArraySet的Set集合中。在注释3处新建了一个自定义的弱引用KeyeDWeakReference,看看它内部的实现。

13、KeyeDWeakReference
final class KeyeDWeakReference extends WeakReference<Object> {    public final String 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 关联起来,如果弱引用reference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。即 KeyeDWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中。

接着我们回到RefWatcher.watch()里注释4处的ensureGoneAsync()方法。

14、RefWatcher#ensureGoneAsync()
private voID ensureGoneAsync(final long watchStartNanoTime, final KeyeDWeakReference reference) {    // 1    watchExecutor.execute(new Retryable() {        @OverrIDe public Retryable.Result run() {            // 2            return ensureGone(reference watchStartNanoTime);        }    });}复制代码

在ensureGoneAsync()方法中,在注释1处使用 watchExecutor 执行了注释2处的 ensureGone 方法,watchExecutor 是 AndroIDWatchExecutor 的实例。

下面看看watchExecutor内部的逻辑。

15、AndroIDWatchExecutor
public final class AndroIDWatchExecutor implements WatchExecutor {    ...    public AndroIDWatchExecutor(long initialDelayMillis)     {      mainHandler = new Handler(Looper.getMainLooper());      HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_name);      handlerThread.start();      // 1      backgroundHandler = new Handler(handlerThread.getLooper());      this.initialDelayMillis = initialDelayMillis;      maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;    }    @OverrIDe public voID execute(@NonNull Retryable retryable) {      // 2      if (Looper.getMainLooper().getThread() == Thread.currentThread()) {        waitForIDle(retryable, 0);      } else {        postWaitForIDle(retryable, 0);      }    }    ...}复制代码

在注释1处AndroIDWatchExecutor的构造方法中,注意到这里使用HandlerThread的looper新建了一个backgroundHandler,后面会用到。在注释2处,会判断当前线程是否是主线程,如果是,则直接调用waitForIDle()方法,如果不是,则调用postWaitForIDle(),来看看这个方法。

private voID postWaitForIDle(final Retryable retryable, final int FailedAttempts) {  mainHandler.post(new Runnable() {    @OverrIDe public voID run() {      waitForIDle(retryable, FailedAttempts);    }  });}复制代码

很清晰,这里使用了在构造方法中用主线程looper构造的mainHandler进行post,那么waitForIDle()最终也会在主线程执行。接着看看waitForIDle()的实现。

private voID waitForIDle(final Retryable retryable,     final int FailedAttempts) {  Looper.myQueue().addIDleHandler(new MessageQueue.IDleHandler() {    @OverrIDe public boolean queueIDle() {      postToBackgrounDWithDelay(retryable, FailedAttempts);      return false;    }  });}复制代码

这里MessageQueue.IDleHandler()回调方法的作用是当 looper 空闲的时候,会回调 queueIDle 方法,利用这个机制我们可以实现第三方库的延迟初始化,然后执行内部的postToBackgrounDWithDelay()方法。接下来看看它的实现。

private voID postToBackgrounDWithDelay(final Retryable retryable, final int FailedAttempts) {  long exponentialBackoffFactor = (long) Math.min(Math.pow(2, FailedAttempts),     maxBackoffFactor);  // 1  long delayMillis = initialDelayMillis * exponentialBackoffFactor;  // 2  backgroundHandler.postDelayed(new Runnable() {    @OverrIDe public voID run() {      // 3      Retryable.Result result = retryable.run();      // 4      if (result == RETRY) {        postWaitForIDle(retryable, FailedAttempts +   1);      }    }  }, delayMillis);}复制代码

先看到注释4处,可以明白,postToBackgrounDWithDelay()是一个递归方法,如果result 一直等于RETRY的话,则会一直执行postWaitForIDle()方法。在回到注释1处,这里initialDelayMillis 的默认值是 5s,因此delayMillis就是5s。在注释2处,使用了在构造方法中用HandlerThread的looper新建的backgroundHandler进行异步延时执行retryable的run()方法。这个run()方法里执行的就是RefWatcher的ensureGoneAsync()方法中注释2处的ensureGone()这行代码,继续看它内部的逻辑。

16、RefWatcher#ensureGone()
Retryable.Result ensureGone(final KeyeDWeakReference reference, final long watchStartNanoTime) {    long gcStartNanoTime = System.nanoTime();    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime -     watchStartNanoTime);    // 1    removeWeaklyReachableReferences();    // 2    if (deBUGgerControl.isDeBUGgerAttached()) {      // The deBUGger can create false leaks.      return RETRY;    }    // 3    if (gone(reference)) {      return DONE;    }    // 4    gcTrigger.runGc();    removeWeaklyReachableReferences();    // 5    if (!gone(reference)) {      long startDumpHeap = System.nanoTime();      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);      file heapDumpfile = heapDumper.dumpHeap();      if (heapDumpfile == RETRY_LATER) {        // Could not dump the heap.        return RETRY;      }      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);      HeapDump heapDump = heapDumpBuilder.heapDumpfile(heapDumpfile).referenceKey(reference.key)          .referencename(reference.name)          .watchDurationMs(watchDurationMs)          .gcDurationMs(gcDurationMs)          .heapDumpDurationMs(heapDumpDurationMs)          .build();      heapdumpListener.analyze(heapDump);    }    return DONE;}复制代码

在注释1处,执行了removeWeaklyReachableReferences()这个方法,接下来分析下它的含义。

private voID removeWeaklyReachableReferences() {    KeyeDWeakReference ref;    while ((ref = (KeyeDWeakReference) queue.poll()) != null) {        retainedKeys.remove(ref.key);    }}复制代码

这里使用了while循环遍历 ReferenceQueue ,并从 retainedKeys中移除对应的Reference。

再看到注释2处,当AndroID设备处于deBUG状态时,会直接返回RETRY进行延时重试检测的 *** 作。在注释3处,我们看看gone(reference)这个方法的逻辑。

private boolean gone(KeyeDWeakReference reference) {    return !retainedKeys.contains(reference.key);}复制代码

这里会判断 retainedKeys 集合中是否还含有 reference,若没有,证明已经被回收了,若含有,可能已经发生内存泄露(或Gc还没有执行回收)。前面的分析中我们知道了 reference 被回收的时候,会被加进 referenceQueue 里面,然后我们会调用removeWeaklyReachableReferences()遍历 referenceQueue 移除掉 retainedKeys 里面的 refrence。

接着我们看到注释4处,执行了gcTrigger的runGc()方法进行垃圾回收,然后使用了removeWeaklyReachableReferences()方法移除已经被回收的引用。这里我们再深入地分析下runGc()的实现。

GcTrigger DEFAulT = new GcTrigger() {    @OverrIDe public voID runGc() {      // Code taken from AOSP FinalizationTest:      // https://androID.Googlesource.com/platform/libc  ore/+/master/support/src/test/java/libcore/      // java/lang/ref/FinalizationTester.java      // System.gc() does not garbage collect every   time. Runtime.gc() is      // more likely to perform a gc.      Runtime.getRuntime().gc();      enqueueReferences();      System.runFinalization();    }    private voID enqueueReferences() {      // Hack. We don't have a programmatic way to wait   for the reference queue daemon to move      // references to the appropriate queues.      try {        Thread.sleep(100);      } catch (InterruptedException e) {        throw new AssertionError();      }    }};复制代码

这里并没有使用System.gc()方法进行回收,因为system.gc()并不会每次都执行。而是从AOSP中拷贝一段GC回收的代码,从而相比System.gc()更能够保证垃圾回收的工作。

最后我们分析下注释5处的代码处理。首先会判断activity是否被回收,如果还没有被回收,则证明发生内存泄露,进行if判断里面的 *** 作。在里面先调用堆信息转储者heapDumper的dumpHeap()生成相应的 hprof 文件。这里的heapDumper是一个HeapDumper接口,具体的实现是AndroIDHeapDumper。我们分析下AndroIDHeapDumper的dumpHeap()方法是如何生成hprof文件的。

public file dumpHeap() {    file heapDumpfile = leakDirectoryProvIDer.newHeapDumpfile();    if (heapDumpfile == RETRY_LATER) {        return RETRY_LATER;    }    ...    try {      DeBUG.dumPHProfData(heapDumpfile.getabsolutePath());      ...      return heapDumpfile;    } catch (Exception e) {      ...      // Abort heap dump      return RETRY_LATER;    }}复制代码

这里的核心 *** 作就是调用了AndroID SDK的API DeBUG.dumPHProfData() 来生成 hprof 文件。

如果这个文件等于RETRY_LATER则表示生成失败,直接返回RETRY进行延时重试检测的 *** 作。如果不等于的话,则表示生成成功,最后会执行heapdumpListener的analyze()对新创建的HeapDump对象进行泄漏分析。由前面对AndroIDRefWatcherBuilder的ListenerServiceClass()的分析可知,heapdumpListener的实现 就是ServiceHeapDumpListener,接着看到ServiceHeapDumpListener的analyze方法。

17、ServiceHeapDumpListener#analyze()
@OverrIDe public voID analyze(@NonNull HeapDump heapDump) {    checkNotNull(heapDump, "heapDump");    HeapAnalyzerService.runAnalysis(context, heapDump, ListenerServiceClass);}复制代码

可以看到,这里执行了HeapAnalyzerService的runAnalysis()方法,为了避免降低app进程的性能或占用内存,这里将HeapAnalyzerService设置在了一个独立的进程中。接着继续分析runAnalysis()方法里面的处理。

public final class HeapAnalyzerService extends ForegroundServiceimplements AnalyzerProgressListener {    ...    public static voID runAnalysis(Context context, HeapDump heapDump,    Class<? extends AbstractAnalysisResultService> ListenerServiceClass) {        ...        ContextCompat.startForegroundService(context, intent);    }    ...    @OverrIDe protected voID onHandleIntentInForeground(@Nullable Intent intent) {        ...        // 1        HeapAnalyzer heapAnalyzer =            new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);        // 2        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpfile, heapDump.referenceKey,        heapDump.computeRetainedHeapSize);        // 3        AbstractAnalysisResultService.sendResultToListener(this, ListenerClassname, heapDump, result);    }        ...}复制代码

这里的HeapAnalyzerService实质是一个类型为IntentService的ForegroundService,执行startForegroundService()之后,会回调onHandleIntentInForeground()方法。注释1处,首先会新建一个HeapAnalyzer对象,顾名思义,它就是根据RefWatcher生成的heap dumps信息来分析被怀疑的泄漏是否是真的。在注释2处,然后会调用它的checkForLeak()方法去使用haha库解析 hprof文件,如下所示:

public @NonNull AnalysisResult checkForLeak(@NonNull file heapDumpfile,  @NonNull String referenceKey,  boolean computeRetainedSize) {    ...    try {    Listener.onProgressUpdate(READING_HEAP_DUMP_file);    // 1    HprofBuffer buffer = new MemoryMappedfilebuffer(heapDumpfile);    // 2    HprofParser parser = new HprofParser(buffer);    Listener.onProgressUpdate(PARSING_HEAP_DUMP);    Snapshot snapshot = parser.parse();    Listener.onProgressUpdate(DEDUPliCATING_GC_ROOTS);    // 3    deduplicateGcRoots(snapshot);    Listener.onProgressUpdate(FINDING_LEAKING_REF);    // 4    Instance leakingRef = findLeakingReference(referenceKey, snapshot);    // 5    if (leakingRef == null) {        return noleak(since(analysisstartNanoTime));    }    // 6    return findLeakTrace(analysisstartNanoTime, snapshot, leakingRef, computeRetainedSize);    } catch (Throwable e) {    return failure(e, since(analysisstartNanoTime));    }}复制代码

在注释1处,会新建一个内存映射缓存文件buffer。在注释2处,会使用buffer新建一个HprofParser解析器去解析出对应的引用内存快照文件snapshot。在注释3处,为了减少在AndroID 6.0版本中重复GCRoots带来的内存压力的影响,使用deduplicateGcRoots()删除了gcRoots中重复的根对象RootObj。在注释4处,调用了findLeakingReference()方法将传入的referenceKey和snapshot对象里面所有类实例的字段值对应的keyCandIDate进行比较,如果没有相等的,则表示没有发生内存泄漏,直接调用注释5处的代码返回一个没有泄漏的分析结果AnalysisResult对象。如果找到了相等的,则表示发生了内存泄漏,执行注释6处的代码findLeakTrace()方法返回一个有泄漏分析结果的AnalysisResult对象。

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

public class displayLeakService extends AbstractAnalysisResultService {    @OverrIDe    protected final voID onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {        ...        boolean resultSaved = false;        boolean shouldSaveResult = result.leakFound || result.failure != null;        if (shouldSaveResult) {            heapDump = renameHeapdump(heapDump);            // 1            resultSaved = saveResult(heapDump, result);        }        if (!shouldSaveResult) {            ...            showNotification(null, contentTitle, contentText);        } else if (resultSaved) {            ...            // 2            PendingIntent pendingIntent =                displayLeakActivity.createPendingIntent(this, heapDump.referenceKey);            ...            showNotification(pendingIntent, contentTitle, contentText);        } else {             onAnalysisResultFailure(getString(R.string.leak_canary_Could_not_save_text));        }    ...}@OverrIDe protected final voID onAnalysisResultFailure(String failureMessage) {    super.onAnalysisResultFailure(failureMessage);    String failureTitle = getString(R.string.leak_canary_result_failure_Title);    showNotification(null, failureTitle, failureMessage);}复制代码

可以看到,只要当分析的堆信息文件保存成功之后,即在注释1处返回的resultSaved为true时,才会执行注释2处的逻辑,即创建一个供用户点击跳转到displayLeakActivity的延时通知。最后给出一张源码流程图用于回顾本篇文章中LeakCanary的运作流程:

四、总结

性能优化一直是AndroID中进阶和深入的方向之一,而内存泄漏一直是性能优化中比较重要的一部分,AndroID Studio自身提供了MAT等工具去分析内存泄漏,但是分析起来比较耗时耗力,因而才诞生了LeakCanary,它的使用非常简单,但是经过对它的深入分析之后,才发现,简单的API后面往往藏着许多复杂的逻辑处理,尝试去领悟它们,你可能会发现不一样的世界。

五、学习笔记分享

为了方便大家更深入的学习AndroID相关源码已经第三框架。我整理了一份《AndroID相关源码解析》和《设计思想解读开源框架》,有需要的伙伴可以点赞+关注后,私信我领取!

有需要的伙伴可以点赞+关注后,私信我领取! 总结

以上是内存溢出为你收集整理的Android主流三方库源码分析:Leakcanary全部内容,希望文章能够帮你解决Android主流三方库源码分析:Leakcanary所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1052594.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存