源码分析基于:1.6.3

对于Android开发者而言,内存泄漏是一种很常见的问题。LeakCanary就是捕获内存泄漏的一把利器。我们在这里就分析一下它的工作原理。

 

一、使用方法

使用方法就是我们在Application中添加代码:

if (LeakCanary.isInAnalyzerProcess(this)) {            // This process is dedicated to LeakCanary for heap analysis.            // You should not onStart your app in this process.            return;        }        LeakCanary.install(this); 

 

看到这些估计你也会和我一样,这样我们就能捕获到内存泄露了么?不禁会产生这样的疑问。我们就带着疑问阅读一下源码寻找答案吧。

 

二、内存泄漏的检测过程:

LeakCanary这个类是一个工具类,我们先看一下LeakCanary.install(this)这个方法的源码:

/**   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity   * references (on ICS+).   */  public static @NonNull RefWatcher install(@NonNull Application application) {    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())        .buildAndInstall();  }

 

该方法中refWatcher(application)方法获取AndroidRefWatcherBuilder实例对象;“.listenerServiceClass(DisplayLeakService.class)”方法是对内存泄漏分析结果的监听;“.buildAndInstall()”方法用于生成RefWatcher对象,关于该方法的详细代码如下:

  /**   * Creates a {@link RefWatcher} instance and makes it available through {@link   * LeakCanary#installedRefWatcher()}.   *   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.   *   * @throws UnsupportedOperationException if called more than once per Android process.   */  public @NonNull RefWatcher buildAndInstall() {  //保证RefWatcher的唯一性    if (LeakCanaryInternals.installedRefWatcher != null) {      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");    }//如果没有创建过RefWatcher,则创建一个;    RefWatcher refWatcher = build();//非空置对象(内存分析相关工具对象都是空壳对象)    if (refWatcher != DISABLED) {//是否允许使用内置展示页面展示内存泄露信息      if (enableDisplayLeakActivity) {        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);      }  //是否允许监测activity      if (watchActivities) {        ActivityRefWatcher.install(context, refWatcher);      }  //是否允许检测fragment      if (watchFragments) {        FragmentRefWatcher.Helper.install(context, refWatcher);      }    }//RefWatcher对象赋值    LeakCanaryInternals.installedRefWatcher = refWatcher;    return refWatcher;  }

 

在这里RefWatcher就是内存泄漏的监控器。该方法就是生成RefWatcher对象,该框架默认允许检测Activity和Fragment的内存泄漏,那么它又是怎么监控的呢?原理由是啥?以什么为判断内存泄漏的标准呢?

我们看一下ActivityRefWatcherinstall()方法的逻辑:

  public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {    Application application = (Application) context.getApplicationContext();    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);//Application注册生命周期回调    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);  }  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =      new ActivityLifecycleCallbacksAdapter() {        @Override public void onActivityDestroyed(Activity activity) {          refWatcher.watch(activity);        }      };

 

lifecycleCallbacks是该类中的成员变量,通过注册生命周期回调RefWatcher就可以通过页面销毁的方法回调触发RefWatcherwatch()方法。我们先不管watch()方法中的具体逻辑,下面在分析一下fragment中的监测机制,下面我们分析一下FragmentRefWatcher.Helper这个内部类的源码:

  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 fragmentRefWatchers = new ArrayList<>();   //大于26的Android版本使用AndroidOFragmentRefWatcher      if (SDK_INT >= O) {        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));      }   //否则使用SupportFragmentRefWatcher      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);       Application application = (Application) context.getApplicationContext();  //注册生命周期回调      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);    }     private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =        new ActivityLifecycleCallbacksAdapter() {          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {            for (FragmentRefWatcher watcher : fragmentRefWatchers) {//对应的FragmentManager注册回调              watcher.watchFragments(activity);            }          }        };     private final List fragmentRefWatchers;     private Helper(List fragmentRefWatchers) {      this.fragmentRefWatchers = fragmentRefWatchers;    }  }

 

该类中主要就是通过Activity的生命周期回调的onActivityCreated()方法,来获取activity实例对象,具体需要分析watcher.watchFragments(activity)中的逻辑,由于存在两种fragment对应的watcher,我们在这里只分析SupportFragmentRefWatcher中对应的代码逻辑。

  @Override public void watchFragments(Activity activity) {    if (activity instanceof FragmentActivity) {      FragmentManager supportFragmentManager =          ((FragmentActivity) activity).getSupportFragmentManager();      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);    }  }

 

方法中获取FragmentManager实例对象,并注册生命周期方法。对于Android版本大于26的,请阅读AndroidOFragmentRefWatcher中的相关源码,原理是一致的。通过上面的源码我们分析得知,对于Activity而言,是在activityonDestory()方法内进行监测的;对于fragment而言,在生命周期回调方法内的onFragmentViewDestroyed()onFragmentDestroyed()方法内进行监测,在这里onFragmentViewDestroyed()方法对应于Fragment的onDestroyView()方法,onFragmentDestroyed()方法对应于Fragment#onDestroy()方法。

 

我们知道了LeakCanary的监测位置了,我们下面就通过源码分析一下它是怎么做到监测内存泄漏的。下面是RefWatcher类中的watch()方法的源码:

/**   * 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();//生成随机的UUID作为该引用对象的key值    String key = UUID.randomUUID().toString();//添加的集合中    retainedKeys.add(key);//保存引用信息    final KeyedWeakReference reference =        new KeyedWeakReference(watchedReference, key, referenceName, queue); //判断引用是否存在    ensureGoneAsync(watchStartNanoTime, reference);  }

 

在该方法中,将之前生命周期中的方法中拿到的activity对象实例或者fragment对象实例进行保存并进行相关的处理,在保存时会生成一个唯一的key,后面会通过key值进行相应的业务逻辑。下面我们看一下ensureGoneAsync()方法相关的逻辑,源码如下:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {    watchExecutor.execute(new Retryable() {      @Override public Retryable.Result run() {        return ensureGone(reference, watchStartNanoTime);      }    });  }

 

该方法中watchExecutorAndroidWatchExecutor的一个具体实例对象,最终是通过该实现类的execute()方法去执行引用相关的逻辑,这部分后面会分析。同时需要注意在excute()方法的内部类中run()方法会通过ensureGone()方法返回Retryable.Result的值。这个值会在后面的分析中用到。我们在这里先看一下AndroidWatchExecutor的具体实现。AndroidWatchExecutor是WatchExecutor接口的具体实现类,该接口主要用来执行Rertyable对象,并根据需要进行重试操作。我们看一下AndroidWatchExecutor的源码:

/** * {@link WatchExecutor} suitable for watching Android reference leaks. This executor waits for the * main thread to be idle then posts to a serial background thread with the delay specified by * {@link AndroidRefWatcherBuilder#watchDelay(long, TimeUnit)}. */ 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) {    mainHandler = new Handler(Looper.getMainLooper());    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);    handlerThread.start();    backgroundHandler = new Handler(handlerThread.getLooper());    this.initialDelayMillis = initialDelayMillis;    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;  }   @Override public void execute(@NonNull Retryable retryable) {  //当前线程是否为主线程    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {      waitForIdle(retryable, 0);    } else {      postWaitForIdle(retryable, 0);    }  }   private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {    mainHandler.post(new Runnable() {      @Override public void run() {        waitForIdle(retryable, failedAttempts);      }    });  }   private void waitForIdle(final Retryable retryable, final int failedAttempts) {    // This needs to be called from the main thread.//主线程中进行调用,当主线程的消息队列空闲时调用    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {      @Override public boolean queueIdle() {  //转移到后台线程进行        postToBackgroundWithDelay(retryable, failedAttempts);        return false;      }    });  }   private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);    long delayMillis = initialDelayMillis * exponentialBackoffFactor;//延迟一定时间,再执行;    backgroundHandler.postDelayed(new Runnable() {      @Override public void run() {        Retryable.Result result = retryable.run();        if (result == RETRY) {          postWaitForIdle(retryable, failedAttempts + 1);        }      }    }, delayMillis);  }}

 

AndroidWatchExecutor 是Android平台中内存泄漏的监测工具类。该类的主要方法就是execute()方法,其主要逻辑是判断当前正在执行的线程是否为主线程,如果是主线程,则通过IdleHandler监测主线程是否空闲,当空闲的时候,执行postToBackgroundWithDelay方法,在此方法中,先通过计算exponentialBackoffFactor(补偿因子),并通过该因子计算出需要延迟的时间(delayMillis ),说实话为什么这么算没想明白。最后通过backgroundHandler延迟执行一条线程去获取Retryable.Result的结果,通过是否需要重试也就是“result == RETRY”来判断是否需要继续postWaitForIdle方法,同时在重试的时候failedAttempts参数会加1。那么我们不禁会思考,retryable.run()的结果,也就是Retryable.Result是在什么时候改变的呢?这个我们在RefWatcher中的ensureGoneAsync()方法的分析中提到,result的值是在ensureGone()方法返回的。好了就是那么清晰。当正在执行的线程非主线程的的时候postWaitForIdle()方法被执行,这里就不再说明了。

 

下面我们看一下RefWatcher中ensureGone()方法的源码:

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {    //GC开始检测时间    long gcStartNanoTime = System.nanoTime();    //从开始watch到GC开始检测的时间差    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);     //移除已经被GC的引用信息    removeWeaklyReachableReferences();     //如果调试模式,则后续重试    if (debuggerControl.isDebuggerAttached()) {      // The debugger can create false leaks.      return RETRY;    }     //如果不包含应用信息,则表示监测已经完成,没有发生泄漏;    if (gone(reference)) {      return DONE;    }    //触发GC    gcTrigger.runGc();    //再次确认移除已经被GC的引用信息    removeWeaklyReachableReferences();    //如果队列中还存在引用信息,    if (!gone(reference)) {      long startDumpHeap = System.nanoTime();      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);       //使用Dumper类dump当前堆内存中的对象信息      File heapDumpFile = heapDumper.dumpHeap();      if (heapDumpFile == RETRY_LATER) {        // Could not dump the heap.        return RETRY;      }      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);       //将hprof文件和reference引用信息构造HeapDump对象      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)          .referenceName(reference.name)          .watchDurationMs(watchDurationMs)          .gcDurationMs(gcDurationMs)          .heapDumpDurationMs(heapDumpDurationMs)          .build();      //分析dump信息      heapdumpListener.analyze(heapDump);    }    return DONE;  }

 

要理解LeackCanary是怎么定义内存泄漏的呢?此方法就是关键。同时不了解Java中的弱引用,强引用的概念的可以查阅一下这方面的资料,这里就不在进行过多的解释了。首先该方法先调用了removeWeaklyReachableReferences()方法,该方法就是移除已经被GC的引用信息,在该方法里就是看queue队列中是否还存在引用的信息KeyedWeakReference,如果存在就从retainedKeys中进行移除。在RefWatcher类中的watch()方法中已经将观察对象watchReference对应的key保存在retainedKeys中,同时关联到了ReferenceQueue的实例对象queue。当弱引用持有的对象被GC之后,与之关联的引用信息KeyedWeakReference的对象reference就会被添加到关联的队列中queue.所以可以通过queue中是否存在对应的引用信息来判断是否发生内存泄漏。下面说一下为什么在开启Debug模式下需要返回RETRY,那是因为在该模式下对象引用的时间会变长。我们继续向下分析,gone(refreence)方法就是看retainedKeys是否包含相应的引用的信息,如果不存在则说明就完成了GC,本次检测也告一段落。否则, 通过gcTrigger.runGc()手动触发一次GC,并在此通过removeWeaklyReachableReferences()方法进行引用信息的移除。如果还存在该引用信息那么说明app当前发生了内存泄漏。后续就是通过heapDumper.dumpHeap()将当前堆内存中的信息保存下来,并通过heapdumpListener.analyze(heapDump)分析获取的dump信息。到此,相信大家就清楚了我们Android开发中内存泄漏检测神器LeakCanary的检测原理了吧。

 

后续将在下一篇文章中分析该框架是怎么分析内存泄漏的,敬请期待~

更多相关文章

  1. 安卓修改屏幕超时后,系统不进入休眠的方法。
  2. 初学Android的一些注意事项
  3. 【Android】项目常用功能集锦(一)
  4. Android(安卓)Thread第二次Thread.start()报错的疑问
  5. android跟服务器使用json传递数据
  6. android实现倒计时功能的方法
  7. Android获取手机SIM卡运营商信息的方法
  8. Android异步更新UI的方式之使用Handler的post(Runnabel r)方法
  9. Android(安卓)利用Gson生成或解析json

随机推荐

  1. 动态图表揭秘:“动”的关键——取数
  2. 利用Excel学习Python:变量
  3. 如何制作高大上的图表
  4. 从Excel的数据类型说Python
  5. 数据分析,除了Excel透视表,还有什么工具?
  6. 列表是个什么鬼?
  7. 如何培养数据分析的思维?
  8. 新手如何学习SQL
  9. 2019年终总结
  10. Python数据结构:字典那些事儿