一、 前言
1. Java 內存模型
名稱 | 特征 | 作用 | 配置參數 | 異常 |
---|---|---|---|---|
程序計數器 | 占用內存小,線程私有,生命周期與線程相同 | 大致為字節碼行號指示器 | 無 | 無 |
虛擬機棧 | 線程私有,生命周期與線程相同,使用連續的內存空間 | Java 方法執行的內存模型,存儲局部變量表、操作棧、動態鏈接、方法出口等信息 | -Xss | StackOverflowError、OutOfMemoryError |
java堆 | 線程共享,生命周期與虛擬機相同,可以不使用連續的內存地址 | 保存對象實例,所有對象實例(包括數組)都要在堆上分配 | -Xms、-Xsx、-Xmn | OutOfMemoryError |
方法區 | 線程共享,生命周期與虛擬機相同,可以不使用連續的內存地址 | 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據 | -XX:PermSize:16M 、-XX:MaxPermSize64M | OutOfMemoryError |
運行時常量池 | 方法區的一部分,具有動態性 | 存放字面量及符號引用 |
2. Java垃圾回收策略
- 引用計數算法:給對象中添加一個引用計數器,每當有引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用。
- 可達性分析算法:通過一系列的稱為"GC Root"的對象作為起點,從這些節點開始向下搜索,搜素所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何的引用鏈相連,就判定對象可以被回收
3. Java 四種引用類型
- 強引用:默認的引用方式,當內存空間不足,JVM寧愿觸發OOM,也不會對這部分內存進行回收。
Object obj = new Object();
obj = null;
- 軟引用(SoftReference):當內存空間不足的時候,JVM會回收這部分內存,一般用來做緩存策略。
- 弱引用(WeakReference): 當GC回收的時候,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收這部分內存。
- 虛引用(PhantomReference):當 GC回收的時候會回收這部分內存, 主要用于檢測對象是否已經從內存中刪除。
代碼示例
public class TestReference {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
String sw = "虛引用";
switch (sw) {
case "軟引用":
Object objSoft = new Object();
SoftReference<Object> softReference = new SoftReference<>(objSoft, referenceQueue);
System.out.println("GC前獲取:" + softReference.get());
objSoft = null;
System.gc();
Thread.sleep(1000);
System.out.println("GC后獲取:" + softReference.get());
System.out.println("隊列中的結果:" + referenceQueue.poll());
break;
case "弱引用":
Object objWeak = new Object();
WeakReference<Object> weakReference = new WeakReference<>(objWeak, referenceQueue);
System.out.println("GC前獲取:" + weakReference.get());
objWeak = null;
System.gc();
Thread.sleep(1000);
System.out.println("GC后獲取:" + weakReference.get());
System.out.println("隊列中的結果:" + referenceQueue.poll());
break;
case "虛引用":
Object objPhan = new Object();
PhantomReference<Object> phantomReference = new PhantomReference<>(objPhan, referenceQueue);
System.out.println("GC前獲取:" + phantomReference.get());
objPhan = null;
System.gc();
//此處的區別是當objPhan的內存被gc回收之前虛引用就會被加入到ReferenceQueue隊列中,其他的引用都為當引用被gc掉時候,引用會加入到ReferenceQueue中
Thread.sleep(1000);
System.out.println("GC后獲取:" + phantomReference.get());
System.out.println("隊列中的結果:" + referenceQueue.poll());
break;
}
}
}
軟引用運行結果:
弱引用運行結果:
虛引用運行結果:
二、 集成
build.gradle文件
dependencies {
// debug 版本依賴
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
// release 版本依賴
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// 如果使用了 support fragment,請同時依賴
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
// 最新2.0版本,只需要導入如下依賴,不再需要在Application里面添加其他代碼
// debugImplementation because LeakCanary should only run in debug builds.
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
}
Application#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);
三、 LeakCanary源碼導讀
3.1. LeakCanary項目架構
LeakCanary 項目結構圖:
- leakcanary-analyzer : 負責分析內存泄漏
- leakcanary-android : android 核心模塊以及通知、界面展示等
- leakcanary-android-instrumentation : 單元測試使用
- leakcanary-sample : 使用案例
- leakcanary-support-fragment : 支持 fragment v4包
- leakcanary-watcher : 負責監聽內存泄漏
- leakcanary-android-no-op : release 版本使用
3.2. LeakCanary執行過程
3.3. LeakCanary初始化
/**
* Whether the current process is the process running the {@link HeapAnalyzerService}, which is
* a different process than the normal app process.
*/
public static boolean isInAnalyzerProcess(@NonNull Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// This only needs to be computed once per process.
Log.d("LeakCanary", "isInAnalyzerProcess = " + isInAnalyzerProcess);
if (isInAnalyzerProcess == null) {
// HeapAnalyzerService進程名是否等于主進程名稱
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
public static @NonNull void install(@NonNull Application application) {
refWatcher(application)
.listenerServiceClass(DisplayLeakService.class) // 設置 heapDumpListener
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 去除Android SDK 引起的內存泄漏
.buildAndInstall(); //創建RefWatcher
}
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
isInAnalyzerProcess方法,用于檢測內存分析進程是否和主進程名相同,如果是相同的,則設置RefWatcher = DISABLED ,不做任何處理。release 版本是通過以下以來確定的。
// release 版本依賴
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
3.4 AndroidRefWatcherBuilder 對象
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
// isAssignableFrom()方法是判斷是否為某個類的父類,instanceof()方法是判斷是否某個類的子類。
//(https://www.cnblogs.com/bethunebtj/p/4681438.html)
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
public final T excludedRefs(ExcludedRefs excludedRefs) {
heapDumpBuilder.excludedRefs(excludedRefs);
return self();
}
@Override protected @NonNull HeapDumper defaultHeapDumper() {
LeakDirectoryProvider leakDirectoryProvider =
LeakCanaryInternals.getLeakDirectoryProvider(context);
return new AndroidHeapDumper(context, leakDirectoryProvider);
}
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
Log.d("LeakCanary", "enableDisplayLeakActivity = " + enableDisplayLeakActivity);
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
Log.d("LeakCanary", "watchActivities = " + watchActivities);
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
Log.d("LeakCanary", "watchFragments = " + watchFragments);
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.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();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger
, heapDumper, heapDumpListener, heapDumpBuilder);
}
@Override protected @NonNull WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
buildAndInstall方法則用于設置heapDumpListener用于分析hprof文件, 過濾掉因為Android SDK引起的系統內存泄漏引用, 展示DisplayLeakActivity桌面icon,監聽Activity 以及Fragment生命周期,在其銷毀的時候調用RefWatcher的watch方法分析是否會發生內存泄漏對象。build方法則是初始化RefWatcher對象,
關于設置DisplayLeakActivity enabled = true的地方,需要看看詳細的代碼
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
public static void setEnabledAsync(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
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);
}
其實就是利用AsyncTask的THREAD_POOL_EXECUTOR線程池去執行PackageManager的setComponentEnabledSetting方法,動態設置COMPONENT_ENABLED_STATE_ENABLED
3.5 RefWatcher 對象
private final Set<String> retainedKeys;
private final ReferenceQueue<Object> queue; // 引用隊列
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);
}
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();
// 如果VM正連接到Debuger,忽略這次檢測,因為Debugger可能會持有一些在當前上下文中不可見的對象,導致誤判
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//上面執行 removeWeaklyReachableReferences 方法,判斷是不是監視對象已經被回收了,如果被回收了,那么說明沒有發生內存泄漏,直接結束
if (gone(reference)) {
return DONE;
}
// 手動觸發一次 GC 垃圾回收
gcTrigger.runGc();
// 再次移除所有弱引用可達對象
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 利用Debug生成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)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump); // 開始分析hprof文件
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
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);
}
}
RefWatcher很重要的方法就是watch方法, 其中參數watchedReference就是對應的view、activity、fragment對象。 通過UUID 函數生成唯一的標識添加到Set<String>集合當中。 KeyedWeakReference繼承了WeakReference,關聯了watchedReference。當進弱引用對象發生回收的時候,虛擬機就會向該引用添加到與之關聯的引用隊列當中。
檢測內存是否泄漏的過程也很簡單:首先會清除所有的弱引用可達對象, 判斷watchedReference對象是否已經被回收了, 如果沒有,則手動進行一個GC,二次確認watchedReference對象是否被回收,如果沒被回收則dump hprof文件,通知heapdumpListener分析hprof文件
3.6. 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;
// 利用HandlerThread對象里面的Looper進行子線程任務
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
WatchExecutor 是一個線程調度器。包含了一個主線程 mainHandler 和后臺線程backgroundHandler。整個的邏輯就是當主線程空閑的時候,才回去啟動后臺線程去執行Retryable.run方法。
3.7. AndroidHeapDumper 生成hprof文件
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)) {
Log.d("LeakCanary", "Did not dump heap, too much time waiting for Toast.");
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
// 創建正在dumping通知
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
// 系統Debug類提供的方法
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);
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
LayoutInflater inflater = LayoutInflater.from(resumedActivity);
toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
waitingForToast.set(toast);
Log.d("LeakCanary", "latch.countDown()");
return false;
}
});
}
});
}
dumpHeap方法利用Debug.dumpHprofData(String filePath)生成hprof文件,同時會展示Toast以及通知欄狀態。如果dump失敗或者Toast展示時間太長就會返回RETRY_LATER。
3.8 HeapAnalyzerService 分析hprof文件,找出泄漏路徑
@Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses);
Log.d("LeakCanary", "leak path:" + heapDump.heapDumpFile.getAbsolutePath());
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
HeapAnalyzer 是hprof文件解析類, 使用了squareup的另外一個開源庫—Haha。
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
@NonNull String referenceKey,
boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
Log.d("LeakCanary", "heapDumpFile path:" + heapDumpFile.getAbsolutePath());
Log.d("LeakCanary", "referenceKey = " + referenceKey);
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
// Hprof 文件協議介紹: https://my.oschina.net/u/217380/blog/1507542
try {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
// 把hprof文件映射到內存 ByteBuffer[]
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
// 創建了HprofParser對象,parse方法解析hprof協議,生成Snapshot
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 去除重復的GC root對象
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// 此對象不存在,表示已經被gc清除了,不存在泄露因此返回無泄漏
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
// 此對象存在, 也不能確認它內存泄漏了,要檢測此對象的gc root
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
3.9 DisplayLeakService 生成內存泄漏通知欄
protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
Log.d("LeakCanary", "[DisplayLeakService]: onHeapAnalyzed");
HeapDump heapDump = analyzedHeap.heapDump;
AnalysisResult result = analyzedHeap.result;
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
heapDump = renameHeapdump(heapDump);
boolean resultSaved = saveResult(heapDump, result);
String contentTitle;
if (resultSaved) {
PendingIntent pendingIntent =
DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure != null) {
contentTitle = "Leak 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);
} else {
onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
}
afterDefaultHandling(heapDump, result, leakInfo);
}
四、LeakCanary不足
雖然 LeakCanary 有諸多優點,但是它也有做不到的地方,比如說檢測申請大容量內存導致的OOM問題、Bitmap內存未釋放問題,Service 中的內存泄漏可能無法檢測等。
五、延申閱讀
1. Matrix ResourceCanary -- Activity 泄漏及Bitmap冗余檢測
2. Hprof 文件協議
3. GC那些事兒--Android內存優化第一彈