前言
最近高产似母猪,闲下来的时候就喜欢找找源码看。昨天看了下LeakCanary,准备来分析一波。
导入
gradle文件中添加:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' // debug
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' // release
初始化
Application中通过LeakCanary.install(this);
进行初始化。
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
// 创建AndroidRefWatcherBuilder对象
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
// 只是保存applicationContext
AndroidRefWatcherBuilder(Context context) {
this.context = context.getApplicationContext();
}
// 服务监听
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
// 堆内存镜像监听服务
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(Context context,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabled(context, listenerServiceClass, true);
setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
这里有两个重点:
-
AndroidRefWatcherBuilder
保存了ServiceHeapDumpListener
监听器。 -
ServiceHeapDumpListener
监听的服务是DisplayLeakService
在初始化时,AndroidExcludedRefs.createAppDefaults().build()
创建了默认忽略内存泄漏的属性,这里不多说。
多说一句,这里面有个代码是setEnabled
,其最终实现是:
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
这个主要就是设置组件可用状态,因为这里面默认的许多组件都是不可用状态,下面的Service以及Activity的enabled属性都为false:
接着看下
buildAndInstall()
方法:
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
// 将Application和ActivityRefWatcher进行绑定
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
// 省略其他生命周期,只有在onActivityDestroyed会调用
......
@Override
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
// 注册对Activity的监听
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
到这里就知道LeakCanary是如何监听页面销毁的了,和Lifecycle一样,通过注册Application.ActivityLifecycleCallbacks
实现对Activity生命周期的监听,在destory的时候进行内存分析。
内存分析
从上面看到,当Activity执行了onDestory生命周期后,就会触发ActivityRefWatcher.this.onActivityDestroyed(activity)
,这里有几个方法调用:
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();
// 将随机生成的key保存
retainedKeys.add(key);
// 创建了一个存储了随机生成的key的弱引用,并且传入了ReferenceQueue
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// watchExecutor是AndroidWatchExecutor
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
这里将我们传入的Activity通过弱引用的方式保存起来KeyedWeakReference
,并且还将随机生成一个key作为改Activity的唯一标志。
我们在构造AndroidRefWatcher时,创建的是 AndroidWatchExecutor
,AndroidWatchExecutor
根据返回的结果Retryable.Result
进行判断,如果为DONE
,则代表处理完成;如果为RETRY
,则需要重新处理。
接着看下
ensureGone
的操作吧:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 移除掉已经被释放的Activity对应的key
removeWeaklyReachableReferences();
// debug状态
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 判断是否结束
if (gone(reference)) {
return DONE;
}
// 手动调用gc,这里会使线程休眠100ms
gcTrigger.runGc();
// 再次移除已经被释放的Activity对应的key
removeWeaklyReachableReferences();
// 如果还是存在key,说明有内存泄漏
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 获取heapDumpFile文件,这里heapDumper是AndroidHeapDumper,会弹出那个经典的toast
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 调用ServiceHeapDumpListener的analyze方法
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
private void removeWeaklyReachableReferences() {
// 弱引用对应的资源被释放,会将弱引用放到Quene中,通过poll可以出队列
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
// 将KeyedWeakReference对应的key移除
retainedKeys.remove(ref.key);
}
}
// 是否结束的标志是保存的key中是否存在当前弱引用的key
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
流程如下:
- 移除已经被回收的Activity,并且移除Activity对应的key
- 判断是否还有对应的key,如果没有则返回DONE
- 此时还有对应的key,调用一次gc,这里会使线程休眠100ms
- 进行1和2的操作,如果仍然有对应的key,则调用
AndroidHeapDumper
生成文件,并且调用之前传入的ServiceHeapDumpListener.analyze()
方法
分析过程
ServiceHeapDumpListener
的analyze()
方法:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
其中listenerServiceClass
是我们创建时添加的类,其类为DisplayLeakService
(后面会用到)。
runAnalysis
方法:
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);
}
该方法只是启动了一个继承了IntentService
的HeapAnalyzerService
,可以看下其onHandleIntent
方法:
@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);
// 分析过程
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
这里面主要的分析过程时通过HeapAnalyzer的checkForLeak
方法实现的:
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
// 将保存的hprof文件转为Snapshot对象,这里通过com.squareup.haha实现
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
// 查找泄漏的引用
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
// 获取引用的跟踪信息
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
private Instance findLeakingReference(String key, Snapshot snapshot) {
// 获得KeyedWeakReference类的引用
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List<String> keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
String keyCandidate = asString(fieldValue(values, "key"));
// 如果某类的key和我们传入的key相同,那么就是这个类泄漏了
if (keyCandidate.equals(key)) {
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
最终根据泄漏的类来进行引用跟踪,并且返回AnalysisResult
分析结果。
这里时通过com.squareup.haha
来进行hropf文件分析以及引用树的建立,其内存泄漏的分析应该和MAT等工具原理一致。
显示结果
构造好了AnalysisResult
分析结果后,通过AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
方法将结果发送到listenerClassName
,这个就是我们上面说的DisplayLeakService
,顾名思义,这个就是用于显示泄漏的Service:
总结
这里最主要的内存泄漏分析过程并没有写出来,主要是其使用了另一个库进行分析,实在是没有精力去分析了。
简单总结下:
- 何时调用内存泄漏分析?
通过注册
application.registerActivityLifecycleCallbacks
注册对Activity生命周期回调的监听,在onDestory后进行内存泄漏分析
- 如何确定可能有内存泄漏?
通过
KeyedWeakReference
和ReferenceQueue
对弱引用进行是否回收判断,如果回收则将该弱引用放入ReferenceQueue
中。