Android内存泄漏工具——LeakCanary源码解析

Android内存泄漏工具——LeakCanary源码解析

LeakCanary 是什么?

来自官网的解释:LeakCanary 是一个用来检测Android内存泄漏的库(官网传送门

LeakCanary is a memory leak detection library for Android.

在这里插入图片描述

LeakCanary 可以帮助开发者找出所有的内存泄漏,极大的减少 OutOfMemoryError 造成的 crash 。

如何使用 LeakCanary

不需要任何侵入性代码, LeakCanary 就可以帮你在Debug模式下,找出内存泄漏

  1. 在 build.gradle 里面添加依赖
dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3'
}
  1. 在 Application 的 onCreate 方法里面,对 LeakCanary 进行初始化
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // 在 LeakCanary 自己的进程里不需要初始化,直接返回
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
        }
        LeakCanary.install(this);
    }
}

// 当然不要忘记在xml文件里把 Application 添加进去
AndroidManifest.xml
<application
    android:name=".MyApplication"
    ....
</application>

好了,只有简单的两步,你就可以愉快地使用 LeakCanary 了,是不是很简单?根本就不需要侵入你的代码,就可以快速用起来,由于只在debug模式下使用,对release版本也不造成影响

LeakCanary 如何检测内存泄漏

如果你想自己查找内存泄漏,可以看下这篇——如何通过最新版Android Stuido发现和解决内存泄漏

WeakReference 和 ReferenceQueue

首先介绍下 LeakCanary 检查内存泄漏用到的最重要的两个类: WeakReference 和 ReferenceQueue

WeakReference 我们都知道是弱引用:弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存

实在有不明白的可以看下——深入理解Java弱引用

ReferenceQueue 是什么呢?因为平时基本不会用到,所以大家基本都不了解,我就拿官方的注释给大家解释一下

Reference queues, to which registered reference objects are appended by the
garbage collector after the appropriate reachability changes are detected.

也就是 ReferenceQueue 是注册到垃圾回收控制器上的,每当对象被回收,都会被加入到 ReferenceQueue 里面。

WeakReference 有一个构造器,传入的参数就有 ReferenceQueue ,所以当 WeakReference 和 ReferenceQueue 关联上之后, WeakReference 被回收,都会放进 ReferenceQueue 里面,而不会直接回收,需要自己手动 poll ReferenceQueue 里面的对象,才能让对象回收。

 * @param referent object the new weak reference will refer to
 * @param q the queue with which the reference is to be registered,
 *          or <tt>null</tt> if registration is not required
 */
public WeakReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
}

LeakCanary 就是用这一特性,来看 activity 是否被回收,如果被回收肯定就在 ReferenceQueue。

源码解析 LeakCanary

先盗一张图,来个总体的印象
在这里插入图片描述

接下来将根据源码,一步步解析 LeakCanary 到底是如何工作的。

首先从 LeakCanary 的初始化入手,也就是 LeakCanary 的 install 方法

LeakCanary.install(this);

// install 方法,前面的build都是构建参数,最后的 buildAndInstall 方法才是真正 install
public static RefWatcher install(Application application) {
	return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
	    .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
	    .buildAndInstall();
}


// buildAndInstall 进行构建,在 installOnIcsPlus 执行 install
public RefWatcher buildAndInstall() {
	RefWatcher refWatcher = build();
	if (refWatcher != DISABLED) {
	  LeakCanary.enableDisplayLeakActivity(context);
	  ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
	}
	return refWatcher;
}


public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
	if (SDK_INT < ICE_CREAM_SANDWICH) {
	  // If you need to support Android < ICS, override onDestroy() in your base activity.
	  return;
	}
	ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
	// 真正开始监听activity
	activityRefWatcher.watchActivities();
}


public void watchActivities() {
	// Make sure you don't get installed twice.
	stopWatchingActivities();
	// 其实就是在 application 加入 activity 生命周期的监听
	application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}


// 通过 activity 生命周期的监听看到,其实只是实现的 onActivityDestroyed 方法,因为只需要在 activity onDestroy 之后,判断下 acitivity 是否内存泄漏就可以了
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) {
		  // 真正对 activity 内存泄漏检测的地方
		  ActivityRefWatcher.this.onActivityDestroyed(activity);
		}
	};

接下来看下 activity onDestroy 之后, LeakCanary 做了什么吧

// 一步步往下跟踪,到真正观察的地方
void onActivityDestroyed(Activity activity) {
	refWatcher.watch(activity);
}

public void watch(Object watchedReference) {
	watch(watchedReference, "");
}

public void watch(Object watchedReference, String referenceName) {
	if (this == DISABLED) {
	  return;
	}
	checkNotNull(watchedReference, "watchedReference");
	checkNotNull(referenceName, "referenceName");
	final long watchStartNanoTime = System.nanoTime();
	String key = UUID.randomUUID().toString();
	// 把当前的 activity 的 key 存起来,后面如果内存回收就移除,
	retainedKeys.add(key);
	// 根据 activity 生成一个 WeakReference (KeyedWeakReference 父类是 WeakReference, 只是做了一层包装)
	final KeyedWeakReference reference =
	    new KeyedWeakReference(watchedReference, key, referenceName, queue);

	// 异步确认是否内存泄漏
	ensureGoneAsync(watchStartNanoTime, reference);
}

具体看下异步确认是否内存泄漏 ensureGoneAsync 方法

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
	// watchExecutor 是在异步线程执行
	watchExecutor.execute(new Retryable() {
	  @Override
	  public Retryable.Result run() {
	    return ensureGone(reference, watchStartNanoTime);
	  }
	});
}

// watchExecutor 具体设置的地方,watchExecutor 其实是一个 AndroidWatchExecutor
public AndroidRefWatcherBuilder watchDelay(long delay, TimeUnit unit) {
	return watchExecutor(new AndroidWatchExecutor(unit.toMillis(delay)));
}

// AndroidWatchExecutor 具体执行 execute 的方法
@Override 
public void execute(Retryable retryable) {
	// 在主线程 post 一个空闲的 handler, 防止阻塞主线程
	if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
	  waitForIdle(retryable, 0);
	} else {
	// 不在主线程,就让主线程 post 一个空闲的 handler
	  postWaitForIdle(retryable, 0);
	}
}

// 不在主线程,就让主线程 post 一个空闲的 handler
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
	mainHandler.post(new Runnable() {
	  @Override public void run() {
	    waitForIdle(retryable, failedAttempts);
	  }
	});
}

// 让主线程 post 一个空闲的 handler
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() {
	  	// 最后在 backgroundHandler 线程里执行
	    postToBackgroundWithDelay(retryable, failedAttempts);
	    return false;
	  }
	});
}

// 最后在 backgroundHandler 线程里执行,backgroundHandler 是 HandlerThread 里面的
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);
}

具体的确认方法 ensureGone 执行

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
	long gcStartNanoTime = System.nanoTime();
	long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

	// 先把已经被GC回收的内存 key 值移除
	removeWeaklyReachableReferences();

	if (debuggerControl.isDebuggerAttached()) {
	  // The debugger can create false leaks.
	  return RETRY;
	}
	// 如果没有当前 activity 的内存了,没有泄漏直接返回
	if (gone(reference)) {
	  return DONE;
	}
	// 如果存在,可能是虽然没引用到,下次垃圾回收,就可以回收到,所以强制进行一次垃圾回收
	gcTrigger.runGc();
	// 在把已经被GC回收的内存 key 值移除
	removeWeaklyReachableReferences();
	// 如果还有当前 activity 的内存了,说明内存泄露了
	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);
	  // 分析当前的内存快照中,内存泄漏的最短引用路径,并显示
	  heapdumpListener.analyze(
	      new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
	          gcDurationMs, heapDumpDurationMs));
	}
	return DONE;
}


// 已经垃圾回收的 key 在方法 removeWeaklyReachableReferences 中都已经移除,retainedKeys 中剩下的都是内存泄漏的
private boolean gone(KeyedWeakReference reference) {
	return !retainedKeys.contains(reference.key);
}

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;
	// 把已经回收的内存的 key 移除,retainedKeys 中剩下的都是内存泄漏的
	while ((ref = (KeyedWeakReference) queue.poll()) != null) {
	  retainedKeys.remove(ref.key);
	}
}

保存当前的内存快照方法 dumpHeap ,具体是在 AndroidHeapDumper 中实现的

@Override 
public File dumpHeap() {
	// 新建保存文件
	File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

	if (heapDumpFile == RETRY_LATER) {
	  return RETRY_LATER;
	}

	FutureResult<Toast> waitingForToast = new FutureResult<>();
	showToast(waitingForToast);

	if (!waitingForToast.wait(5, SECONDS)) {
	  CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
	  return RETRY_LATER;
	}

	Toast toast = waitingForToast.get();
	try {
	  // 这个是系统方法, 把内存快照 'hprof' 文件保存到当前文件文件中,可能会造成GC
	  Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
	  cancelToast(toast);
	  return heapDumpFile;
	} catch (Exception e) {
	  CanaryLog.d(e, "Could not dump heap");
	  // Abort heap dump
	  return RETRY_LATER;
	}
}

分析当前的内存快照中,内存泄漏的最短引用路径,并显示 analyze 是 ServiceHeapDumpListener 具体实现的

@Override 
public void analyze(HeapDump heapDump) {
	// 首先是判空
	checkNotNull(heapDump, "heapDump");
	// 然后真正分析内存快照中内存泄漏的最短引用路径
	HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}

// 分析内存快照中内存泄漏的最短引用路径,使用了一个 IntentService
// 因为这是在另一个进程中分析(也就是LeakCanary.isInAnalyzerProcess(this), 判断的线程)显示的,所以采用了进程间可以通讯的 Service
public final class HeapAnalyzerService extends IntentService {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

  // 把消息封装,发送给 IntentService, 自己的进程处理
  public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName());
  }

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

    // 最终分析内存快照用的是他们自己的库:com.squareup.haha, 有兴趣的可以自己深入看下
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    // 把消息发送给另一个 IntentService,准备显示
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
}

到这里一个 LeakCanary 检测 acitivity 内存泄漏的源码分析到此就结束啦。

发布了29 篇原创文章 · 获赞 3 · 访问量 1127

猜你喜欢

转载自blog.csdn.net/qq_16927853/article/details/102924569