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();
}
调用 buildAndInstall() 方法返回 RefWatcher 类:
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);
}
};
最后一行,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<String> retainedKeys;
private final ReferenceQueue<Object> queue;
private final HeapDump.Listener heapdumpListener;
private final ExcludedRefs excludedRefs;
}
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 方法:
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();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
watch 方法中传入的 Acitivity 对象,此是通过生成唯一号,给此销毁的 Activity 创建一个 KeyedWeakReference 弱引用的对象,然后调用 ensureGoneAsync(watchStartNanoTime, reference) 方法:
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);
}
我们在 Activity 进行 destory 的时候把 Activity 放在一个 弱引用中,ensureGone 方法会尝试删除弱引用中的数据,如果发现 Activity 可以被删掉,那么不存在内存泄漏的情况。 如果 Activity 不能被从弱引用中成功删除,再次代码调用 gcTrigger.runGc() 执行 GC 方法,进行检测 Activity 的弱引用是否还存在。此时,如果 Activity 依然不能被成功删除(可能有其他对象对其引用),则可能存在内存泄漏情况。启动 heapdumpListener.analyze() 方法进行内存泄漏分析。
对上面先做一个小的总结,程序运行到这里主要做了哪些工作?
1 创建一个 refwatcher ,启动一个ActivityRefWatcher。
2 通过 ActivityLiftcycleCallbacks 把 Activity 的 ondestory 生命周期关联。
3 线程池中分析我们的泄漏情况。
接下里继续分析 heapdumpListener.analyze(...) 方法:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
analyze 方法的实现类在 ServiceHeapDumpListener 中,继续执行 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 的父类,则这里会执行到 onHandleIntent 处理的方法,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<String> keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> 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 方法将内存泄漏的结果信息反馈出来。
LeakCanary分析.png
网友评论