一、总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dPzeaHgC-1611411403417)(https://uploader.shimo.im/f/V9gp69g4D0TNV3Oc.png!thumbnail?fileGuid=Pk8VCdxhDKv8qkKh)]
总结:
1、在LeakCanary初始化的方法中,我们调用了application的registerActivityLifecycleCallbacks方法,在这里面可以监听到activity的生命周期。
在onDestroyed方法中,也就是activity生命结束的时候,我们调用refWatcher.watch(activity),进行内存泄漏的检测。
2、我们会将当前的activity生成唯一的id添加到集合retainedKeys中。然后将activity进行弱引用。当我们的弱引用对象被垃圾回收的时候,会将它添加到引用队列ReferenceQueue中。也就是引用队列存储着被垃圾回收的对象。
3、为了保证垃圾回收执行,我们主动调用一次垃圾回收。
4、遍历引用队列,取出对象生成唯一的key和之前集合的key进行对应,将retainedKeys集合中的数据删除。此时retainedKeys剩余的就是没有被回收的对象。说明发生了内存泄漏。
5、接下来进行内存泄漏分析,在这里使用了IdleHandler。当 looper 空闲的时候,会回调IdleHandler的 queueIdle 方法。
6、开启一个类型为IntentService的ForegroundService,在里面会创建通知,告诉用户发生了内存泄漏。点击会跳转到泄漏详情activity。
二、源码解析
1、LeakCanary.install()
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
代码解析:
LeakCanary.install()可以分为四步
- 1、refWatcher(application):创建AndroidRefWatcherBuilder。并在父类RefWatcherBuilder的构造方法中新建了一个HeapDump的构造器heapDumpBuilder对象。其中HeapDump就是一个保存heap dump信息的数据结构。
- 2、链式调用listenerServiceClass(DisplayLeakService.class):(1)传入了一个DisplayLeakService的Class对象,它的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知;(2)在listenerServiceClass()这个方法中新建了一个ServiceHeapDumpListener对象。
- 链式调用excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
- 链式调用buildAndInstall():(1)创建activityRefWatcher实例(2)调用了application的registerActivityLifecycleCallbacks()方法,这样就能够监听activity对应的生命周期事件了。(3)在ActivityLifecycleCallbacks重写了onActivityDestroyed()方法,这样便能在所有Activity执行完onDestroyed()方法之后调用 refWatcher.watch(activity)这行代码进行内存泄漏的检测了。
- 接下来重点到了当Activity执行完onDestroyed()方法之后,会调用refWatcher.watch(activity)
2、refWatcher.watch(activity)
->watch(watchedReference, "")
public void watch(Object watchedReference, String referenceName) {
、、、省略部分代码
//1、使用随机的UUID保证了每个检测对象对应 key 的唯一性。
String key = UUID.randomUUID().toString();
//2、将生成的key添加到类型为CopyOnWriteArraySet的Set集合中
retainedKeys.add(key);
//3、创建自定义的弱引用KeyedWeakReference
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//4、调用ensureGoneAsync
ensureGoneAsync(watchStartNanoTime, reference);
}
1)弱引用KeyedWeakReference
KeyedWeakReference继承自WeakReference,在它的构造方法中将watchedReference和ReferenceQueue关联,如果弱引用watchedReference持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列ReferenceQueue中。即 :KeyedWeakReference 持有的 Activity 对象如果被GC回收,该对象就会加入到引用队列 referenceQueue 中。
//1、在watch方法里创建reference,参数里的
queue=new ReferenceQueue<>()
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
2)ensureGoneAsync
- 1、执行watchExecutor的execute方法。watchExecutor就是AndroidWatchExecutor。
- 在execute方法中执行waitForIdle方法。
- 2、在waitForIdle使用了MessageQueue.IdleHandler(),当 looper 空闲的时候,会回调IdleHandler的 queueIdle 方法,利用这个机制我们可以实现第三方库的延迟初始化,然后执行内部的postToBackgroundWithDelay()方法。
- 3、postToBackgroundWithDelay()方法会执行RefWatcher#ensureGone()
ensureGoneAsync(watchStartNanoTime, reference);
private void ensureGoneAsync() {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
3)RefWatcher#ensureGone()
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//reference 被回收的时候,会被加进 referenceQueue队列中。此时referenceQueue就存储着已经被回收的引用。
//1、接下来while循环遍历 ReferenceQueue,从里面取出已经被回收的reference,并从 retainedKeys中移除对应的Reference的key。
removeWeaklyReachableReferences();
//2、判断 retainedKeys 集合中是否还含有 reference的key,若没有,证明已经被回收了。若含有,可能已经发生内存泄露(或Gc还没有执行回收)。
if (gone(reference)) {
return DONE;
}
//3、gcTrigger的runGc()方法进行垃圾回收
gcTrigger.runGc();
//4、再次执行removeWeaklyReachableReferences方法移除已经被回收的引用
removeWeaklyReachableReferences();
//5、再次判断retainedKeys 集合中是否还含有 reference的key,如果此时包含。说明发生了内存泄漏。
if (!gone(reference)) {
//6、调用堆信息转储者heapDumper的dumpHeap()生成相应的 hprof 文件。这里的heapDumper是一个HeapDumper接口,具体的实现是AndroidHeapDumper。
File heapDumpFile = heapDumper.dumpHeap();
//7、创建heapDump对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//8、执行heapdumpListener的analyze()对新创建的HeapDump对象进行泄漏分析。
heapdumpListener.analyze(heapDump);
}
return DONE;
}
补充:gcTrigger.runGc()垃圾回收
@Override public void runGc() {
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
解析:这里并没有使用System.gc()方法进行回收,因为system.gc()并不会每次都执行。而是调用Runtime.getRuntime().gc(),相比System.gc()更能够保证垃圾回收的工作。
❌4)File heapDumpFile = heapDumper.dumpHeap();
生成heapDumpFile泄漏文件
这里的核心操作就是调用了Android SDK的API Debug.dumpHprofData() 来生成 hprof 文件。
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;
}
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
5)heapdumpListener.analyze(heapDump);
1、heapdumpListener的实现者是ServiceHeapDumpListener,执行analyze方法。
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
2、开启了HeapAnalyzerService。HeapAnalyzerService是一个类型为IntentService的ForegroundService,并且这个服务在单独的进程中运行,为了避免降低app进程的性能或占用内存。
3、执行startForegroundService()之后,会回调onHandleIntentInForeground()方法。
6)调用HeapAnalyzerService的onHandleIntentInForeground方法
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
//1、创建heapAnalyzer对象
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//2、解析 hprof文件
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
//3、创建一个由PendingIntent构建的泄漏通知用于供用户点击去展示详细的泄漏界面DisplayLeakActivity。即创建一个供用户点击跳转到DisplayLeakActivity的延时通知
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
2、解析 hprof文件->checkForLeak方法
/**
* 在堆转储中搜索具有相应键的实例,然后计算从该实例到GC根的最短强引用路径。
*/
public @NonNull AnalysisResult checkForLeak() {
、、、
//1、新建一个内存映射缓存文件buffer
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//2、使用buffer新建一个HprofParser解析器去解析出对应的引用内存快照文件snapshot
HprofParser parser = new HprofParser(buffer);
、、、
//3、为了减少在Android 6.0版本中重复GCRoots带来的内存压力的影响,使用deduplicateGcRoots()删除了gcRoots中重复的根对象RootObj。
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
//4、调用了findLeakingReference()方法将传入的referenceKey和snapshot对象里面所有类实例的字段值对应的keyCandidate进行比较,如果没有相等的,则表示没有发生内存泄漏
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
、、、
//回一个有泄漏分析结果的AnalysisResult对象
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
、、、
}
3、展示泄漏路径信息的DisplayLeakService对象
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
解析:
AbstractAnalysisResultService的实现类DisplayLeakService对象。在里面直接创建一个由PendingIntent构建的泄漏通知用于供用户点击去展示详细的泄漏界面DisplayLeakActivity。
即创建一个供用户点击跳转到DisplayLeakActivity的延时通知
//创建通知PendingIntent
PendingIntent pendingIntent =
DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);