网上LeakCanary源码解析的很多,此篇文章不粘贴满屏的代码,只从个人理解角度去选择性的展示核心代码来阐述LeakCanary的原理
LeakCanary既然要监控内存泄露,那么肯定需要一个时机来监控Activity执行了destroy方法,进而再继续观察是否被正常回收,那么Activity销毁是如何得知的呢?Application.ActivityLifecycleCallbacks
//该段代码可在ActivityRefWatcher类里找到
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
/** 省略其他无用生命周期的观察 **/
@Override public void onActivityDestroyed(Activity activity) {
//当某个activity执行到了destroy方法,那么就开始监控
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
1.准备一个一个弱引用指向当前activity,并为当前activity生成一个随机数key作为身份标识,该标识也传给弱引用
2.准备一个引用队列,将步骤一的弱引用进行关联,当弱引用被收回时,那么该弱引用会自动添加到引用队列里
3.准备一个集合只存步骤一中为activity生成的key,当activity被正常回收时,我们可以遍历引用队列,如果引用队列里有弱引用,我们可以拿到步骤一中传入的key,同时将步骤三的集合里的key也移除掉,如果没有移除掉说明发生了内存泄露
//该代码在RefWatcher.java
//存activity对应的随机数
private final Set<String> retainedKeys;
//引用队列 若弱引用被收回时,我们通过poll方法可以拿到这个弱引用
private final ReferenceQueue<Object> queue;
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);
//创建一个弱引用 并与引用队列进行关联 KeyedWeakReference其实很简单就继承了WeakReference
//只是为了能传入一个key
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
//该代码在RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
//这个回调是IdleHandler执行的,为啥要通过IdleHandler呢,因为IdleHandler是主线程消息队列空闲的时候
//才执行的,这时候大概率是在GC之后执行,这次只是碰下运气,看看回收了没
return ensureGone(reference, watchStartNanoTime);
}
});
}
我们可以看下上面的IdleHandler是如何执行的
//AndroidWatchExecutor.java
void waitForIdle(final Retryable retryable, final int failedAttempts) {
//往主线程队列里的mIdleHandlers添加事件处理,当消息队列空闲时会回调queueIdle方法
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
//return false意味着这个事件只会被执行一次 执行完毕会从mIdleHandlers移除掉
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() {
//此处执行了run方法
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
//RefWatcher.java
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//IdleHandler回调的第一次尝试,这时候系统可能发生了GC
removeWeaklyReachableReferences();
//判断是否回收掉了
if (gone(reference)) {
return DONE;
}
//走到这里说明activity没有被回收,这里有两种情况,一种是系统没有发生GC因此没被回收,一种是发生GC了没有被回收
//所以我们需要手动GC(GC后延迟了100ms,目的是为了确保弱引用入引用队列)
gcTrigger.runGc();
//再次尝试移除key
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);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
//通过判断retainedKeys是否有key来判断是否发生了泄露,若有key说明了发生泄露了
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
//该方法就是遍历引用队列,如果activity被回收,那么队列里会有弱引用,会拿到对应的key,并从retainedKeys移除
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
LeakCanary采用了弱引用、引用队列的特性来判断对象是否被回收。对于GC的时机,首先根据IdleHandler的特点来尝试系统的GC,IdleHandler的执行,大概率是系统GC执行完了,若发生没有被回收可能发生了内存泄露也可能系统没有GC过,因此再次手动GC,确保一定发生了GC再去分析内存泄露。