LeakCanary 是由 Square 開源的針對 Android
和 Java
的記憶體洩漏檢測工具。
使用
LeakCanary
的整合過程很簡單,首先在 build.gradle
檔案中新增依賴:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
複製程式碼
debug
和 release
版本中使用的是不同的庫。LeakCanary
執行時會經常執行 GC
操作,在 release
版本中會影響效率。android-no-op
版本中基本沒有邏輯實現,用於 release
版本。
然後實現自己的 Application
類:
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.install(this)
開始分析:
LeakCanary.install
LeakCanary.java
/**
* 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
/** 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
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
複製程式碼
RefWatcherBuilder.java
/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
複製程式碼
DisplayLeakService.java
/**
* 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
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}
複製程式碼
AndroidExcludedRefs.java
/**
* 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
/**
* 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
/** 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);
:
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
複製程式碼
最後執行到 LeakCanaryInternals#setEnabledBlocking
:
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"
:
<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
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
的引用。
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
/**
* 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<String>
集合,每個檢測的物件都對應著一個唯一的key
,儲存在retainedKeys
中KeyedWeakReference
: 自定義的弱引用,持有檢測物件和對用的key
值
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
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
物件。
@Override protected WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
複製程式碼
DEFAULT_WATCH_DELAY_MILLIS
為 5 s。
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
@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 秒執行記憶體洩漏檢測工作。
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
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()
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()
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
複製程式碼
判斷 retainedKeys
中是否包含當前 Activity
引用的 key
值。
如果不包含,說明上一步操作中 retainedKeys
移除了該引用的 key
值,也就說上一步操作之前引用佇列 queue
中包含該引用,GC
處理了該引用,未發生記憶體洩漏,返回 DONE
,不再往下執行。
如果包含,並不會立即判定發生記憶體洩漏,可能存在某個物件已經不可達,但是尚未進入引用佇列 queue
。這時會主動執行一次 GC
操作之後再次進行判斷。
gcTrigger.runGc()
/**
* 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
@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
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
複製程式碼
這裡的 heapdumpListener
是 ServiceHeapDumpListener
物件,接著進入 ServiceHeapDumpListener.runAnalysis()
方法。
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
複製程式碼
這裡的 listenerServiceClass
指的是 DisplayLeakService.class
,文章開頭提到的 AndroidRefWatcherBuilder
中進行了配置。
@Override protected HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
複製程式碼
HeapAnalyzerService.runAnalysis
HeapAnalyzerService.runAnalysis()
方法中啟動了它自己,傳遞了兩個引數,DisplayLeakService
類名和要分析的 heapDump
。啟動自己後,在 onHandleIntent
中進行處理。
/**
* 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
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
@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
@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
向開發者通知記憶體洩漏。
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
:
/**
* 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
的使用,四大元件的開啟和關閉等等,都很值的大家去深究。
文章同步更新於微信公眾號:
秉心說
, 專注 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!