说到内存泄漏检测工具基本都知道用LeakCanary,它可以用来检测Activity和Fragment是否发生内存泄露,并且自动弹出通知告知用户是否发生了内存泄漏,且最终以 UI 的形式向我们展示内存泄漏对象的引用链,以便我们能精确的定位到内存泄漏的代码。但是我们不能只局限于表面,也应该去学习其源码的实现,LeakCanary的强大不仅在于能够精确定位内存泄漏代码的具体位置,也在于里面设计的模块分明,这也是值得学习的。所以下面来分析下其整体实现流程,因为里面其实也是对square的另一个开源库haha的api封装,没必要过多去阅读源码细节。
流程分析
它的使用是比较简单的,一句话就可以使用了
//只需要在项目的application中调用就可以使用了
LeakCanary.install(this);
下面看下内部实现
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application) //1
.listenerServiceClass(DisplayLeakService.class) //2
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) //3
.buildAndInstall(); //4
}
在注释1处内部会创建一个AndroidRefWatcherBuilder
对象,在注释2处设置了一个用于监听内存泄漏结果的DisplayLeakService
,它继承自IntentService
,内部是用来发送通知给应用层的,注释3处排除了一些系统Bug引起的内存路径,在注释4处就会创建RefWatcher对象,整体就是一个build构建者模式,先看下注释2
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
会将DisplayLeakService的class对象传给ServiceHeapDumpListener,接着它的实例又会传给heapDumpListener变量,DisplayLeakService后面会使用的。
接着看注释4处
public @NonNull RefWatcher buildAndInstall() {
//这个方法只允许调用一次
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//创建RefWatcher实例
RefWatcher refWatcher = build(); //1
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
//监听Activity
if (watchActivities) { //2
ActivityRefWatcher.install(context, refWatcher);
}
//监听Fragment
if (watchFragments) { //3
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
这段代码中首先会在注释1处创建RefWatcher对象
public final RefWatcher build() {
...
//默认排除路径设置
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
//HeapDumpListener设置
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
//HeadDumper设置
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
//线程池设置
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
//GC设置
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
//设置完RefWatcher的各个模块之后,接着就会去创建RefWatcher对象
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
可以看出这个方法就是设置了RefWatcher的各个子模块,这里需要注意的是各种default操作都是在AndroidRefWatcherBuilder
中实现的,它重写了父类的相关方法。各个细节就不去探究了,这里只分析流程。
接着就会在注释2或者注释3处启动activity或者fragment的监听,先来分析注释2处的逻辑。
ActivityRefWatcher.install(context, refWatcher)
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
可以看出其内部就是设置了一个registerActivityLifecycleCallbacks监听,这样就可以监听activity生命周期了,然后在onActivityDestroyed中调用refWatcher的watch方法。来做内存泄漏分析
然后来看fragment的监听
FragmentRefWatcher.Helper.install(context, refWatcher);
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
...
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
fragmentRefWatchers.add(supportFragmentRefWatcher);
...
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
//会调用到FragmentRefWatcher的watchFragments,具体实现就是注册一个FragmentManager.FragmentLifecycleCallbacks的监听,然后在fragment生命周期中调用refWatcher的watch方法
watcher.watchFragments(activity);
}
}
};
//监听fragment生命周期
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
至此refWatcher的install方法已经分析完了,接着来分析它的watch是如何分析内存泄漏的
public void watch(Object watchedReference) {
//调用它的重载方法
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
...
final long watchStartNanoTime = System.nanoTime();
//使用UUID生成key
String key = UUID.randomUUID().toString();
//添加进入retainedKeys集合,将来作为弱引用对象是否已经被回收的依据
retainedKeys.add(key);
//构建弱引用对象,指定watcherReference,就是activity或者fragment,并指定了一个引用队列,当对象可以被回收时,就会被加入引用队列中,如果没有加入,就可能发生内存泄漏
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
//为了不影响主线程的操作,这里是异步的
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
这个方法中有个比较重要的知识就是引用队列queue,它传入一个弱引用对象,当gc之后,所引用对象可以被回收,就会被加入到引用队列queue中,如果检测不到就可能发生内存泄漏,具体可以看文末参考部分。
然后来看异步操作是如何完成的,watchExecutor的设置是前面通过defaultWatchExecutor来设置的
@Override protected @NonNull WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
public final class AndroidWatchExecutor implements WatchExecutor {
...
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
//通过handlerThread来实现异步操作
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
...
}
@Override public void execute(@NonNull Retryable retryable) {
...
}
}
在异步操作中会调用ensureGone
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//尝试在queue中寻找引用对象的弱引用,存在就会移除retainedKeys中对应的key引用
removeWeaklyReachableReferences();
//调试模式直接返回
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//移除成功,表示没有发生内存泄漏,直接返回DONE
if (gone(reference)) {
return DONE;
}
//进行手动GC
gcTrigger.runGc();
//再此尝试移除key
removeWeaklyReachableReferences();
//包含key就表示引用对象没有进入引用队列queue,此时已经发生内存泄漏,接下来进行head dump操作
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump hprf文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//构建HeadDump对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//对hprf文件进行分析,基本就是调用haha库里面的api操作。
heapdumpListener.analyze(heapDump);
}
return DONE;
}
这里首先尝试移除retainedKeys里面的key,如果移除不了,就进行一次gc操作,接着再进行移除操作,如果还是移除不了,就会判定发生了内存泄漏,接着就是head dump操作了,看下是如何实现的
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
...
//发送一个应用层通知
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);
...
try {
//原生api dump hprf文件
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
...
}
}
这个方法里面会发送一个通知,接着调用原生api Debug.dumpHprofData去生成hprf文件。文件生成好之后就会去构建HeapDump对象,接着调用heapdumpListener.analyze去分析Heapdump对象。
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
接着就会调用HeapAnalyzerService的runAnalysis去启动一个HeapAnalyzerService,它是IntentService,具体回调方法是onHandleIntentInForeground
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
...
//获取传递过来的LISTENER_CLASS_EXTRA
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
//获取HeapDump对象
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize); //1
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);//2
}
这里关键的在注释1处的checkForLeak方法上
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
@NonNull String referenceKey,
boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
...
try {
//构建内存映射的 HprofBuffer,针对大文件的一种快速的读取方式,其原理是将文件流的通道与 ByteBuffer 建立起关联,并只在真正发生读取时才从磁盘读取内容出来。
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//构造 Hprof 解析器
HprofParser parser = new HprofParser(buffer);
//获取快照
Snapshot snapshot = parser.parse();
//gcRoots去重操作
deduplicateGcRoots(snapshot);
//搜索内存泄漏的索引
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
// 搜索索引链,并作为结果返回
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
之后就会返回一个AnalysisResult对象,然后在注释2处将结果传递给一个IntentService,它就是前面说到的DisplayLeakService
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
public static void sendResultToListener(@NonNull Context context,
@NonNull String listenerServiceClassName,
@NonNull HeapDump heapDump,
@NonNull AnalysisResult result) {
Class<?> listenerServiceClass;
try {
//listenerServiceClass就是DisplayLeakService,它是一个IntentService,里面是对通知的封装
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);
if (analyzedHeapFile != null) {
intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());
}
ContextCompat.startForegroundService(context, intent);
}
收到通知之后,点击通知就会跳转到一个新进程的DisplayLeakActivity页面,这个页面就是发生内存泄漏的具体引用链,用户就可以通过引用链去分析具体的内存泄漏了。
public static PendingIntent createPendingIntent(Context context, String referenceKey) {
setEnabledBlocking(context, DisplayLeakActivity.class, true);
Intent intent = new Intent(context, DisplayLeakActivity.class);
intent.putExtra(SHOW_LEAK_EXTRA, referenceKey);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
}
框架
优秀的框架之所以优秀,并不只是它向我们提供了强大的功能和解决了我们的问题,相对更重要的还在于它对模块的清晰划分以及模块之间层叠有秩的层次。通过对各模块的依赖关系的梳理以及对源码的理解,梳理出如下的框架图。
leakcanary-android&leakcanary-android-no-op :LeakCanary 一般建议工作在 Debug 模式下,这时我们依赖 leakcanary-android 就可以了。当有检测到内存泄漏时其会先冻结进程并 dump 出内存快照并进行分析。而在 release 模式下就依赖 leakcanry-android-no-op,其不会产生后续的分析行为。初始化Leakcanary相关操作,以及各个模块之间的创建
leakcanary-analyzer: 在相应的回调中,检测内存泄漏,接着通过 haha 进行内存泄漏的分析。
leakcanary-watcher: 对对象的引用进行监测,当有内存泄漏时先进行 heap dump,然后再告知 leakcanary-analyzer 进行接下来的分析工作。
haha: square开源的一个库专门用于自动分析 Android heap dump。类似的库还有vshor/mat,AndroMAT 以及鼎鼎大名的 Eclipse Memory Analyzer。
主体类图
通过基本流程可以有如下的类图:
LeakCanary属于leakcanary-android层的,主要负责相关子模块的创建,也包含各个子模块的具体实现
RefWatcher 属于leakcanary-watcher层,主要用来检测是否内存泄漏
HeapAnalyzer 属于leakcanary-analyzer层,使用haha里面的api对hprf文件进行分析,并找出内存泄漏引用链然后通知给应用层。
分析就到这里了,具体的详细细节可以看源码,有很多地方分析不到位,是自己学习得不够深,后续会加深学习的。
参考
1.LeakCanary 源码分析
2.LeakCanary详解与源码分析
3.Reference 、ReferenceQueue 详解