LeakCanary源码解析-很值得我们学习的一款框架

前言

一直在使用这个框架,但是一直没有注意这个框架的实现原理。使用过这款框架的人应该都知道,LeakCanary是一款能够帮助开发者检查内存泄漏的开源库,只需要简单配置,就可以当使用过程中产生内存泄漏时,弹出通知,并且可以我们可以查看详细的引用链,帮助我们进行内存泄漏的分析。

项目预览

LeakCanary源码解析-很值得我们学习的一款框架_第1张图片
项目结构

这里专门放了一张LeakCanary的项目的目录结构,可以看到,LeakCanary的项目目录还是很考究的,总体分为了5个模块,每个都专门负责一个功能。

1.analyzer 内存泄漏分析功能模块,内存泄漏的路径分析就是该模块,haha库就是在该模块中使用
2.watcher 内存泄漏监控模块,监控并且发现内存泄漏的就是该模块
3.android 将内存泄漏监控和Android的生命周期绑定,实现Android中的内存泄漏监听
4.android-no-op 空壳模块,实现真正发布realeas包后,不进行内存泄漏监控,损耗性能
5.sample demo模块

从上面的分析可以看出,LeakCanary这款内存泄漏框架的目录结构真的很考究,按照LeakCanary的功能体积,完全没必要拆分的这么散,但是这样做,充分将LeakCanary的功能发挥到最大化,为什么这么说哪?
熟悉LeakCanary原理的都知道,LeankCanary其实是利用Java的弱引用特性,加上JVM回收一定的机制,实现内存泄漏的监控的,也就是说,LeakCanary并不是一款和Android死绑定的一款开源框架,所以这时候LeakCanary这样的目录结构就体现出了它的优点,我们完全可以将watcher模块或者analyzer模块拆分出来,处理其他以Java语言作为开发语言的项目的内存泄漏分析。

源码分析

还是老方式,一个好的框架肯定有一个好的外观类来统领入口,LeakCanary当然不能少,一般我们的使用方式就是:

protected void setupLeakCanary() {
    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);
  }

当然最核心的就是这句代码LeakCanary.install(this);前面的LeakCanary.isInAnalyzerProcess(this)这里先暂不分析(当然后面会分析的,这里面有一个很棒的方法)

public static RefWatcher install(Application application) {
    //创建RefWatcher
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            //设置已知的内存泄漏问题,或者系统的内存泄漏问题
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

这里可以看到,总体上来看分为了四个步骤:

  1. 创建了RefWatcher(也就是检测内存泄漏的类)
  2. 设置了内存泄漏的通知Service(通知)
  3. 设置了需要忽略的已知的系统级别的内存泄漏(可以自定义)
  4. 开始监听

接下来就分别看看每一个步骤。

第一个步骤

/**
   * 创建一个builder对象
   */
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

第一个步骤没什么好说的,创建了一个专门为Android使用的Watcher的Builder类。后面我们看的很多方法都基于这个类。

第二个步骤

public AndroidRefWatcherBuilder listenerServiceClass(
      Class listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }
  
  public ServiceHeapDumpListener(Context context,
      Class listenerServiceClass) {
    setEnabled(context, listenerServiceClass, true);
    setEnabled(context, HeapAnalyzerService.class, true);
    this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }
  
  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
    this.heapDumpListener = heapDumpListener;
    return self();
  }
  
  @SuppressWarnings("unchecked")
  protected final T self() {
    return (T) this;
  }

接下来这个地方还是需要我们注意到,这里new了一个ServiceHeapDumpListener,在heapDumpListener方法里将new到listener赋值给了this.heapDumpListener
这里有一个小细节挺值得我们注意的,这里由于是泛型,所以不能直接返回this,这里统一返回了self()方法,统一在self方法里做强制转换和unchecked操作。
通过这里的方法我们可以知道,我们将DisplayLeakService.class类,设置给了AndroidRefWatcherBuilder的一个自定义Listener变量。

第三个步骤:设置已知的内存泄漏问题,或者系统的内存泄漏问题

这里我们就看一下AndroidExcludedRefs.createAppDefaults().build()这个方法。

public static ExcludedRefs.Builder createAppDefaults() {
    return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
  }
  public static ExcludedRefs.Builder createBuilder(EnumSet 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.class类中定义的已知的一些系统级别的bug,得到一个集合,在后面发现内存泄漏的时候会进行忽略操作。

第四个步骤:开始进行监听操作。

public RefWatcher buildAndInstall() {
    //只能创建一次
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //创建RefWatcher
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {

      LeakCanary.enableDisplayLeakActivity(context);
      //默认为true
      if (watchActivities) {
        //注意,在这里通过监听Application,监听Activity的生命周期
        ActivityRefWatcher.install((Application) context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

可以看到,首先进行了判空,单例的思想还是很重要的。
下面这个build()方法还是很重要的。

public final RefWatcher build() {
    // 判断install是否在Analyzer进程里,重复执行
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    //用于排除某些系统bug导致的内存泄露
    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      excludedRefs = defaultExcludedRefs();
    }
    //用于分析生成的dump文件,找到内存泄露的原因
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
    //用于查询是否正在调试中,调试中不会执行内存泄露检测
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
    //用于在产生内存泄露室执行dump 内存heap
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }
    //执行内存泄露检测的executor
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      //创建默认的监听内存泄漏的线程池
      watchExecutor = defaultWatchExecutor();
    }
    //用于在判断内存泄露之前,再给一次GC的机会
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }

这个build方法这里我们需要注意,我们在最开始的生成了AndroidRefWatcherBuilder,这个是继承于RefWatcherBuilder类的,但是这里的build()方法是父类的方法,也就是RefWatcherBuilder的方法,但是在build()内的许多调用都是AndroidRefWatcherBuilder重写的方法。
首先来看第一个方法isDisabled()

@Override protected boolean isDisabled() {
    //用于判断服务进程是否在前台,重要
    return LeakCanary.isInAnalyzerProcess(context);
  }

可以看到,这里又用到了我们前面提到的一个方法,是用于判断当前进程是否在前台,这里因为分析主流程,所以先不做分析。

@Override protected ExcludedRefs defaultExcludedRefs() {
    return AndroidExcludedRefs.createAppDefaults().build();
  }

接下来看到是设置了Android特有的一些系统的内存泄漏,和前面分析的一致。

//用于分析生成的dump文件,找到内存泄露的原因
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
    @Override protected HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
  }

因为在install()方法中已经设置了用于发送内存泄漏通知的Service,这里变不为null,不然其实default的和初始化的也是相同的。

//用于查询是否正在调试中,调试中不会执行内存泄露检测
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
    
    @Override protected DebuggerControl defaultDebuggerControl() {
    return new AndroidDebuggerControl();
  }
  
  public final class AndroidDebuggerControl implements DebuggerControl {
  @Override public boolean isDebuggerAttached() {
    return Debug.isDebuggerConnected();
  }
}

这里其实也挺值得我们学习的,这里当是调试的时候,便不会进行内存泄漏检测,而如何确定是在进行调试,这里可以看到使用了Debug.isDebuggerConnected()方法。

//用于在产生内存泄露室执行dump 内存heap
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }
    
    @Override protected HeapDumper defaultHeapDumper() {
    LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
    return new AndroidHeapDumper(context, leakDirectoryProvider);
  }

这里就是生产内存泄漏的文件的。

//执行内存泄露检测的executor
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      //创建默认的监听内存泄漏的线程池
      watchExecutor = defaultWatchExecutor();
    }
    
    @Override protected WatchExecutor defaultWatchExecutor() {
    //默认线程池,5s
    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  }
  
  public AndroidWatchExecutor(long initialDelayMillis) {
    //主线程Handler
    mainHandler = new Handler(Looper.getMainLooper());
    //这里new了一个HandlerThread,也就是一个异步线程,内部封装好了looper.prepare()等操作
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    //handlerThread内部的handler
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }

可以看到这里虽然叫做看似像是线程池,其实也是利用了Android官方的基础组件,这里可以看到快速的创建了一个主现场handler和一个HanlderThread,和HandlerThread内部的Handler。具体HanlderThread是什么,这里放上这个类。

/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
}

可以看到就是一个Thread只不过内部封装好了Android使用多线程Hanlder的一系列操作。

//用于在判断内存泄露之前,再给一次GC的机会
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }
    
    public interface GcTrigger {
  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()
      //注意这里和System.gc()的区别
      Runtime.getRuntime().gc();
      //等待100毫秒
      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();
      }
    }
  };

  void runGc();
}

接下来算是LeakCanary的一个比较特殊的地方,接下来看到,设置了一个和GC相关的一个类,最终我们会发现是使用的上面放的DEFAULT,这里可以看到一个很特殊的一点,这里使用了一个方法Runtime.getRuntime().gc(),而且也看到了官方对于此处的注释这里引用了AOSP的一段代码,System.gc()并不会每次都真正调用回收,所以使用Runtime.getRuntime().gc();这里就是我们平常不会注意到的知识点,这里需要我们区分一下两个的区别,我自己看了一下两个的源码,并没有发现两个内部的不同(不知道是不是我看的方式的问题),我通过查询,网上对于这两个方法的区别总体是这样解释的。

/**
 * Indicates to the VM that it would be a good time to run the
 * garbage collector. Note that this is a hint only. There is no guarantee
 * that the garbage collector will actually be run.
 */
public static void gc() {
    boolean shouldRunGC;
    synchronized(lock) {
        shouldRunGC = justRanFinalization;
        if (shouldRunGC) {
            justRanFinalization = false;
        } else {
            runGC = true;
        }
    }
    if (shouldRunGC) {
        Runtime.getRuntime().gc();
    }
}

以上是引用一篇不错的LeakCanary的源码分析中关于gc()源码(不知道为什么我自己看不到这样的源码,如果有人知道,评论告诉我一下,谢谢~~),从这里就可以看出,System.gc()的实质其实是调用Runtime.getRuntime().gc(),只不过做了一些多线程同步的判断,所以,我们调用System.gc()并不会一定出发JVM的GC操作。
到此build()方法到这里就分析完了,通过上面的分析我们会发现,到目前为止基本上都是做的准备工作,接下来就是LeakCanary的核心操作,检测内存泄漏。

//开启LeakCanary的Activity,使图标显示
LeakCanary.enableDisplayLeakActivity(context);

  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);
  }

下面这行代码其实作用是开启LeakCanary的应用图标,使其显示。我们可以看到,这里传入了DisplayLeakActivity.class类,最后通过packageManager.setComponentEnabledSetting这个方法,将Activity设置为COMPONENT_ENABLED_STATE_ENABLED状态。这样设置有什么作用哪,我们来看一下AndroidManifest.xml文件。


      
        
        
      
    

可以看到,这里在清单文件中,将DisplayLeakActivityenabled。这里还有一个需要我们注意到点,这里使用了线程池

private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");

public static void executeOnFileIoThread(Runnable runnable) {
    fileIoExecutor.execute(runnable);
  }
  
  public static Executor newSingleThreadExecutor(String threadName) {
    return Executors.newSingleThreadExecutor(new LeakCanarySingleThreadFactory(threadName));
  }

可以看到这里创建了Java中的newSingleThreadExecutor线程池,具体特点这里就不详细介绍了,简单的说就是一个唯一的线程,顺序的执行任务。

监听生命周期

//默认为true
      if (watchActivities) {
        //注意,在这里通过监听Application,监听Activity的生命周期
        ActivityRefWatcher.install((Application) context, refWatcher);
      }

前面的一系列分析,这里终于可以开始监听生命周期,也就是检测内存泄漏的地方了。

public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }
  
  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    //注册监听回调
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

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

可以看到这里避免重复监听,因为内部是使用一个ArrayList进行保存lifecycleCallbacks,所以为了和之前的单例保持一致,这里就做移除操作。这里其实我们就要关注,这里是如果实现监听的。

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) {
            //onDestroy的时候回调
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

可以看到这里利用了Android中的Application的特性,注册了Application.ActivityLifecycleCallbacks监听器,在Activity的onDestroy方法中,调用了ActivityRefWatcher.this.onActivityDestroyed(activity);方法。

void onActivityDestroyed(Activity activity) {
      //Activity在onDestroy的时候回调
    refWatcher.watch(activity);
  }

这里就调用了我们之前构建的refWatcher对象的watch方法。

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    //获得当前时间
    final long watchStartNanoTime = System.nanoTime();
    //生成一个唯一的key
    String key = UUID.randomUUID().toString();
    //保存这个key
    retainedKeys.add(key);
    //将检查内存泄漏的对象保存为一个弱引用,注意queue
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //异步开始分析这个弱引用
    ensureGoneAsync(watchStartNanoTime, reference);
  }

这里可以看到,这里使用了当前时间作为唯一标示,这里获取时间的方法也很讲究System.nanoTime()这个和System.currentTimeMillis()的区别也很简答,这里引用别人的分析简单的说明一下:

平时产生随机数时我们经常拿时间做种子,比如用System.currentTimeMillis的结果,但是在执行一些循环中使用了System.currentTimeMillis,那么每次的结果将会差别很小,甚至一样,因为现代的计算机运行速度很快。后来看到java中产生随机数函数以及线程池中的一些函数使用的都是System.nanoTime,下面说一下这2个方法的具体区别。
System.nanoTime提供相对精确的计时,但是不能用他来计算当前日期
System.nanoTime与System.currentTimeMillis的区别

接下来利用UUID.randomUUID().toString()生成了一个唯一的key保存在retainedKeys集合中,而这个retainedKeys是一个Set数据类型。

// 用于判断弱引用所持有的对象是否已被GC,如果被回收,会存在队列中,反之,没有存在队列中则泄漏了
  private final ReferenceQueue queue;
final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

final class KeyedWeakReference extends WeakReference {
  public final String key;
  public final String name;

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

下面这个就是重点了,可以说是LeakCanary的核心,可以看到这里new了一个KeyedWeakReference对象,这里传入了我们观察的对象,也就是Activity,传入了一个queue,而这个queue可以看到是一个ReferenceQueue。而这里KeyedWeakReference继承了WeakReference,也就是我们熟知的弱引用,熟悉弱引用特性的应该都知道,当弱引用被回收的时候,会被放入一个队列里,这里就是利用这个特性,使用弱引用持有一个Activity对象

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

接下来就开始了真正开始分析的过程了,可以看到这里使用了我们前面创建的HandlerThread这个异步线程进行操作。

  // 避免因为gc不及时带来的误判,leakcanay会手动进行gc,进行二次确认进行保证
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    //System.currentTimeMillis,那么每次的结果将会差别很小,甚至一样,因为现代的计算机运行速度很快
    //检测系统的耗时所用,所以使用System.nanoTime提供相对精确的计时
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //第一次判断,移除此时已经被回收的对象
    removeWeaklyReachableReferences();
    //调试的的时候是否开启内存泄漏判断,默认是false
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //如果此时该对象已经不再retainedKeys中说明第一次判断时该对象已经被回收,不存在内存泄漏
    if (gone(reference)) {
      return DONE;
    }
    //如果当前检测对象还没有被回收,则手动调用gc
    gcTrigger.runGc();
    //再次做一次判断,移除被回收的对象
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      //如果该对象仍然在retainedKey中,则说明内存泄漏了,进行分析
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // dump出来heap,此时认为内存确实已经泄漏了
      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这个方法,也就是在Activity执行了onDestroy之后,执行这个方法,进行第一次判断

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;
    //如果此时已经在queue中,说明已经被回收
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      //则从retainedKeys中移除
      retainedKeys.remove(ref.key);
    }
  }

这里可以看到,遍历了刚才传入了的弱应用队列,如果弱引用队列中存在引用,说明改对象已经被回收,然后通过存储的唯一性key,从retainedKeys中移除。

//如果此时该对象已经不再retainedKeys中说明第一次判断时该对象已经被回收,不存在内存泄漏
    if (gone(reference)) {
      return DONE;
    }
    private boolean gone(KeyedWeakReference reference) {
    //retainedKeys不存在该对象的key
    return !retainedKeys.contains(reference.key);
  }

执行完第一次判断后,这里就判断retainedKeys中是否存在该对象的key,如果不存在,说明该对象已经成功被GC回收,则表明这时是不存在内存泄漏的,则直接return.

//如果当前检测对象还没有被回收,则手动调用gc
    gcTrigger.runGc();
    //再次做一次判断,移除被回收的对象
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      ...
    }
    
    
    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()
      //注意这里和System.gc()的区别
      Runtime.getRuntime().gc();
      //等待100毫秒
      enqueueReferences();
      System.runFinalization();
    }
    。。。
  };

如果这时还存在retainedKeys说明可能存在内存泄漏,熟悉GC的应该都知道,GC的操作并不是实时的,所以第一次虽然该对象还没有被回收,也可能是由于GC没有触发导致的,所以可以看到这里手动触发了GC操作,这里就要联系到我们前面分析的Runtime.getRuntime().gc()。这样就通了,这里手动调用了Runtime.getRuntime().gc()方法,强制触发GC。然后在执行一次removeWeaklyReachableReferences();方法。再重复做一次判断,弱引用是否被回收,存在于引用队列中。

if (!gone(reference)) {
      //如果该对象仍然在retainedKey中,则说明内存泄漏了,进行分析
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // dump出来heap,此时认为内存确实已经泄漏了
      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));
    }

可以看到,当强制GC后,进行第二次判断后,还是存在retainedKey中,这里就认为产生了内存泄漏,这时候就开始进行分析,这里就利用了LeakCanary使用到的另一个库Haha库,用于分析引用路径。首先这里的heapdumpListener的实现类就是我们前面提到的ServiceHeapDumpListener

@Override protected HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
  }
  
  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    //开启HeapAnalyzerService,是一个HandlerService
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
  
  public static void  runAnalysis(Context context, HeapDump heapDump,
      Class listenerServiceClass) {
    //开启一个IntentService用于分析内存泄漏
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    //将回调的监听Service的class传入,分析完成,回调到这个service
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    //收集的文件
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

这里我们就注意几个关键点就行:

  1. 默认创建的是ServiceHeapDumpListener,传入了DisplayLeakService.class类对象。
  2. 执行analyze方法的实质就是开启HeapAnalyzerService这个Service,并且将收集的heapDump传入用于分析。
@Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    //分析获得结果,haha库就在内部调用的,注意分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //回调结果
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

这时我们看一下HeapAnalyzerService的onHandleIntent方法,这里我们需要注意的就是heapAnalyzer.checkForLeak,这个方法就是LeakCanary内部分析引用路径的方法,内部使用了Haha库,当然这个过程在一个IntentService中,当然是异步的。

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);
    }
    //启动Service通知,抽象类,DisplayLeakService
    Intent intent = new Intent(context, listenerServiceClass);
    //将分析的信息传回给Service,发出内存泄漏的通知
    intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
    intent.putExtra(RESULT_EXTRA, result);
    context.startService(intent);
  }

当分析完结果后,可以看到这里,利用反射,创建了我们之前传入的DisplayLeakService对象,然后将分析接口发送给DisplayLeakService

@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);

    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);
      resultSaved = saveResult(heapDump, result);
    }

    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;

    if (!shouldSaveResult) {
      //无泄露
      contentTitle = getString(R.string.leak_canary_no_leak_title);
      contentText = getString(R.string.leak_canary_no_leak_text);
      pendingIntent = null;
    } else if (resultSaved) {
      //获得一个pendingIntent
      pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

      if (result.failure == null) {
        String size = formatShortFileSize(this, result.retainedHeapSize);
        String className = classSimpleName(result.className);
        if (result.excludedLeak) {
          contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
        } else {
          contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
        }
      } else {
        contentTitle = getString(R.string.leak_canary_analysis_failed);
      }
      contentText = getString(R.string.leak_canary_notification_message);
    } else {
      contentTitle = getString(R.string.leak_canary_could_not_save_title);
      contentText = getString(R.string.leak_canary_could_not_save_text);
      pendingIntent = null;
    }
    // New notification id every second.
    int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
    //显示一个通知,显示内存泄漏
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }

这里由于DisplayLeakService继承了AbstractAnalysisResultService,而AbstractAnalysisResultService继承了IntentService,最终会调onHeapAnalyzed方法,这里可以看到当存在内存泄漏的时候,会创建一个pendingIntent用于后面通知的点击事件跳转,而后发送了一个通知notification

LeakCanary.isInAnalyzerProcess(context);

这里我们再来看一下前面提到的LeakCanary提供给我们的一个比较不错的工具类,用于判断当前进程是否在后台。

public static boolean isInAnalyzerProcess(Context context) {
    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
    // This only needs to be computed once per process.
    if (isInAnalyzerProcess == null) {
      //判断进程是否在后台,重要
      isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
      LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
    }
    return isInAnalyzerProcess;
  }
  
  public static boolean isInServiceProcess(Context context, Class serviceClass) {
    PackageManager packageManager = context.getPackageManager();
    PackageInfo packageInfo;
    try {
      packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
    } catch (Exception e) {
      CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
      return false;
    }
    String mainProcess = packageInfo.applicationInfo.processName;

    ComponentName component = new ComponentName(context, serviceClass);
    ServiceInfo serviceInfo;
    try {
      serviceInfo = packageManager.getServiceInfo(component, 0);
    } catch (PackageManager.NameNotFoundException ignored) {
      // Service is disabled.
      return false;
    }

    if (serviceInfo.processName.equals(mainProcess)) {
      //如果服务进程和主进程是同一个进程,那就不对了
      CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
      // Technically we are in the service process, but we're not in the service dedicated process.
      return false;
    }

    int myPid = android.os.Process.myPid();
    ActivityManager activityManager =
        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.RunningAppProcessInfo myProcess = null;
    List runningProcesses;
    try {
      runningProcesses = activityManager.getRunningAppProcesses();
    } catch (SecurityException exception) {
      // https://github.com/square/leakcanary/issues/948
      CanaryLog.d("Could not get running app processes %d", exception);
      return false;
    }
    if (runningProcesses != null) {
      for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
        //获取当前正在前台对进程
        if (process.pid == myPid) {
          myProcess = process;
          break;
        }
      }
    }
    if (myProcess == null) {
      CanaryLog.d("Could not find running process for %d", myPid);
      return false;
    }

    return myProcess.processName.equals(serviceInfo.processName);
  }

思路还是比较清晰的,遍历当前的所有运行中的进程,获得当前运行的主进程,然后和对应的进程名称做对比。

总结

到此LeakCanary的整个流程已经走完了,可能写的比较琐碎,但是大体流程还是比较清晰的。

1.通过Application监听Activity的生命周期
2.在Activity的Destroy时,进行内存泄漏分析。
3.利用弱应用的特性使用一个引用队列保存Activity的引用,如果onDestroy后引用队列中存在该Activity的实例则说明成功回收。
4.若不存在,则手动利用Runtime.getRuntime().gc()方法手动触发GC,执行完后再进行一次判断。
5.若此时还没有在队列中存在,说明没有被回收,则认定此时发生内存泄漏。
6.异步执行Haha库进行引用链分析,然后通知Service发出通知。

你可能感兴趣的:(LeakCanary源码解析-很值得我们学习的一款框架)