LeakCanary源码分析

Posted 低调小一

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeakCanary源码分析相关的知识,希望对你有一定的参考价值。

基本使用

LeakCanary是用来检测android内存泄漏的工具。

在gradlew文件中引入:

dependencies 
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

在项目Application中增加如下代码:

public class ExampleApplication extends Application 

  @Override public void onCreate() 
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) 
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    
    LeakCanary.install(this);
    // Normal app init code...
  

源码分析

从LeakCanary使用示例可以看出,在项目的Application中只要增加一行LeakCanary.install(this)即可检测内存泄漏。那我们就从这行代码开始深入分析。

install()

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


/** Builder to create a customized @link RefWatcher with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) 
  return new AndroidRefWatcherBuilder(context);

install()方法里面首先创建了一个AndroidRefWatcherBuilder类。从名字就可以看出,这里使用了建造者模式。

DisplayLeakService.java

public class DisplayLeakService extends AbstractAnalysisResultService 
  @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) 
    // ......
  

  private boolean saveResult(HeapDump heapDump, AnalysisResult result) 
    // ......
  

  private HeapDump renameHeapdump(HeapDump heapDump) 
    // ......
  

  protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) 
  

DisplayLeakService本身继承自AbstractAnalysisResultService,而AbstractAnalysisResultService是一个IntentService.

public abstract class AbstractAnalysisResultService extends IntentService 

  private static final String HEAP_DUMP_EXTRA = "heap_dump_extra";
  private static final String RESULT_EXTRA = "result_extra";
  /**
   * 静态方法,便于调用
   */
  public static void sendResultToListener(Context context, String listenerServiceClassName,
      HeapDump heapDump, AnalysisResult result) 
    Class<?> listenerServiceClass;
    try 
      listenerServiceClass = Class.forName(listenerServiceClassName);
     catch (ClassNotFoundException e) 
      throw new RuntimeException(e);
    
    Intent intent = new Intent(context, listenerServiceClass);
    intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
    intent.putExtra(RESULT_EXTRA, result);
    context.startService(intent);
  

  public AbstractAnalysisResultService() 
    super(AbstractAnalysisResultService.class.getName());
  

  @Override protected final void onHandleIntent(Intent intent) 
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
    try 
      onHeapAnalyzed(heapDump, result);
     finally 
      //noinspection ResultOfMethodCallIgnored
      heapDump.heapDumpFile.delete();
    
  

  protected abstract void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result);

DisplayLeakService继承自IntentService,将分析Heap文件这种耗时操作放在子线程中执行,然后存储日志记录,发送Notification通知,避免出现ANR。

AndroidExcludedRefs.java

public enum AndroidExcludedRefs 
    // ######## Android SDK Excluded refs ########

AndroidExcludedRef是一个枚举类,用来定义白名单。在白名单里面的泄漏case,不会被记录,也不会通过DisplayLeakService通知给用户。如果我们发现有系统的内存泄漏,当前是无法解决的,就可以继承这个类,增加相应的白名单,避免开发时不必要的内存泄漏上报。

buildAndInstall()

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

首先,通过建造者方法创建出RefWatcher类。如果创建出来的refWatcher不等于DISABLE,说明当前线程是主线程,不是内存泄漏分析线程,那就接着进行初始化操作。

LeakCanary.enableDisplayLeakActivity

  public static void enableDisplayLeakActivity(Context context) 
    setEnabled(context, DisplayLeakActivity.class, true);
  

  public static void setEnabled(Context context, final Class<?> componentClass,
      final boolean enabled) 
    final Context appContext = context.getApplicationContext();
    executeOnFileIoThread(new Runnable() 
      @Override public void run() 
        setEnabledBlocking(appContext, componentClass, enabled);
      
    );
  

  public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
      boolean enabled) 
    ComponentName component = new ComponentName(appContext, componentClass);
    PackageManager packageManager = appContext.getPackageManager();
    int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
    // Blocks on IPC.
    packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
  

通过源码可以看出,这里是使用PackageManager来使得DisplayLeakActivity处于enable状态。之所以需要这样做,是因为LeakCanary在最初始是将DisplayLeakActivity设置为disable的,这样桌面上就不会显示LeakCanary的桌面图标。

    <activity
        android:theme="@style/leak_canary_LeakCanary.Base"
        android:name=".internal.DisplayLeakActivity"
        android:enabled="false"
        android:label="@string/leak_canary_display_activity_label"
        android:icon="@drawable/leak_canary_icon"
        android:taskAffinity="com.squareup.leakcanary.$applicationId"
        >
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

ActivityRefWatcher.install

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

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

        @Override public void onActivityStarted(Activity activity) 
        

        @Override public void onActivityResumed(Activity activity) 
        

        @Override public void onActivityPaused(Activity activity) 
        

        @Override public void onActivityStopped(Activity activity) 
        

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

        @Override public void onActivityDestroyed(Activity activity) 
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        
      ;

  private final Application application;
  private final RefWatcher refWatcher;

  /**
   * Constructs an @link ActivityRefWatcher that will make sure the activities are not leaking
   * after they have been destroyed.
   */
  public ActivityRefWatcher(Application application, RefWatcher refWatcher) 
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
  

  void onActivityDestroyed(Activity activity) 
    refWatcher.watch(activity);
  

  public void watchActivities() 
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  

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

通过源码可以看出,使用Application.ActivityLifecycleCallbacks来监听Activity的生命周期。为了保证不初始化两次监听,先移除了一次,然后再次添加监听。每次当Activity执行完onDestroy生命周期,LeakCanary就会获取到这个Activity,然后对它进行分析,查看是否存在内存泄漏。

RefWatcher类

成员变量

public final class RefWatcher 
  // 不在主线程时,用于标记的RefWatcher
  public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();
  // 主线程执行任务池
  private final WatchExecutor watchExecutor;
  // Debug控制器
  private final DebuggerControl debuggerControl;
  // 内部调用Runtime.getRuntime().gc(),手动触发系统GC
  private final GcTrigger gcTrigger;
  // 用于创建.hprof文件,用于储存heap堆的快照
  private final HeapDumper heapDumper;
  // 内存中没被gc的对象key集合
  private final Set<String> retainedKeys;
  // 与WeakReference配合使用的ReferenceQueue
  private final ReferenceQueue<Object> queue;
  // 监听heap分析
  private final HeapDump.Listener heapdumpListener;
  // 内存忽略白名单
  private final ExcludedRefs excludedRefs;

watch方法

在ActivityRefWatcher的Application.ActivityLifecycleCallbacks中,我们为每个Activity的onDestory方法增加了监听,在onDestory方法中调用如下代码:

ActivityRefWatcher.this.onActivityDestroyed(activity);

而ActivityRefWatcher的onActivityDestoryed方法实际调用的是RefWatcher的watch方法:

  void onActivityDestroyed(Activity activity) 
    refWatcher.watch(activity);
  

RefWatcher的watch方法源码如下:

  public void watch(Object watchedReference) 
    watch(watchedReference, "");
  
  public void watch(Object watchedReference, String referenceName) 
    if (this == DISABLED) 
      return;
    
    // 检测watchedReference是否为null
    checkNotNull(watchedReference, "watchedReference");
    // 检测referenceName是否为null
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    // 生成对应watchReference的唯一标识,并添加到retainedKeys集合中
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    // 构造WeakReference,这里的WeakReference是集合queue一起使用的
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 检测对象是否内存泄漏
    ensureGoneAsync(watchStartNanoTime, reference);
  

ensureGoneAsync

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

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) 
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 移除掉已经回收的对象的key
    removeWeaklyReachableReferences();

    // 如果retainedKeys已经不包含这个reference,说明对象已经回收了
    if (gone(reference)) 
      return DONE;
    

    // 手动触发一次系统GC
    gcTrigger.runGc();
    // 再次移除GC后已经回收的对象的key
    removeWeaklyReachableReferences();
    if (!gone(reference)) 
      // 如果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);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    
    return DONE;
  

首先执行removeWeaklyReachableReferences()方法尝试着从弱引用的队列中获取待分析对象,如果不为空,说明该对象已经被系统回收了,就将retainedKeys中对应的key去掉。如果被系统回收了,直接就返回DONE;如果没有被系统回收,可能存在内存泄漏,为了保证结果的准确性,还需要调用gcTrigger.runGc(),手动触发一下系统GC,然后再尝试移除待分析对象,如果还存在,说明存在内存泄漏。

gcTrigger.runGc

我们看一下LeakCanary是如何触发GC的:

  GcTrigger DEFAULT = new GcTrigger() 
    @Override public void runGc() 
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/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 perfom 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()不同,这里调用的是Runtime.getRuntime().gc()方法,enqueueReference()方法通过沉睡100毫秒给系统GC时间,System.runFinalization()是强制调用已经失去引用的对象的finalize方法。

总结

通过对LeakCanary的源码分析,我们可以知道,LeakCanary检测内存泄漏分为三个步骤,分别是:

监听

在Android中,当一个Activity走完onDestroy生命周期后,说明该页面已经被销毁了,应该被系统GC回收。通过Application.registerActivityLifecycleCallbacks()方法注册Activity生命周期的监听,每当一个Activity页面销毁时候,通过在onDestory生命周期回调里获取到这个Activity,并去检测这个Activity是否真的被系统GC。

检测

通过WeakReference + ReferenceQueue来判断对象是否被系统GC回收,WeakReference 创建时,可以传入一个 ReferenceQueue 对象。当被 WeakReference 引用的对象的生命周期结束,一旦被 GC 检查到,GC 将会把该对象添加到 ReferenceQueue 中,待ReferenceQueue处理。当 GC 过后对象一直不被加入 ReferenceQueue,它可能存在内存泄漏。当我们初步确定待分析对象未被GC回收时候,手动触发GC,二次确认。

分析

分析使用了Square的开源库haha,利用它获取当前内存中的heap堆的快照t,然后通过待分析对象去snapshot里面去查找最短路径,并通过系统通知告知用户。

以上是关于LeakCanary源码分析的主要内容,如果未能解决你的问题,请参考以下文章

LeakCanary核心原理源码浅析

全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !

全新 LeakCanary 2 ! 完全基于 Kotlin 重构升级 !

LeakCanary 内存泄露监测原理研究

LeakCanary源码学习笔记

工作中遇到的Android内存优化问题-leakcanary源码解析