这个名字我很喜欢。于是我先摘录一段金丝雀的故事:
17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。
原文地址://www.greatytc.com/p/49239eac7a76
LeakCanary简单介绍:
帮我们在Android或Java项目开发时检测内存泄漏的库。
square - github地址
引:什么叫内存泄漏?内存溢出?
- 内存溢出(
out of memory
):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。 - 内存泄漏(
memory leak
):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
so:memory leak
会最终会导致out of memory
!
一、源码导读
对于还没有接触过LeakCanary源码的同学来说,先有个大致印象很重要。
1. 包结构
首先,它的包结构是这个样子:
LeakCanary这个分包简直可以称得上范本了,下面简单介绍下每个包大致的功能:
-
leakcanary-analyzer
:负责泄漏信息的分析 -
leakcanary-android
:负责核心模块与Android的对接,还包含着UI展示部分 -
leakcanary-android-instrumentation
:单元测试用的 -
leakcanary-android-no-op
:release环境下引用的空包 -
leakcanary-sample
:库使用的demo -
leakcanary-support-fragment
:v4包额外适配支持 -
leakcanary-watcher
:监听泄漏核心部分
2. 监听泄漏流程图
3. 模块结构
这个模块结构并不能代表LeakCanary的模块结构,而是为了这篇文章后面的源码解析,同学们读源码的时候也可以用这张图做一个参考,至少不会手足无措。
二、源码解析(Android视角)
源码阅读不像读文章,并不遵循从上到下的逻辑。一般我们从引用入口开始阅读,配合IDE,阅读起来很方便。按照这个顺序,就能整理出上面模块结构那张图。其中“泄漏判断”、“泄漏信息分析”、“泄漏信息存储”三部分涉及到UI展示。后文在源码解析的时候,我会把UI展示部分的逻辑单独抽出来,方便理解。
1. 安装
所谓安装,就是就是把这个库在我们的项目中初始化,让其可以为我所用。LeakCanary是在Application中初始化的。代码如下:
//com.example.leakcanary.ExampleApplication
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
源码阅读第一步:点进去!
我们看到在install之前有个判断,这个判断是用来判断进程的。这个方法最终会调用LeakCanaryInternals#isInServiceProcess
,通过PackageManager
、ActivityManager
以及android.os.Process
来判断当前进程是否为HeapAnalyzerService
运行的进程。LeakCanary的操作会新起一个名为:leakcanary
的进程,以不影响我们主程序的使用。在leakcanary-android的manifest中,我们可以看到相关配置:
<!--leakcanary-sample/src/main/AndroidManifest.xml-->
<service
android:name=".internal.HeapAnalyzerService"
android:process=":leakcanary"
android:enabled="false"
/>
<service
android:name=".DisplayLeakService"
android:process=":leakcanary"
android:enabled="false"
/>
<activity
android:theme="@style/leak_canary_LeakCanary.Base"
android:name=".internal.DisplayLeakActivity"
android:process=":leakcanary"
android:enabled="false"
android:label="@string/leak_canary_display_activity_label"
android:icon="@mipmap/leak_canary_icon"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
从中我们可以看到DisplayLeakActivity
被设置为Launcher,并设置了金丝雀的icon,这也就是为什么使用LeakCanary会在桌面上生成icon入口的原因。但是,这里要注意DisplayLeakActivity
的enable
属性被设置为false了,默认在桌面上是不会显示入口的。
回过头来,我们用同样的方式查看LeakCanary#install
的源码:
//com.squareup.leakcanary.LeakCanary
@NonNull
public static RefWatcher install(@NonNull Application application) {
return refWatcher(application)
.listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
这里正式进入安装流程,refWatcher
创建了一个AndroidRefWatcherBuilder
//com.squareup.leakcanary.LeakCanary
@NonNull
public static AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
然后,经过一系列设置,最终调用了buildAndInstall
。
//com.squareup.leakcanary.AndroidRefWatcherBuilder
/**
* Creates a {@link RefWatcher} instance and makes it available through {@link
* LeakCanary#installedRefWatcher()}.
*
* Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
*
* @throws UnsupportedOperationException if called more than once per Android process.
*/
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
这个方法读起来也不复杂,首先判断了refWatcher
是否已被初始化。build()
方法构建一个RefWatcher
,这个东西就是用来检查内存泄漏的。然后判断refWatcher
可用后有三个if代码块,这里按顺序结束一下分别的作用。
-
setEnabledAsync
最终调用了packageManager.setComponentEnabledSetting
,将Activity组件设置为可用,即之前提到的manifest中enable
属性。也就是说,当我们运行LeakCanary.install(this)
的时候,LeakCanary的icon才显示出来。 -
ActivityRefWatcher.install
和FragmentRefWatcher.Helper.install
的功能差不多,注册了生命周期监听。不同的是,前者用application监听Activity的生命周期,后者用Activity监听fragment的生命周期,而且用到了leakcanary-support-fragment
包,兼容了v4的fragment。
后面开始便涉及检查时机的判断了,进入下一环节。
2. 检查时机
关于用application监听Activity的生命周期,我们使用application.registerActivityLifecycleCallbacks
,而用Activity监听fragment的生命周期中的Activity实例也是通过这个方法拿到的。监听的fragment的生命周期的相关代码如下:
//com.squareup.leakcanary.internal.AndroidOFragmentRefWatcher
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
如图,与监听Activity生命周期一样,最后调用了refWatcher.watch
方法(在ActivityLifecycleCallbacksAdapter
的onActivityDestroyed
方法中)。refWatcher.watch
方法便是泄漏检查的触发点,如图:
//com.squareup.leakcanary.RefWatcher
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
但是真正的检查操作并不会马上开始运行。watchExecutor
方法的实现类为com.squareup.leakcanary.AndroidWatchExecutor
,具体代码如下:
//com.squareup.leakcanary.AndroidWatchExecutor
public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
private final Handler mainHandler;
private final Handler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
从代码中可以看出影响检查代码开始运行时间的一共有两个地方:
-
Looper.myQueue().addIdleHandler
queueIdle
方法会在主线程空闲时执行,并不需要我们去指定一个指定的时间。 -
backgroundHandler.postDelayed
首先注意的是这个Handler的Looper取自哪里。它并不是我们平时常用的那个Looper。使用HandlerThread
可以创建一个维护着Looper的线程,使用这个Looper我们postDelayed
的方法便运行在非UI线程的消息队列中。 -
retryable.run()
方法的返回值
如果返回值为RETRY
时,会再次延时再次尝试执行。延迟初始时间为5s,以后每次重试时间x2。 查看run()
方法实现,可以看到返回值产生的位置是在com.squareup.leakcanary.RefWatcher#ensureGone
,在两种情况下会得到返回值为RETRY
:(a) debug模式启动时;(b)创建dumpHeap文件失败时;(c) 5s 后UI线程未空闲时(见:【4. 泄漏判断UI展示】)。
ensureGone
方法中开始涉及泄漏判断了,进入下一环节。
3. 泄漏判断
泄漏判断的逻辑主要部分即为ensureGone
方法:
//com.squareup.leakcanary.RefWatcher
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//纳秒,返回值只和进程已运行的时间有关, 不受调系统时间影响.返回的数值实际是64位无符号数,System.nanoTime()的返回值要用相减是否大于0来判断调用的先后顺序, 但不能用>或<来判断.
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)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//这里阻塞,将“hprof”数据转储到指定的文件。 这可能会导致GC。
//这个文件以.hprof结尾
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
//TODO ...为方便查看泄漏判断的逻辑,此处省略泄漏信息分析的逻辑
}
return DONE;
}
泄漏判断的逻辑很简单:
- 尝试移除弱引用,然后检查弱引用是否正常移除。
- 若未正常移除,gc(给了100ms,然后运行runFinalization),重复步骤1。
- 再次失败则初步认定为内存泄漏,开始后续操作。
提示:弱引用是在执行refWatcher.watch
时得到的
4. 泄漏判断UI展示
泄漏判断的UI展示部分是在创建heapDump文件时:
//com.squareup.leakcanary.AndroidHeapDumper
@Override
@Nullable
public File dumpHeap() {
//创建制定命名的空文件.hprof
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {//阻塞至toast放入waitingForToast,或超时
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Notification.Builder builder = new Notification.Builder(context)
//TODO ...设置通知Notification
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
//将“hprof”数据转储到指定的文件。 这可能会导致GC。
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;
}
}
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override
public void run() {
if (resumedActivity == null) {
waitingForToast.set(null);
return;
}
final Toast toast = new Toast(resumedActivity);
//TODO ...设置toast布局
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
waitingForToast.set(toast);
return false;
}
});
}
});
}
- 判断出现泄漏后在当前页面弹出带图标的toast提示,正常弹出后显示通知
- 当前页面是通过application注册的生命周期监听拿到的,相关生命周期为
onResume
和onPause
。 - toast提示使用CountDownLatch阻塞子线程5s,若5s后UI线程仍未空闲,则返回重试标识,即检查时机中 【3.(c)】。方便理解,这里需要了解一个类:
//com.squareup.leakcanary.internal.FutureResult
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public final class FutureResult<T> {
//原子性引用,是引用对象线程安全,多线程更新保证一致性
private final AtomicReference<T> resultHolder;
//能够使一个或多个线程等待其他线程完成各自的工作后再执行,通过一个计数器来实现。
private final CountDownLatch latch;
public FutureResult() {
resultHolder = new AtomicReference<>();
latch = new CountDownLatch(1);
}
public boolean wait(long timeout, TimeUnit unit) {
try {
//阻塞线程,至count为0,线程中断,或timeout
return latch.await(timeout, unit);
} catch (InterruptedException e) {
throw new RuntimeException("Did not expect thread to be interrupted", e);
}
}
public T get() {
if (latch.getCount() > 0) {
throw new IllegalStateException("Call wait() and check its result");
}
return resultHolder.get();
}
public void set(T result) {
resultHolder.set(result);
latch.countDown();
}
}
- 将泄漏信息保存下来后,取消toast显示和通知。
5. 泄漏信息分析
泄漏信息分析是在泄漏判断之后,初步判定为泄漏后,相关代码是 【3.泄漏判断】中省略的代码部分:
//com.squareup.leakcanary.RefWatcher#ensureGone
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//这里阻塞,将“hprof”数据转储到指定的文件。 这可能会导致GC。
//这个文件以.hprof结尾
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump =
heapDumpBuilder
.heapDumpFile(heapDumpFile)
.referenceKey(reference.key)//uuid
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//启动一个前台服务 新进程名为:leakcanary
heapdumpListener.analyze(heapDump);
//启动HeapAnalyzerService显示分析进度通知
//启动DisplayLeakService保存分析结果.result文件
//显示点击进入DisplayLeakActivity的通知
}
-
heapDumper.dumpHeap()
方法中使用android.os.Debug
包获取dumpHprof数据,保存为.hprof文件。 - 顺着
heapdumpListener.analyze(heapDump)
方法可以寻到leakcanary-analyzer
包下的文件HeapAnalyzer
的checkForLeak
方法:
//com.squareup.leakcanary.HeapAnalyzer#checkForLeak
try {
//使用haha库分析堆栈
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
DataBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = Snapshot.createSnapshot(buffer);
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
//把Snapshot里堆栈信息转存到THashMap,通过信息构建一个key
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak("UnknownNoKeyedWeakReference", since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
//com.squareup.leakcanary.HeapAnalyzer
/**
* Pruning duplicates reduces memory pressure from hprof bloat added in Marshmallow.
* 把Snapshot里堆栈信息转存到THashMap,通过信息构建一个key
*/
void deduplicateGcRoots(Snapshot snapshot) {
// THashMap has a smaller memory footprint than HashMap.
// THashMap的内存占用量比HashMap小。
final THashMap<String, RootObj> uniqueRootMap = new THashMap<>();
final Collection<RootObj> gcRoots = snapshot.getGCRoots();
for (RootObj root : gcRoots) {
String key = generateRootKey(root);
if (!uniqueRootMap.containsKey(key)) {
uniqueRootMap.put(key, root);
}
}
// Repopulate snapshot with unique GC roots.
gcRoots.clear();
uniqueRootMap.forEach(new TObjectProcedure<String>() {
@Override
public boolean execute(String key) {
return gcRoots.add(uniqueRootMap.get(key));
}
});
}
- findLeakTrace会过滤掉预先设置的假泄漏,这里的预先设置,便是在
LeakCanary.install(this)
时通过excludedRefs
方法设置的,见【1. 安装】。
6. 泄漏信息分析UI展示
泄漏信息分析的UI主要是一个通知栏的进度条,在【5. 泄漏信息分析】第二步的代码中可以看到listener.onProgressUpdate
便是在更新进度条,UI代码由HeapAnalyzerService
实现AnalyzerProgressListener
:
//com.squareup.leakcanary.internal.HeapAnalyzerService
@Override public void onProgressUpdate(Step step) {
int percent = (int) ((100f * step.ordinal()) / Step.values().length);
CanaryLog.d("Analysis in progress, working on: %s", step.name());
String lowercase = step.name().replace("_", " ").toLowerCase();
String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
showForegroundNotification(100, percent, false, message);
}
其中Step
是一个枚举类型:
//com.squareup.leakcanary.AnalyzerProgressListener
enum Step {
READING_HEAP_DUMP_FILE,
PARSING_HEAP_DUMP,
DEDUPLICATING_GC_ROOTS,
FINDING_LEAKING_REF,
FINDING_SHORTEST_PATH,
BUILDING_LEAK_TRACE,
COMPUTING_DOMINATORS,
}
7. 泄漏信息存储
- 在
AndroidHeapDumper
类的dumpHeap
方法第一行(代码见【4. 泄漏判断UI展示】)我们可以看到,泄漏信息的储存文件由com.squareup.leakcanary.DefaultLeakDirectoryProvider
创建,最多创建7个文件,数目超过后,删除最早创建的文件。所有文件保存在Download文件加下。 - 通过
android.os.Debug
获取数据后,存入本地的为.hprof文件。 - 数据解析后会启动
DisplayLeakService
(AbstractAnalysisResultService
的子类),这里会读取.hprof文件,读取后会重命名然后保存为.result文件。
//com.squareup.leakcanary.DisplayLeakService#onHeapAnalyzed
heapDump = renameHeapdump(heapDump);
boolean resultSaved = saveResult(heapDump, result);
//com.squareup.leakcanary.DisplayLeakService
private boolean saveResult(HeapDump heapDump, AnalysisResult result) {
File resultFile = AnalyzedHeap.save(heapDump, result);
return resultFile != null;
}
private HeapDump renameHeapdump(HeapDump heapDump) {
String fileName = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'.hprof'", Locale.US).format(new Date());
File newFile = new File(heapDump.heapDumpFile.getParent(), fileName);
boolean renamed = heapDump.heapDumpFile.renameTo(newFile);
if (!renamed) {
CanaryLog.d("Could not rename heap dump file %s to %s", heapDump.heapDumpFile.getPath(),
newFile.getPath());
}
return heapDump.buildUpon().heapDumpFile(newFile).build();
}
8. 泄漏信息存储UI展示
泄漏信息存储部分的UI展示是一个可以点击的通知,点击之后打开DisplayLeakActivity
。在【7. 泄漏信息存储】saveResult
方法调用之后紧接着便是如下代码:
//com.squareup.leakcanary.DisplayLeakService#onHeapAnalyzed
String contentTitle;
if (resultSaved) {
PendingIntent pendingIntent =
DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure != null) {
contentTitle = getString(R.string.leak_canary_analysis_failed);
} else {
String className = classSimpleName(result.className);
if (result.leakFound) {
if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
}
} else {
String size = formatShortFileSize(this, result.retainedHeapSize);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
}
}
} else {
contentTitle = getString(R.string.leak_canary_class_no_leak, className);
}
}
String contentText = getString(R.string.leak_canary_notification_message);
showNotification(pendingIntent, contentTitle, contentText);
9. UI界面
这里的UI界面说的是com.squareup.leakcanary.internal.DisplayLeakActivity
,也就是我们平时用到的通过桌面入口进入的泄漏信息查看Activity。
//com.squareup.leakcanary.internal.DisplayLeakActivity
@Override protected void onResume() {
super.onResume();
LoadLeaks.load(this, getLeakDirectoryProvider(this));
}
在onResume的时候使用了LoadLeaks
,并传入一个Provider,这个Provider就是【7. 泄漏信息存储】创建泄漏信息的储存文件时所用到的DefaultLeakDirectoryProvider
,而在load方法中最终执行了如下方法:
//com.squareup.leakcanary.internal.DisplayLeakActivity#LoadLeaks#run
@Override public void run() {
final List<AnalyzedHeap> leaks = new ArrayList<>();
List<File> files = leakDirectoryProvider.listFiles(new FilenameFilter() {
@Override public boolean accept(File dir, String filename) {
return filename.endsWith(".result");
}
});
for (File resultFile : files) {
final AnalyzedHeap leak = AnalyzedHeap.load(resultFile);
if (leak != null) {
leaks.add(leak);
}
}
Collections.sort(leaks, new Comparator<AnalyzedHeap>() {
@Override public int compare(AnalyzedHeap lhs, AnalyzedHeap rhs) {
return Long.valueOf(rhs.selfFile.lastModified())
.compareTo(lhs.selfFile.lastModified());
}
});
mainHandler.post(new Runnable() {
@Override public void run() {
inFlight.remove(LoadLeaks.this);
if (activityOrNull != null) {
activityOrNull.leaks = leaks;
activityOrNull.updateUi();
}
}
});
}
简单的说,读取.result文件,更新ui。
三、总结
1. 原理总结
- application启动时,通过application实例监听Activity和fragment生命周期。
- 在生命周期结束后,主线程空闲时,再延时开始检测泄漏。
- 尝试两次回收,再判断实例是否存在,若存在则判定泄漏。
- 使用
android.os.Debug
包获取.hprof数据。 - 使用haha库分析并精简泄漏信息,排除特例后保存泄漏信息。