OOM 是 Android 开发中常见的问题,而内存泄漏往往是罪魁祸首。
为了简单方便的检测内存泄漏,Square 开源了 LeakCanary,它可以实时监测 Activity 是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。
本文的目的是试图通过分析源码来探讨它的 Activity 泄漏检测机制。
- LeakCanary
使用方式
- LeakCanary
为了将
引入到我们的项目里,我们只需要做以下两步:
- LeakCanary
- dependencies {
- debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
- releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
- testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
- }
- 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);
- }
- }
可以看出,最关键的就是
这么一句话,正式开启了
- LeakCanary.install(this);
的大门,未来它就会自动帮我们检测内存泄漏,并在发生泄漏是弹出通知信息。
- LeakCanary
开始
- LeakCanary.install(this);
下面我们来看下它做了些什么?
- public static RefWatcher install(Application application) {
- return install(application, DisplayLeakService.class,
- AndroidExcludedRefs.createAppDefaults().build());
- }
- public static RefWatcher install(Application application,
- Class<? extends AbstractAnalysisResultService> listenerServiceClass,
- ExcludedRefs excludedRefs) {
- if (isInAnalyzerProcess(application)) {
- return RefWatcher.DISABLED;
- }
- enableDisplayLeakActivity(application);
- HeapDump.Listener heapDumpListener =
- new ServiceHeapDumpListener(application, listenerServiceClass);
- RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
- ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
- return refWatcher;
- }
首先,我们先看最重要的部分,就是:
- RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
- ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
先生成了一个
,这个东西非常关键,从名字可以看出,它是用来
- RefWatcher
的,也就是用来一个监控引用的工具。然后再把
- watch Reference
和我们自己提供的
- refWatcher
传入到
- application
这句里面,继续看。
- ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
- public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
- ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
- activityRefWatcher.watchActivities();
- }
创建了一个
,大家应该能感受到,这个东西就是用来监控我们的
- ActivityRefWatcher
泄漏状况的,它调用
- Activity
方法,就可以开始进行监控了。下面就是它监控的核心原理:
- watchActivities()
- public void watchActivities() {
- application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
- }
它向
里注册了一个
- application
的回调函数,可以用来监听
- ActivitylifecycleCallbacks
整个生命周期所有
- Application
的 lifecycle 事件。再看下这个
- Activity
是什么?
- 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) {
- ActivityRefWatcher.this.onActivityDestroyed(activity);
- }
- };
原来它只监听了所有
的
- Activity
事件,当
- onActivityDestroyed
被
- Activity
时,调用
- Destory
函数。
- ActivityRefWatcher.this.onActivityDestroyed(activity);
猜测下,正常情况下,当一个这个函数应该
被
- activity
时,那这个
- Destory
对象应该变成 null 才是正确的。如果没有变成null,那么就意味着发生了内存泄漏。
- activity
因此我们向,这个函数
应该是用来监听
- ActivityRefWatcher.this.onActivityDestroyed(activity);
对象是否变成了 null。继续看。
- activity
- void onActivityDestroyed(Activity activity) {
- refWatcher.watch(activity);
- }
- RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
可以看出,这个函数把目标
对象传给了
- activity
,让它去监控这个
- RefWatcher
是否被正常回收了,若未被回收,则意味着发生了内存泄漏。
- activity
我们先来看看这个
究竟是个什么东西?
- RefWatcher
- public static RefWatcher androidWatcher(Context context, HeapDump.Listener heapDumpListener,
- ExcludedRefs excludedRefs) {
- AndroidHeapDumper heapDumper = new AndroidHeapDumper(context, leakDirectoryProvider);
- heapDumper.cleanup();
- int watchDelayMillis = 5000;
- AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis);
- return new RefWatcher(executor, debuggerControl, GcTrigger.DEFAULT, heapDumper,
- heapDumpListener, excludedRefs);
- }
这里面涉及到两个新的对象:
和
- AndroidHeapDumper
,前者用来 dump 堆内存状态的,后者则是用来 watch 一个引用的监听器。具体原理后面再看。总之,这里已经生成好了一个
- AndroidWatchExecutor
对象了。
- RefWatcher
现在再看上面
里调用的
- onActivityDestroyed(Activity activity)
,下面来看下这个最为核心的
- refWatcher.watch(activity);
方法,了解它是如何监控
- watch(activity)
是否被回收的。
- activity
- private final Set<String> retainedKeys;
- public void watch(Object activity, String referenceName) {
- String key = UUID.randomUUID().toString();
- retainedKeys.add(key);
- final KeyedWeakReference reference =
- new KeyedWeakReference(activity, key, referenceName, queue);
- watchExecutor.execute(new Runnable() {
- @Override public void run() {
- ensureGone(reference, watchStartNanoTime);
- }
- });
- }
- final class KeyedWeakReference extends WeakReference<Object> {
- public final String key;
- public final String name;
- }
可以看到,它首先把我们传入的
包装成了一个
- activity
(可以暂时看成一个普通的 WeakReference),然后
- KeyedWeakReference
会去执行一个 Runnable,这个 Runnable 会调用
- watchExecutor
函数。
- ensureGone(reference, watchStartNanoTime)
看这个函数之前猜测下,我们知道
函数本身就是用来监听
- watch
是否被正常回收,这就涉及到两个问题:
- activity
所以我们觉得
函数本身要做的事正如它的名字,就是确保
- ensureGone
被回收掉了,否则就意味着内存泄漏。
- reference
下面来看这个函数实现:
- void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
- removeWeaklyReachableReferences();
- if (gone(reference) || debuggerControl.isDebuggerAttached()) {
- return;
- }
- gcTrigger.runGc();
- removeWeaklyReachableReferences();
- if (!gone(reference)) {
- File heapDumpFile = heapDumper.dumpHeap();
- heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
- }
- }
- private boolean gone(KeyedWeakReference reference) {
- return ! retainedKeys.contains(reference.key);
- }
- private void removeWeaklyReachableReferences() {
- KeyedWeakReference ref;
- while ((ref = (KeyedWeakReference) queue.poll()) != null) {
- retainedKeys.remove(ref.key);
- }
- }
这里先来解释下
和
- WeakReference
的工作原理。
- ReferenceQueue
,这里我们创建了一个
- WeakReference<Activity> reference = new WeakReference(activity);
来弱引用到某个
- reference
,当这个
- activity
被垃圾回收器回收后,这个
- activity
会被放入内部的
- reference
中。也就是说,从队列
- ReferenceQueue
取出来的所有
- ReferenceQueue
,它们指向的真实对象都已经成功被回收了。
- reference
然后再回到上面的代码。
在一个 activity 传给 RefWatcher 时会创建一个唯一的 key 对应这个 activity,该key存入一个集合
中。也就是说,所有我们想要观测的
- retainedKeys
对应的唯一 key 都会被放入
- activity
集合中。
- retainedKeys
基于我们对
的了解,只要把队列中所有的 reference 取出来,并把对应 retainedKeys 里的key移除,剩下的 key 对应的对象都没有被回收。
- ReferenceQueue
把已被回收的对象的 key 从 retainedKeys 移除,剩下的 key 都是未被回收的对象;
- removeWeaklyReachableReferences
发送通知。
- DisplayLeakService
至此,核心的内存泄漏检测机制便看完了。
从上面我们大概了解了内存泄漏检测机制,大概是以下几个步骤:
来监听整个生命周期内的 Activity
- application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
事件;
- onDestoryed
被 destory 后,将它传给 RefWatcher 去做观测,确保其后续会被正常回收;
- Activity
使用 KeyedWeakReference 引用起来,并使用一个 ReferenceQueue 来记录该 KeyedWeakReference 指向的对象是否已被回收;
- Activity
会在 5s 后,开始检查这个弱引用内的
- AndroidWatchExecutor
是否被正常回收。判断条件是:若
- Activity
被正常回收,那么引用它的 KeyedWeakReference 会被自动放入 ReferenceQueue 中。
- Activity
对应的
- Activity
是否已经放入
- KeyedWeakReference
中;如果没有,则手动GC:
- ReferenceQueue
;然后再一次判断
- gcTrigger.runGc();
是否已经含有对应的
- ReferenceQueue
。若还未被回收,则认为可能发生内存泄漏。
- KeyedWeakReference
发送通知。
- DisplayLeakService
有趣的问题
- LeakCanary
在学习了
的源码之后,我想再提几个有趣的问题做些探讨。
- LeakCanary
项目目录结构为什么这样分?
- LeakCanary
下面是整个
的项目结构:
- LeakCanary
对于开发者而言,只需要使用到
这一句即可。那整个项目为什么要分成这么多个 module 呢?
- LeakCanary.install(this);
实际上,这里面每一个 module 都有自己的角色。
: 这是一个通用的内存检测器,对外提供一个 RefWatcher#watch(Object watchedReference),可以看出,它不仅能够检测
- leakcanary-watcher
,还能监测任意常规的 Java Object 的泄漏情况。
- Activity
: 这个 module 是与
- leakcanary-android
世界的接入点,用来专门监测
- Android
的泄漏情况,内部使用了 application#registerActivityLifecycleCallbacks 方法来监听 onDestory 事件,然后利用
- Activity
来进行弱引用+手动 GC 机制进行监控。
- leakcanary-watcher
: 这个 module 提供了
- leakcanary-analyzer
,用来对 dump 出来的内存进行分析并返回内存分析结果
- HeapAnalyzer
,内部包含了泄漏发生的路径等信息供开发者寻找定位。
- AnalysisResult
: 这个 module 是专门给 release 的版本用的,内部只提供了两个完全空白的类
- leakcanary-android-no-op
和
- LeakCanary
,这两个类不会做任何内存泄漏相关的分析。为什么?因为
- RefWatcher
本身会由于不断 gc 影响到 app 本身的运行,而且主要用于开发阶段的内存泄漏检测。因此对于 release 则可以 disable 所有泄漏分析。
- LeakCanary
: 这个很简单,就是提供了一个用法 sample。
- leakcanary-sample
在源码中可以看到,LeakCanary 并不会在 destory 后立即去检查,而是让一个
去进行检查。它会做什么呢?
- AndroidWatchExecutor
- @Override public void execute(final Runnable command) {
- if (isOnMainThread()) {
- executeDelayedAfterIdleUnsafe(command);
- } else {
- mainHandler.post(new Runnable() {
- @Override public void run() {
- executeDelayedAfterIdleUnsafe(command);
- }
- });
- }
- }
- void executeDelayedAfterIdleUnsafe(final Runnable runnable) {
- // This needs to be called from the main thread.
- Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
- @Override public boolean queueIdle() {
- backgroundHandler.postDelayed(runnable, delayMillis);
- return false;
- }
- });
- }
可以看到,它首先会向主线程的 MessageQueue 添加一个
。
- IdleHandler
什么是
?我们知道 Looper 会不断从 MessageQueue 里取出 Message 并执行。当没有新的 Message 执行时,Looper 进入 Idle 状态时,就会取出
- IdleHandler
来执行。
- IdleHandler
换句话说,
就是
- IdleHandler
,只有当 Looper 没有消息要处理时才得到处理。而且,内部的
- 优先级别较低的 Message
方法若返回
- queueIdle()
,表示该任务一直存活,每次 Looper 进入 Idle 时就执行;反正,如果返回
- true
,则表示只会执行一次,执行完后丢弃。
- false
那么,这件优先级较低的任务是什么呢?
,runnable 就是之前
- backgroundHandler.postDelayed(runnable, delayMillis);
。
- ensureGone()
也就是说,当主线程空闲了,没事做了,开始向后台线程发送一个延时消息,告诉后台线程,5s(delayMillis)后开始检查
是否被回收了。
- Activity
所以,当
发生
- Activity
后,首先要等到主线程空闲,然后再延时 5s(delayMillis),才开始执行泄漏检查。
- destory
- Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
- @Override public boolean queueIdle() {
- // do task
- return false; // only once
- }
- });
- //主线程handler
- mainHandler = new Handler(Looper.getMainLooper());
- //子线程handler
- HandlerThread handlerThread = new HandlerThread(“子线程任务”);
- handlerThread.start();
- Handler backgroundHandler = new Handler(handlerThread.getLooper());
- Looper.getMainLooper().getThread() == Thread.currentThread();
在 LeakCanary 里,需要立即触发 gc,并在之后立即判断弱引用是否被回收。这意味着该 gc 必须能够立即同步执行。
常用的触发 gc 方法是
,那它能达到我们的要求吗?
- System.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();
- }
- }
注释里清楚说了,
只是建议垃圾回收器来执行回收,但是
- System.gc()
。从代码也能看出,必须先判断
- 不能保证真的去回收
才能决定是否真的要 gc。
- shouldRunGC
那要怎么实现 即时 GC 呢?
参考了一段 AOSP 的代码
- LeakCanary
- // System.gc() does not garbage collect every time. Runtime.gc() is
- // more likely to perfom a gc.
- Runtime.getRuntime().gc();
- enqueueReferences();
- System.runFinalization();
- public static 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();
- }
- }
LeakCanary 提供了 ExcludedRefs 类,可以向里面添加某些主动忽略的类。比如已知 Android 源代码里有某些内存泄漏,不属于我们 App 的泄漏,那么就可以 exclude 掉。
另外,如果不想监控某些特殊的 Activity,那么可以在
里,过滤掉特殊的 Activity,只对其它 Activity 调用
- onActivityDestroyed(Activity activity)
监控。
- refWatcher.watch(activity)
在 LeakCanary 提供了
,它是一个 intentService,接收到的 intent 内包含了
- AbstractAnalysisResultService
数据和
- HeapDump
结果,我们只要继承这个类,实现自己的
- AnalysisResult
,就可以将堆数据和分析结果上传到我们自己的服务器上。
- listenerServiceClass
本文通过源代码分析了 LeakCanary 的原理,并提出了一些有趣的问题,学习了一些实用的知识点。希望对读者有所启发,欢迎与我讨论。
之后会继续挑选优质开源项目进行分析,欢迎提意见。
谢谢。
wingjay
wingjay.com
来源: https://juejin.im/post/59fafa7ef265da43111f8cb2