内存泄露一直是Android开发中需要避免的问题,因此发现和定位内存泄露就是我们治理内存泄露问题的首要动作。目前市面上最流行的内存泄露排查组件非大名鼎鼎的***LeakCanary***莫属了,它能非常方便直观把内存泄漏处的引用链展示出来,有助于我们的快速定位。另外,其使用方式也十分简单友好。对于开发者而言,仅满足使用还是不够的,尽量知其所以然,于是就有了这篇源码的解析。
源码分析基于LeakCanary版本1.6.3
LeakCanary可以检测具体Activity和Fragment是否存在内存泄露,也可以对想要观察的对象进行检测。它的使用十分简单,以检测Activity为例,以下几行代码即可。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (!LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
}
}
自定义Application类,调用LeakCanary.install()
方法即可。
LeakCanary的检测原理是:
弱引用对象在其包装对象被回收后(弱引用对象创建时,传入了引用队列),该弱引用对象会被加到引用队列中(ReferenceQueue)。
通过在ReferenceQueue中检测是否有目标对象的弱引用对象存在,即可判断目标对象是否被回收。
例如:
......
Activity mActivity;
......
ReferenceQueue<Activity> mQueue = new ReferenceQueue<>();
WeakReference<Activity> mWeakReference = new WeakReference<>(mActivity, mQueue);
......
在创建目标对象mActivity
的弱引用对象时,如果构造方法中传入了引用队列mQueue
,那么当mActivity
对象被回收时,mWeakReference
对象将会被添加到引用队列mQueue
中。
了解了检测原理后,现在以检测Activity是否存在内存泄露为例,来详细分析下实现源码。
install
方法public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
进行一些初始化的构造,重点在buildAndInstall
方法
buildAndInstall
方法public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// ①
RefWatcher refWatcher = build();
// ②
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
// ③
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
① 调用build
方法创建RefWatcher对象,注意这里使用的是AndroidRefWatcherBuilder,是在install
方法中调用refWatcher
方法返回的。
② 这里主要进行一些自定义的设置,比如是否展示内存泄露的链路展示页面、是否检测Activity的内存泄露和是否检测Fragment的内存泄露。默认为否-是-是。所以会调用ActivityRefWatcher.install()
方法和FragmentRefWatcher.Helper.install
方法。
这2个方法就是去设置检测Activity/Fragment对象是否泄露的时机。
以ActivityRefWatcher.install()
方法为例
// ActivityRefWatcher.class
......
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// ②.1
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
// ②.2
refWatcher.watch(activity);
}
};
......
注册Activity的生命周期回调,LeakCanary注册了在onDestroy
生命周期方法执行的时候,去检测当前Activity对象是否存在内存泄露**。
对于Fragment逻辑类似,是在onFragmentDestroyed
和onFragmentViewDestroyed
时检测
③ 将创建的RefWatcher对象赋值给LeakCanaryInternals.installedRefWatcher对象
refWatcher.watch()
代码如下
// RefWatcher.class
......
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();
retainedKeys.add(key);
// ③
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// ④
ensureGoneAsync(watchStartNanoTime, reference);
}
① 对于Activity和Fragment来说,传入的referenceName为空串
② 每次检测时,生成一个随机数并添加到集合中,这个随机数会被当做KeyedWeakReference对象的一个属性,所以可以指代被检测的Activity/Fragment对象。
③ 然后创建KeyedWeakReference对象,这里传入了引用队列。所以当被观测的Activity或Fragment被回收时,对应的引用对象将会被添加到引用队列中。
④ 开始内存泄露检测的具体逻辑
// RefWatcher.class
......
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// ⑤
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
⑤ watchExecutor是一个函数式接口,只有一个execute
方法,看它的实现类AndroidWatchExecutor,调用
execute
方法
// AndroidWatchExecutor.class
@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);
}
.......
如果在主线程,就调用waitForIdle
方法,否则调用postWaitForIdle
,最终都是调用waitForIdle
方法。
使用IdleHandler在消息队列空闲的时候执行任务postToBackgroundWithDelay
,且只执行一次。
⑥ 延迟5s子线程中执行runnable,会执行到ensureGone
方法
ensureGone
方法主要是进行是否真的存在内存泄露的判定逻辑执行,通过2次判定来最终确定是否有内存泄露的问题。
// RefWatcher.class
......
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// ①
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// ②
if (gone(reference)) {
return DONE;
}
// ③
gcTrigger.runGc();
// ④
removeWeaklyReachableReferences();
// ⑤
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);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
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;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
① 从引用队列中取出对象,如果不为空,就删除集合保存的隐射该对象的随机数。如果对象被回收了,就会在引用队列中存在,那么就会删除集合中的随机数,也就意味着没有内存泄露。
② 判断retainedKeys集合中是否还有该随机数,如果没有了,说明引用队列有该对象,该对象被回收了,没有内存泄露。
③ 如果retainedKeys集合还有该随机数,意味着引用队列中没有该对象,该对象没能被回收,于是手动触发一次GC。
④ GC后,再次移除retainedKeys集合中指代被检测对象的随机数。如果GC后被观测对象可以回收,那么引用队列就会存在,并且能够移除retainedKeys集合中的key。
⑤ retainedKeys集合还有该随机数,说明对象不能被回收,发生了内存泄露。就开始导出堆文件,进行分析,展示泄露引用链路页面等。
LeakCanary默认是检测Activity/Fragment的内存泄露,其实根据上文所述,检测内存泄露核心方法就是refWatcher.watch
方法,所以只要创建了RefWatcher对象,理论上可以检测任意对象的内存泄露问题。
本文介绍了LeakCanary的使用方法和检测原理。核心原理是:利用被回收对象,在用弱引用包装时传入的引用队列,弱引用对象会在被包装对象被回收时放入引用队列中,通过检测其在引用队列的存在与否,来判定对象是否被回收。
另外,对其源码进行了分析,剖析了内存检测的时机,检测的具体逻辑等。