LeakCanary源码整体分析

LeakCanary是由Square开源的针对Android和Java的内存泄漏检测工具。

使用

LeakCanary的集成过程很简单,首先在build.gradle文件中添加依赖:

1
2
3
4
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

debug和release版本中使用的是不同的库。LeakCanary运行时会经常执行GC操作,在release版本中会影响效率。android-no-op版本中基本没有逻辑实现,用于release版本。

然后实现自己的Application类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ExampleApplication extends Application {

@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);
// Normal app init code...
}
}

这样就集成完成了。当LeakCanary检测到内存泄露时,会自动弹出Notification通知开发者发生内存泄漏的Activity和引用链,以便进行修复。

源码分析

LeakCanary.isInAnalyzerProcess(this)

因为LeakCanary会开启一个远程Service用来分析每次产生的内存泄露,而安卓的应用每次开启进程都会调用Applicaiton的onCreate方法,因此我们有必要预先判断此次Application启动是不是在analyze service启动的。

LeakCanary.java

1
2
3
public static boolean isInAnalyzerProcess(Context context) {
return LeakCanaryInternals.isInServiceProcess(context, HeapAnalyzerService.class);
}

LeakCanaryInternals.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();

PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), 4);
} catch (Exception var14) {
CanaryLog.d(var14, "Could not get package info for %s", new Object[]{context.getPackageName()});
return false;
}

String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);

ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (NameNotFoundException var13) {
return false;
}

if(serviceInfo.processName.equals(mainProcess)) {
CanaryLog.d("Did not expect service %s to run in main process %s", new Object[]{serviceClass, mainProcess});
return false;
} else {
int myPid = Process.myPid();
ActivityManager activityManager = (ActivityManager)context.getSystemService("activity");
RunningAppProcessInfo myProcess = null;
List runningProcesses = activityManager.getRunningAppProcesses();
if(runningProcesses != null) {
Iterator var11 = runningProcesses.iterator();

while(var11.hasNext()) {
RunningAppProcessInfo process = (RunningAppProcessInfo)var11.next();
if(process.pid == myPid) {
myProcess = process;
break;
}
}
}

if(myProcess == null) {
CanaryLog.d("Could not find running process for %d", new Object[]{Integer.valueOf(myPid)});
return false;
} else {
return myProcess.processName.equals(serviceInfo.processName);
}
}
}

判断Application是否是在service进程里面启动,最直接的方法就是判断当前进程名和Service所属的进程是否相同。当前进程名的获取方式是使用ActivityManager的getRunningAppProcessInfo方法,找到进程pid与当前进程pid相同的进程,然后从中拿到processName.service所属的进程名。获取service应处进程的方法是用PackageManager的getPackageInfo方法。

接下来分析入口函数LeakCanary.install(this):

LeakCanary.install

LeakCanary.java

1
2
3
4
5
6
7
8
9
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}

LeakCanary.refWatcher

LeakCanary.java

1
2
3
4
/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}

refWatcher()方法新建了一个AndroidRefWatcherBuilder 对象,该对象继承于RefWatcherBuilder类,配置了一些默认参数,利用建造者构建一个RefWatcher对象。

AndroidRefWatcherBuilder.listenerServiceClass

AndroidRefWatcherBuilder.java

1
2
3
4
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

RefWatcherBuilder.java

1
2
3
4
5
/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}

DisplayLeakService.java

1
2
3
4
5
6
7
8
/**
* Logs leak analysis results, and then shows a notification which will start {@link
* DisplayLeakActivity}.
*
* You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,
* String)} to add custom behavior, e.g. uploading the heap dump.
*/
public class DisplayLeakService extends AbstractAnalysisResultService {}

listenerServiceClass()方法绑定了一个后台服务DisplayLeakService,这个服务主要用来分析内存泄漏结果并发送通知。你可以继承并重写这个类来进行一些自定义操作,比如上传分析结果等。

RefWatcherBuilder.excludedRefs

RefWatcherBuilder.java

1
2
3
4
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}

AndroidExcludedRefs.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* This returns the references in the leak path that can be ignored for app developers. This
* doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
* in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
* developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
*/
public static ExcludedRefs.Builder createAppDefaults() {
return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}

public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
ExcludedRefs.Builder excluded = ExcludedRefs.builder();
for (AndroidExcludedRefs ref : refs) {
if (ref.applies) {
ref.add(excluded);
((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
}
}
return excluded;
}

excludedRefs()方法定义了一些对于开发者可以忽略的路径,意思就是即使这里发生了内存泄漏,LeakCanary也不会弹出通知。这大多是系统Bug导致的,无需用户进行处理。

AndroidRefWatcherBuilder.buildAndInstall

最后调用buildAndInstall()方法构建RefWatcher实例并开始监听Activity的引用:

AndroidRefWatcherBuilder.java

1
2
3
4
5
6
7
8
9
10
11
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}

看一下主要的build()和install()方法:

RefWatcherBuilder.build

RefWatcherBuilder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/** Creates a {@link RefWatcher}. */
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}

ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}

HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}

DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}

HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}

WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}

GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}

return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}

build()方法利用建造者模式构建 RefWatcher实例,看一下其中的主要参数:

  • watchExecutor : 线程控制器,在onDestroy()之后并且主线程空闲时执行内存泄漏检测
  • debuggerControl :判断是否处于调试模式,调试模式中不会进行内存泄漏检测
  • gcTrigger : 用于GC,watchExecutor首次检测到可能的内存泄漏,会主动进行 GC,GC之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作
  • heapDumper : dump内存泄漏处的heap信息,写入hprof文件
  • heapDumpListener : 解析完hprof文件并通知DisplayLeakService弹出提醒
  • excludedRefs : 排除可以忽略的泄漏路径

LeakCanary.enableDisplayLeakActivity

接下来就是最核心的install()方法,这里就开始观察Activity的引用了。在这之前还执行了一步操作,LeakCanary.enableDisplayLeakActivity(context); :

1
2
3
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}

最后执行到 LeakCanaryInternals#setEnabledBlocking :

1
2
3
4
5
6
7
8
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);
}

这里启用了DisplayLeakActivity并且显示应用图标。注意,这是指的不是你自己的应用图标,是一个单独的LeakCanary的应用,用于展示内存泄露历史的,入口函数是 DisplayLeakActivity,在 AndroidManifest.xml中可以看到默认情况下android:enabled=”false” :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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>

ActivityRefWatcher.install

ActivityRefWatcher.java

1
2
3
4
5
6
7
8
9
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}

public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

watchActivities()方法中先解绑生命周期回调注册lifecycleCallbacks,再重新绑定,避免重复绑定。lifecycleCallbacks监听了Activity的各个生命周期,在onDestroy()中开始检测当前Activity 的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
}
};

void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}

下面着重分析RefWatcher是如何检测Activity的。

RefWatcher.watch

调用RefWatcher#watch检测Activity。

RefWatcher.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}

/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
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()方法的参数是Object ,LeakCanary并不仅仅是针对Android 的,它可以检测任何对象的内存泄漏,原理都是一致的。

这里出现了几个新面孔,先来了解一下各自是什么:

  • retainedKeys : 一个Set集合,每个检测的对象都对应着一个唯一的key,存储在retainedKeys中。
  • KeyedWeakReference : 自定义的弱引用,持有检测对象和对用的key值。
1
2
3
4
5
6
7
8
9
10
11
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;

KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
  • queue : ReferenceQueue对象,和KeyedWeakReference配合使用

这里有个小知识点,弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue中。

接着看看具体的内存泄漏判断过程:

RefWatcher.ensureGoneAsync

1
2
3
4
5
6
7
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}

通过watchExecutor执行检测操作,这里的watchExecutor是 AndroidWatchExecutor对象。

1
2
3
@Override protected WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}

DEFAULT_WATCH_DELAY_MILLIS 为 5 s。

1
2
3
4
5
6
7
8
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;
}

看看其中用到的几个对象:

  • mainHandler : 主线程消息队列
  • handlerThread : 后台线程,HandlerThread对象,线程名为LeakCanary-Heap-Dump
  • backgroundHandler : 上面的后台线程的消息队列
  • initialDelayMillis : 5 s,即之前的 DEFAULT_WATCH_DELAY_MILLIS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}

void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}

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;
}
});
}

在具体的execute()过程中,不管是waitForIdle还是postWaitForIdle,最终还是要切换到主线程中执行。要注意的是,这里的IdleHandler到底是什么时候去执行?

我们都知道Handler是循环处理MessageQueue中的消息的,当消息队列中没有更多消息需要处理的时候,且声明了IdleHandler接口,这是就会去处理这里的操作。即指定一些操作,当线程空闲的时候来处理。当主线程空闲时,就会通知后台线程延时5秒执行内存泄漏检测工作。

1
2
3
4
5
6
7
8
9
10
11
12
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);
}

下面是真正的检测过程,AndroidWatchExecutor在执行时调用ensureGone()方法:

RefWatcher.ensureGone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
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;
}

再重复一次几个变量的含义,retainedKeys是一个Set集合,存储检测对象对应的唯一key值,queue是一个引用队列,存储被垃圾回收的对象。

主要过程有一下几步:

RefWatcher.emoveWeaklyReachableReferences()

1
2
3
4
5
6
7
8
private void removeWeaklyReachableReferences() {
// 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) {
retainedKeys.remove(ref.key);
}
}

遍历引用队列queue,判断队列中是否存在当前Activity的弱引用,存在则删除retainedKeys中对应的引用的key值。

RefWatcher.gone()

1
2
3
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}

判断retainedKeys中是否包含当前Activity引用的key值。

如果不包含,说明上一步操作中retainedKeys移除了该引用的key值,也就说上一步操作之前引用队列queue中包含该引用,GC处理了该引用,未发生内存泄漏,返回DONE,不再往下执行。

如果包含,并不会立即判定发生内存泄漏,可能存在某个对象已经不可达,但是尚未进入引用队列 queue。这时会主动执行一次GC操作之后再次进行判断。

gcTrigger.runGc()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued
* in the reference queue yet. This gives the application a hook to run the GC before the {@link
* RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible.
*/
public interface GcTrigger {
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}

private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};

void runGc();
}

注意这里调用GC的写法,并不是使用System.gc。System.gc仅仅只是通知系统在合适的时间进行一次垃圾回收操作,实际上并不能保证一定执行。

主动进行GC之后会再次进行判定,过程同上。首先调用removeWeaklyReachableReferences()清除retainedKeys中弱引用的key值,再判断是否移除。如果仍然没有移除,判定为内存泄漏。

内存泄露结果处理

AndroidHeapDumper.dumpHeap

判定内存泄漏之后,调用heapDumper.dumpHeap()进行处理:

AndroidHeapDumper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}

FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);

if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}

Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}

leakDirectoryProvider.newHeapDumpFile()新建了hprof文件,然后调用Debug.dumpHprofData() 方法dump当前堆内存并写入刚才创建的文件。

回到RefWatcher.ensureGone()方法中,生成heapDumpFile文件之后,通过heapdumpListener分析。

ServiceHeapDumpListener.analyze

1
2
3
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));

这里的heapdumpListener是ServiceHeapDumpListener对象,接着进入ServiceHeapDumpListener.runAnalysis()方法。

1
2
3
4
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}

HeapAnalyzerService.runAnalysis

HeapAnalyzerService.runAnalysis()方法中启动了它自己,传递了两个参数,DisplayLeakService 类名和要分析的 heapDump。启动自己后,在 onHandleIntent 中进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* This service runs in a separate process to avoid slowing down the app process or making it run
* out of memory.
*/
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);
}
}

heapAnalyzer.checkForLeak

checkForLeak方法中主要使用了Square公司的另一个库haha来分析 Android heap dump,得到结果后回调给 DisplayLeakService。

AbstractAnalysisResultService.sendResultToListener

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}

同样在 onHandleIntent 中进行处理。

DisplayLeakService.onHandleIntent

1
2
3
4
5
6
7
8
9
10
@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.onHeapAnalyzed

调用onHeapAnalyzed()之后,会将hprof文件删除。

DisplayLeakService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Override 
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);

boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);
}

PendingIntent pendingIntent;
String contentTitle;
String contentText;

if (!shouldSaveResult) {
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}

根据分析结果,调用showNotification()方法构建了一个Notification向开发者通知内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void showNotification(Context context, CharSequence contentTitle,
CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

Notification notification;
Notification.Builder builder = new Notification.Builder(context) //
.setSmallIcon(R.drawable.leak_canary_notification)
.setWhen(System.currentTimeMillis())
.setContentTitle(contentTitle)
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
if (SDK_INT >= O) {
String channelName = context.getString(R.string.leak_canary_notification_channel);
setupNotificationChannel(channelName, notificationManager, builder);
}
if (SDK_INT < JELLY_BEAN) {
notification = builder.getNotification();
} else {
notification = builder.build();
}
notificationManager.notify(notificationId, notification);
}

DisplayLeakService.afterDefaultHandling

最后还会执行一个空实现的方法 afterDefaultHandling:

1
2
3
4
5
6
7
/**
* You can override this method and do a blocking call to a server to upload the leak trace and
* the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
* AnalysisResult#excludedLeak} first.
*/
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
}

你可以重写这个方法进行一些自定义的操作,比如向服务器上传泄漏的堆栈信息等。

这样,LeakCanary 就完成了整个内存泄漏检测的过程。可以看到,LeakCanary 的设计思路十分巧妙,同时也很清晰,有很多有意思的知识点,像对于弱引用和 ReferenceQueue 的使用, IdleHandler 的使用,四大组件的开启和关闭等等,都很值的大家去深究。

相关知识

IdleHandler

你知道android的MessageQueue.IdleHandler吗?

android addIdleHandler 空闲线程 解析

监控应用中Activity的生命周期

Android开发 - ActivityLifecycleCallbacks使用方法初探

判断应用是否处于调试模式

判断apk是否运行在调试状态的方法Debug.isDebuggerConnected之原理

让系统进行GC

java System.gc()与Runtime.getRuntime().gc()有什么区别?

system.gc()和system.runFinalization()区别作用

System.gc()和Runtime.getRuntime().gc()的作用是相同的,只是建议系统进行垃圾回收,但系统进步进行垃圾回收是不确定的。

System.gc(); 告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的
System.runFinalization(); 强制调用已经失去引用的对象的finalize方法

Runtime.getRuntime().gc()和Runtime.getRuntime().runFinalization()的区别和上面的区别是一样的。

生成内存快照文件

[Android]生成heap dump文件(.hprof)

[Android] 内存泄漏调试经验分享 (二)

Android内存泄露利器(hprof篇)

android.os.Debug.dumpHprofData(hprofPath) 生成当前的内存快照文件

控制应用图标的显示

Android - 安装应用(APP) 不显示图标

安装了应用后,隐藏和显示应用的launcher显示

1
2
3
4
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled?1:2;
packageManager.setComponentEnabledSetting(component, newState, 1);

判断进程是否是前台进程

android 判断进程是否处于前台

主要参考链接:

LeakCanary 源码解析

LeakCanary

LeakCanary 内存泄露监测原理研究

深入理解 Android 之 LeakCanary 源码解析

LeakCanary源码分析

LeakCanary源码分析第三讲-HeapAnalyzerService详解

带你学开源项目:LeakCanary- 如何检测 Activity 是否泄漏

LeakCanary源码分析

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器