LeakCanary 是 Square 公司基于 MAT 开源的一个工具,用来检测 Android App 中的内存泄露问题。官方地址:https://github.com/square/leakcanary
上一篇文章分析了 android 开发可能会出现的内存的泄漏的情况,本篇文章主要来看看LeakCanary 是如何去分析代码中可能出现内存泄漏的原因。
android 开发中可能会引起内存泄漏的情况
LeakCanary 的简单用法
首先,build.gradle 中配置依赖库;
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
Application 开启检测:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 注册 LeakCanary
LeakCanary.install(this);
}
}
在 Application 中调用 install 方法之后,就可以检测我们 app 运行时候的内存泄漏情况。
分析 LeakCanary.install(this) 方法做了什么?
接下来我们就该详细分析 install 方法主要做了什么,进入LeakCanary类中:
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)// 开启线程监听内存泄漏信息
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 排除一些忽略内存泄漏的路径
.buildAndInstall();
}
查看 refWatcher 方法;
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
AndroidRefWatcherBuilder(Context context) {
this.context = context.getApplicationContext();
}
refWatcher 方法返回了 AndroidRefWatcherBuilder 实例,而 AndroidRefWatcherBuilder 构造方法初始化了上下问环境,接下来则是调用 AndroidRefWatcherBuilder 的相关方法;
接下来在调用 AndroidRefWatcherBuilder 的 listenerServiceClass 方法之前,先看下参数 DisplayLeakService 类;
public class DisplayLeakService extends AbstractAnalysisResultService {
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
}
public abstract class AbstractAnalysisResultService extends IntentService {
@Override protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
}
DisplayLeakService 实际是一个 IntentService,开了一个线程去分析相关信息,这里需要知道的就是为了监听内存泄漏的信息;后面检测到内存泄漏,会继续详细分析此方法;
所以 listenerServiceClass 也是为了开启一个线程进行监听;
public AndroidRefWatcherBuilder listenerServiceClass(
Class extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
继续调用 excludedRefs 方法;
/** @see ExcludedRefs */
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}
@SuppressWarnings("unchecked")
protected final T self() {
return (T) this;
}
excludedRefs 方法是为了排除一些可以忽略内存泄漏的路径,例如系统的内存泄漏;
具体操作可以通过配置 AndroidExcludedRefs builder 的相关参数配置;
public static final class BuilderWithParams implements Builder {
private final Map> fieldNameByClassName =
new LinkedHashMap<>();
private final Map> staticFieldNameByClassName =
new LinkedHashMap<>();
private final Map threadNames = new LinkedHashMap<>();
private final Map classNames = new LinkedHashMap<>();
private ParamsBuilder lastParams;
BuilderWithParams() {
}
@Override public BuilderWithParams instanceField(String className, String fieldName) {// 忽略的某个类中某个变量
checkNotNull(className, "className");
checkNotNull(fieldName, "fieldName");
Map excludedFields = fieldNameByClassName.get(className);
if (excludedFields == null) {
excludedFields = new LinkedHashMap<>();
fieldNameByClassName.put(className, excludedFields);
}
lastParams = new ParamsBuilder("field " + className + "#" + fieldName);
excludedFields.put(fieldName, lastParams);
return this;
}
@Override public BuilderWithParams staticField(String className, String fieldName) {// 忽略的静态变量
checkNotNull(className, "className");
checkNotNull(fieldName, "fieldName");
Map excludedFields = staticFieldNameByClassName.get(className);
if (excludedFields == null) {
excludedFields = new LinkedHashMap<>();
staticFieldNameByClassName.put(className, excludedFields);
}
lastParams = new ParamsBuilder("static field " + className + "#" + fieldName);
excludedFields.put(fieldName, lastParams);
return this;
}
@Override public BuilderWithParams thread(String threadName) {// 忽略某个线程
checkNotNull(threadName, "threadName");
lastParams = new ParamsBuilder("any threads named " + threadName);
threadNames.put(threadName, lastParams);
return this;
}
/** Ignores all fields and static fields of all subclasses of the provided class name. */
@Override public BuilderWithParams clazz(String className) {// 忽略某个类
checkNotNull(className, "className");
lastParams = new ParamsBuilder("any subclass of " + className);
classNames.put(className, lastParams);
return this;
}
public BuilderWithParams named(String name) {
lastParams.name = name;
return this;
}
public BuilderWithParams reason(String reason) {
lastParams.reason = reason;
return this;
}
public BuilderWithParams alwaysExclude() {
lastParams.alwaysExclude = true;
return this;
}
@Override public ExcludedRefs build() {
return new ExcludedRefs(this);
}
}
接下来,继续调用 buildAndInstall 方法;
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
RefWatcher 用来观察 Activity 的释放资源,并分析内存泄漏情况。LeakCanary.enableDisplayLeakActivity(context) 方法是提示内存泄漏的通知提醒。
再往下继续查看 ActivityRefWatcher.install((Application) context, refWatcher),这里是对内存是否泄漏判断的核心;
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
查看watchActivities 方法 :
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities(); // 只需要处理一次,先关闭之前的监听
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
watchActivities 方法先取消注册之前的的监听方法,然后注册此次运行Activity的内存泄漏的观察。
接下来看下 lifecycleCallbacks 这个类:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
ActivityLifecycleCallbacks 实则是对每个 Activity 的生命周期进行监听;
最后一行,onActivityDestroyed 的销毁方法中 ActivityRefWatcher 会调用 onActivityDestroyed 方法:
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
当 Activity 销毁的时候,会调用到 refWatcher 的 watch 方法,解析这个方法之前,先去看下 refWatcher 这个类有哪些成员变量:
public final class RefWatcher {
public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();
private final WatchExecutor watchExecutor;
private final DebuggerControl debuggerControl;
private final GcTrigger gcTrigger;
private final HeapDumper heapDumper;
private final Set retainedKeys;
private final ReferenceQueue
WatchExecutor watchExecutor:查找检测内存泄露的对象
DebuggerControl debuggerControl:检测当前是否正在调试中
GcTrigger gcTrigger:调用gc方法
HeapDumper heapDumper:dump内存泄露产生的文件
SetretainedKeys:存储引用key(待检测或已经产生泄露)
ReferenceQueue queue:引用队列,存储待检测的弱引用
HeapDump.Listener heapdumpListener:HeapDumper的回调
ExcludedRefs excludedRefs:排除系统引起的内存泄露
boolean computeRetainedHeapSize:检测泄露时是否计算堆的大小,默认为false
接下来继续看 RefWatcher 类的 watch 方法:
private final Set retainedKeys;
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference"); // 检测 watchedReference 是否为 null
checkNotNull(referenceName, "referenceName"); // 检测 referenceName 是否为 null
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString(); // 获取一个唯一Id
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference); // 执行 gc 操作
}
watch 方法会生成一个唯一 ID,同时将唯一 ID 存入当 一个 Set 集合中,然后会根据相关信息创建一个 KeyedWeakReference 类弱引用,并与 RefWatcher 中的 quene 进行关联。我们知道对于弱引用,在下一次 gc 的时候会被清除掉,如果不能被清除,则有可能会产生内存泄漏的情况;
final class KeyedWeakReference extends WeakReference
接下来,查看 ensureGoneAsync 方法;
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {// 线程池执行
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
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)) { // 如果不包含key ,即 activity 的弱引用被删了。 则可以完全回收不必担心内存泄漏
return DONE;
}
gcTrigger.runGc(); // 使用代码调用 GC,内存回收;
removeWeaklyReachableReferences();
if (!gone(reference)) {// 如果 activity 还存在弱引用中,则可能出现泄漏
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;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() { // 删除 RefWatcher 中的 KeyedWeakReference
// 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) { // 取 KeyedWeakReference 弱引用对象
retainedKeys.remove(ref.key);
}
}
因为 KeyedWeakReference 和 RefWatcher 的 quene 进行了关联, ensureGone 方法先遍历 quene 进行删除,此时如果对应 Activity 的 KeyedWeakReference 被删除了,就没有内存泄漏情况;如果未被删除,而且再次代码进行 GC 内存回收操作,还不能删除,则认定此 Activity 因为相关引用无法被删除,则会发生内存泄漏的情况;
对上面先做一个小的总结,LeakCanary 运行到这里主要做了哪些工作?
1 创建一个 Refwatcher ,启动一个ActivityRefWatcher。
2 通过 ActivityLiftcycleCallbacks 把 Activity 的 ondestory 生命周期关联。
3 将 Activity 以及相关信息放入到 KeyedWeakReference 弱引用对象中,调用 Gc 内存回收,通过 KeyedWeakReference 能否被回收掉判断是否存在内存卸扣情况;
之后创建 HeapDump 对象,并传达监听信息;
接下里继续分析 heapdumpListener.analyze(...) 方法:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
analyze 方法的实现类在 ServiceHeapDumpListener 中 Refwatcher 初始化第一步即提到过 ,继续执行 HeapAnalyzerService.runAnalysis 方法:
public final class HeapAnalyzerService extends IntentService {
private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
private static final String HEAPDUMP_EXTRA = "heapdump_extra";
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);
}
public HeapAnalyzerService() {
super(HeapAnalyzerService.class.getSimpleName());
}
@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);
}
}
HeapAnalyzerService 是 IntentService 的子类,onHandlerIntent 方法主要做了一下事情:
1 创建 HeapAnalyzer 对象,这里 heapDump.excludedRefs 排除了系统引起的内存泄漏。
2 执行 heapAnalyzer.checkForLeak 方法查找内存泄漏信息。
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 {
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));
}
}
checkForLeak 这里看到如果 heapDumpFile 不存在会抛出异常。存在的情况下:
1 .hprof 转化成 Snapshot。
2 deduplicateGcRoots(snapshot) 去除重复的泄漏文件。
3 findLeakingReference 找出内存泄漏的引用和 findLeakTrace 找出内存泄漏的路径。
findLeakingReference 获取内存泄漏对象的方法:
private Instance findLeakingReference(String key, Snapshot snapshot) {
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
List keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {
List values = classInstanceValues(instance);
String keyCandidate = asString(fieldValue(values, "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);
}
napshot.findClass(KeyedWeakReference.class.getName()) 通过查找弱引用的对象,然后通过遍历该弱引用的所有实例,当发现 key 值相等的,返回泄漏对象并存储到集合中。
findLeakTrace 方法获取内存泄漏对象的路径:
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
Instance leakingRef) {
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
long retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
通过buildLeakTrace 方法 ShortestPathFinder 对象找到内存泄漏的路径,同时 retainedSize 表示内存泄漏的空间大小。
到这里就完成内存泄漏信息分析,并通过 leakDetected 方法将内存泄漏的结果信息反馈出来。