1、使用
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// 可選颅围,如果你使用支持庫的fragments的話
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
public class MyApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = setRefWatcher();
}
private RefWatcher setRefWatcher(){
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWathcher(Context context){
MyApplication myApplication = (MyApplication) context.getApplicationContext();
return myApplication.refWatcher;
}
}
這樣使用只會檢測Activity和標(biāo)準(zhǔn)Fragment是否發(fā)生內(nèi)存泄漏,如果要檢測V4包的Fragment在執(zhí)行完onDestroy()之后是否發(fā)生內(nèi)存泄露的話砚嘴,則需要在Fragment的onDestroy()方法中加上如下兩行代碼去監(jiān)視當(dāng)前的Fragment:
RefWatcher refWatcher = WanAndroidApp.getRefWatcher(_mActivity);
refWatcher.watch(this);
源碼分析
流程:
- 監(jiān)聽哪些類的堆內(nèi)存泄露情況
- 如何找到泄露的對象或類
- 找到泄露后如何找到導(dǎo)致泄露的引用鏈
- 找到引用鏈后將引用鏈拋出來寫入到泄露日志中
首先我們從LeakCanary.install()開始
LeakCanary.install():
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
refWatcher(application) : 創(chuàng)建一個AndroidRefWatcherBuilder
listenerServiceClass(DisplayLeakService.class) : 傳入了一個HeapDump.Listener桩盲,HeapDump.Listener中的一個參數(shù)是DisplayLeakService,DisplayLeakService的作用是展示泄露分析的結(jié)果日志湃番,然后會展示一個用于跳轉(zhuǎn)到顯示泄露界面DisplayLeakActivity的通知夭织。
excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) :
過濾那些系統(tǒng)或者制造商導(dǎo)致的泄露吭露,這些泄露是我們開發(fā)者無法處理的泄露buildAndInstall() : 創(chuàng)建一個RefWatcher吠撮,并且開始觀察Activity和Fragment。
注意,這里如果在一個進(jìn)程調(diào)用多次會拋出UnsupportedOperationException異常
AndroidExcludedRefs 類
它是一個enum類泥兰,它聲明了Android SDK和廠商定制的SDK中存在的內(nèi)存泄露的case弄屡,根據(jù)AndroidExcludedRefs這個類的類名就可看出這些case都會被Leakcanary的監(jiān)測過濾掉。目前這個版本是有46種這樣的case被包含在內(nèi)鞋诗,后續(xù)可能會一直增加膀捷。
這里我們來說一下如何觀察Activity和Fragment的:
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
我們看到,分別調(diào)用了ActivityRefWatcher.install(context, refWatcher);和FragmentRefWatcher.Helper.install(context, refWatcher);
我們先來看Activity:
public final class ActivityRefWatcher {
//...
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
}
這里可以看到削彬,ActivityRefWatcher類中的install方法中創(chuàng)建了一個ActivityRefWatcher全庸,并且注冊了它的Lifecycle,在onActivityDestroyed時調(diào)用了refWatcher.watch(activity);來進(jìn)行觀察
Fragment 的觀察基本一致:
public interface FragmentRefWatcher {
final class Helper {
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor<?> constructor =
fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
FragmentRefWatcher supportFragmentRefWatcher =
(FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
}
if (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
//...
}
}
這里面的邏輯很簡單融痛,首先在將Android標(biāo)準(zhǔn)的Fragment的RefWatcher類壶笼,即AndroidOfFragmentRefWatcher添加到新創(chuàng)建的fragmentRefWatchers中。在使用反射將leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加進(jìn)來雁刷,如果你在app的build.gradle下沒有添加下面這行引用的話覆劈,則會拿不到此類,即LeakCanary只會檢測Activity和標(biāo)準(zhǔn)Fragment這兩種情況沛励。
watcher.watchFragments(activity);最終調(diào)用到了:
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {
refWatcher.watch(fragment);
}
可以看到责语,不管是activity還是fragment,最后都調(diào)用到了watch中目派,我們來看下watch(activity)方法中做了什么:
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);
}
- 為每個reference創(chuàng)建一個唯一的UUID坤候,把這個UUID放入到set集合retainedKeys中。
- 為watchedReference對象創(chuàng)建一個弱引用KeyedWeakReference
- 調(diào)用ensureGoneAsync(watchStartNanoTime, reference);
我們先看看KeyedWeakReference中做了什么:
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
//1址貌、
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
在super中铐拐,將referent注冊到queue中,這里說一下练对,在我們創(chuàng)建軟引用遍蟋、弱引用時,可以聲明一個ReferenceQueue:
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
在KeyedWeakReference內(nèi)部螟凭,使用了key和name標(biāo)識了一個被檢測的WeakReference對象虚青。在注釋1處,將弱引用和引用隊(duì)列 ReferenceQueue 關(guān)聯(lián)起來螺男,如果弱引用reference持有的對象被GC回收棒厘,JVM就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列referenceQueue中。即 KeyedWeakReference 持有的 Activity 對象如果被GC回收下隧,該對象就會加入到引用隊(duì)列 referenceQueue 中奢人。
如果我們在創(chuàng)建一個引用對象時,指定了ReferenceQueue淆院,那么當(dāng)引用對象指向的對象達(dá)到合適的狀態(tài)(根據(jù)引用類型不同而不同)時何乎,GC 會把引用對象本身添加到這個隊(duì)列中,方便我們處理它,因?yàn)椤耙脤ο笾赶虻膶ο?GC 會自動清理支救,但是引用對象本身也是對象(是對象就占用一定資源)抢野,所以需要我們自己清理「髂”
參考:Reference 指孤、ReferenceQueue 詳解
再來看看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);
}
});
}
通過異步調(diào)用ensureGone(reference, watchStartNanoTime);,watchExecutor我們往回翻找可以發(fā)現(xiàn)其實(shí)是AndroidWatchExecutor贬堵,來看下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;
//1恃轩、
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) {
//2、
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() {
//3黎做、
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() {
//4详恼、
Retryable.Result result = retryable.run();
if (result == RETRY) {
//5、
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
- 1引几、initialDelayMillis默認(rèn)為5s
- 2昧互、如果當(dāng)前是主線程,調(diào)用waitForIdle(retryable, 0);伟桅,如果不是敞掘,通過handler機(jī)制切換到主線程調(diào)用waitForIdle(retryable, failedAttempts);
- 3、queueIdle方法是在消息隊(duì)列用完消息時調(diào)用楣铁,必須在主線程調(diào)用
- 4玖雁、在子線程中調(diào)用retryable.run(),run()方法調(diào)用到了RefWatcher類ensureGoneAsync方法中的run方法
- 5盖腕、如果是RETRY赫冬,那么遞歸調(diào)用postWaitForIdle,但是時間會加1s
接下來我們在看下ensureGone(reference, watchStartNanoTime)方法:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//1溃列、
removeWeaklyReachableReferences();
//2劲厌、
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//3、
if (gone(reference)) {
return DONE;
}
//4听隐、
gcTrigger.runGc();
//5补鼻、
removeWeaklyReachableReferences();
//6、
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//7雅任、
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();
//8风范、
heapdumpListener.analyze(heapDump);
}
return DONE;
}
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);
}
}
- 1、調(diào)用removeWeaklyReachableReferences沪么,queue.poll()是從ReferenceQueue中取出queue的第一個Reference硼婿,如果它是可達(dá)的,那么將它從queue中移除并且返回禽车,如果有不可達(dá)的Reference(該被回收的)寇漫,直接返回null拳喻。通過while循環(huán)queue.poll(),可以將queue中的所有可達(dá)對象找出來并且從queue中移除猪腕,再調(diào)用將其從retainedKeys中remove掉。如果有需要被回收的對象钦勘,則直接停止循環(huán)
- 2陋葡、如果當(dāng)前是debug模式下,那么不執(zhí)行回收策咯彻采,因?yàn)樵赿ebug模式下可能存在假泄露
- 3腐缤、如果retainedKeys為空,那么表示所有對象均被回收肛响,不存在泄露
- 4岭粤、執(zhí)行g(shù)c
- 5、再次調(diào)用removeWeaklyReachableReferences
- 6特笋、如果retainedKeys還不為空剃浇,那么說明存在不能被回收的弱引用對象,表示已經(jīng)發(fā)生了泄露
- 7猎物、生成一個heapDumpFile虎囚,并且創(chuàng)建一個Notification來通知用戶
- 8、回調(diào)到ServiceHeapDumpListener的analyze方法中來分析泄露
來看下ServiceHeapDumpListener類:
public final class ServiceHeapDumpListener implements HeapDump.Listener {
...
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
可以看到蔫磨,這里執(zhí)行了HeapAnalyzerService的runAnalysis()方法淘讥,為了避免降低app進(jìn)程的性能或占用內(nèi)存,這里將HeapAnalyzerService設(shè)置在了一個獨(dú)立的進(jìn)程中堤如。接著繼續(xù)分析runAnalysis()方法里面的處理蒲列。
public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {
...
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
...
ContextCompat.startForegroundService(context, intent);
}
...
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
...
// 1
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// 2
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 3
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
...
}
這里的HeapAnalyzerService實(shí)質(zhì)是一個類型為IntentService的ForegroundService,執(zhí)行startForegroundService()之后搀罢,會回調(diào)onHandleIntentInForeground()方法蝗岖。
- 1、首先會新建一個HeapAnalyzer對象榔至,顧名思義剪侮,它就是根據(jù)RefWatcher生成的heap dumps信息來分析被懷疑的泄漏是否是真的。
- 2洛退、調(diào)用它的checkForLeak()方法去使用haha庫解析 hprof文件
- 3瓣俯、通過sendResultToListener調(diào)用到onHandleIntentInForeground中的onHeapAnalyzed,最后調(diào)用到DisplayLeakService中的DisplayLeakActivity.createPendingIntent來開啟一個泄露的Activity
到此兵怯,我們就分析完了整個過程彩匕,整理一下
- 1、Leakcanary.install中創(chuàng)建了一個RefWatcher
- 2媒区、通過RefWatcher中的watch()來觀察Activity和fragment驼仪,觀察的方式是為被觀察的activity或fragment創(chuàng)建一個WeakReference
- 3掸犬、將WeakReference放入到一個set集合retainedKeys中
- 4、調(diào)用檢測方法绪爸,在異步線程中湾碎,先將retainedKeys中可達(dá)的全部移除
- 5、一旦發(fā)現(xiàn)不可達(dá)的WeakReference奠货,執(zhí)行g(shù)c
- 6介褥、如果引用仍然沒有被清除(retainedKeys不為空),那么它將會把堆棧信息保存在文件系統(tǒng)中的.hprof文件里递惋。
- 7柔滔、HeapAnalyzerService被開啟在一個獨(dú)立的進(jìn)程中,并且HeapAnalyzer使用了HAHA開源庫解析了指定時刻的堆椘妓洌快照文件heap dump睛廊。
- 8、從heap dump中杉编,HeapAnalyzer根據(jù)一個獨(dú)特的引用key找到了KeyedWeakReference超全,并且定位了泄露的引用。
- 9邓馒、HeapAnalyzer為了確定是否有泄露卵迂,計(jì)算了到GC Roots的最短強(qiáng)引用路徑,然后建立了導(dǎo)致泄露的鏈?zhǔn)揭谩?/li>
- 10绒净、這個結(jié)果被傳回到app進(jìn)程中的DisplayLeakService见咒,然后一個泄露通知便展現(xiàn)出來了。