源碼分析基于:1.6.3
對于Android開發者而言,記憶體洩漏是一種很常見的問題。LeakCanary就是捕獲記憶體洩漏的一把利器。我們在這裡就分析一下它的工作原理。
一、使用方法
使用方法就是我們在Application中添加代碼:
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not onStart your app in this process.
return;
}
LeakCanary.install(this);
看到這些估計你也會和我一樣,這樣我們就能捕獲到記憶體洩露了麼?不禁會産生這樣的疑問。我們就帶着疑問閱讀一下源碼尋找答案吧。
二、記憶體洩漏的檢測過程:
LeakCanary這個類是一個工具類,我們先看一下LeakCanary.install(this)這個方法的源碼:
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
該方法中refWatcher(application)方法擷取AndroidRefWatcherBuilder執行個體對象;“.listenerServiceClass(DisplayLeakService.class)”方法是對記憶體洩漏分析結果的監聽;“.buildAndInstall()”方法用于生成RefWatcher對象,關于該方法的詳細代碼如下:
/**
* 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() {
//保證RefWatcher的唯一性
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//如果沒有建立過RefWatcher,則建立一個;
RefWatcher refWatcher = build();
//非空置對象(記憶體分析相關工具對象都是空殼對象)
if (refWatcher != DISABLED) {
//是否允許使用内置展示頁面展示記憶體洩露資訊
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//是否允許監測activity
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
//是否允許檢測fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
//RefWatcher對象指派
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
在這裡RefWatcher就是記憶體洩漏的監控器。該方法就是生成RefWatcher對象,該架構預設允許檢測Activity和Fragment的記憶體洩漏,那麼它又是怎麼監控的呢?原理由是啥?以什麼為判斷記憶體洩漏的标準呢?
我們看一下ActivityRefWatcher中install()方法的邏輯:
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//Application注冊生命周期回調
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
lifecycleCallbacks是該類中的成員變量,通過注冊生命周期回調RefWatcher就可以通過頁面銷毀的方法回調觸發RefWatcher的watch()方法。我們先不管watch()方法中的具體邏輯,下面在分析一下fragment中的監測機制,下面我們分析一下FragmentRefWatcher.Helper這個内部類的源碼:
final class Helper {
private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
"com.squareup.leakcanary.internal.SupportFragmentRefWatcher";
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
//大于26的Android版本使用AndroidOFragmentRefWatcher
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
//否則使用SupportFragmentRefWatcher
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) {
//對應的FragmentManager注冊回調
watcher.watchFragments(activity);
}
}
};
private final List<FragmentRefWatcher> fragmentRefWatchers;
private Helper(List<FragmentRefWatcher> fragmentRefWatchers) {
this.fragmentRefWatchers = fragmentRefWatchers;
}
}
該類中主要就是通過Activity的生命周期回調的onActivityCreated()方法,來擷取activity執行個體對象,具體需要分析watcher.watchFragments(activity)中的邏輯,由于存在兩種fragment對應的watcher,我們在這裡隻分析SupportFragmentRefWatcher中對應的代碼邏輯。
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
方法中擷取FragmentManager執行個體對象,并注冊生命周期方法。對于Android版本大于26的,請閱讀AndroidOFragmentRefWatcher中的相關源碼,原理是一緻的。通過上面的源碼我們分析得知,對于Activity而言,是在activity的onDestory()方法内進行監測的;對于fragment而言,在生命周期回調方法内的onFragmentViewDestroyed()和onFragmentDestroyed()方法内進行監測,在這裡onFragmentViewDestroyed()方法對應于Fragment的onDestroyView()方法,onFragmentDestroyed()方法對應于Fragment#onDestroy()方法。
我們知道了LeakCanary的監測位置了,我們下面就通過源碼分析一下它是怎麼做到監測記憶體洩漏的。下面是RefWatcher類中的watch()方法的源碼:
/**
* 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();
//生成随機的UUID作為該引用對象的key值
String key = UUID.randomUUID().toString();
//添加的集合中
retainedKeys.add(key);
//儲存引用資訊
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//判斷引用是否存在
ensureGoneAsync(watchStartNanoTime, reference);
}
在該方法中,将之前生命周期中的方法中拿到的activity對象執行個體或者fragment對象執行個體進行儲存并進行相關的處理,在儲存時會生成一個唯一的key值,後面會通過key值進行相應的業務邏輯。下面我們看一下ensureGoneAsync()方法相關的邏輯,源碼如下:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
該方法中watchExecutor是AndroidWatchExecutor的一個具體執行個體對象,最終是通過該實作類的execute()方法去執行引用相關的邏輯,這部分後面會分析。同時需要注意在excute()方法的内部類中run()方法會通過ensureGone()方法傳回Retryable.Result的值。這個值會在後面的分析中用到。我們在這裡先看一下AndroidWatchExecutor的具體實作。AndroidWatchExecutor是WatchExecutor接口的具體實作類,該接口主要用來執行Rertyable對象,并根據需要進行重試操作。我們看一下AndroidWatchExecutor的源碼:
/**
* {@link WatchExecutor} suitable for watching Android reference leaks. This executor waits for the
* main thread to be idle then posts to a serial background thread with the delay specified by
* {@link AndroidRefWatcherBuilder#watchDelay(long, TimeUnit)}.
*/
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);
}
}
AndroidWatchExecutor 是Android平台中記憶體洩漏的監測工具類。該類的主要方法就是execute()方法,其主要邏輯是判斷目前正在執行的線程是否為主線程,如果是主線程,則通過IdleHandler監測主線程是否空閑,當空閑的時候,執行postToBackgroundWithDelay方法,在此方法中,先通過計算exponentialBackoffFactor(補償因子),并通過該因子計算出需要延遲的時間(delayMillis ),說實話為什麼這麼算沒想明白。最後通過backgroundHandler延遲執行一條線程去擷取Retryable.Result的結果,通過是否需要重試也就是“result == RETRY”來判斷是否需要繼續postWaitForIdle方法,同時在重試的時候failedAttempts參數會加1。那麼我們不禁會思考,retryable.run()的結果,也就是Retryable.Result是在什麼時候改變的呢?這個我們在RefWatcher中的ensureGoneAsync()方法的分析中提到,result的值是在ensureGone()方法傳回的。好了就是那麼清晰。當正在執行的線程非主線程的的時候postWaitForIdle()方法被執行,這裡就不再說明了。
下面我們看一下RefWatcher中ensureGone()方法的源碼:
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//GC開始檢測時間
long gcStartNanoTime = System.nanoTime();
//從開始watch到GC開始檢測的時間差
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//移除已經被GC的引用資訊
removeWeaklyReachableReferences();
//如果調試模式,則後續重試
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//如果不包含應用資訊,則表示監測已經完成,沒有發生洩漏;
if (gone(reference)) {
return DONE;
}
//觸發GC
gcTrigger.runGc();
//再次确認移除已經被GC的引用資訊
removeWeaklyReachableReferences();
//如果隊列中還存在引用資訊,
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//使用Dumper類dump目前堆記憶體中的對象資訊
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//将hprof檔案和reference引用資訊構造HeapDump對象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//分析dump資訊
heapdumpListener.analyze(heapDump);
}
return DONE;
}
要了解LeackCanary是怎麼定義記憶體洩漏的呢?此方法就是關鍵。同時不了解Java中的弱引用,強引用的概念的可以查閱一下這方面的資料,這裡就不在進行過多的解釋了。首先該方法先調用了removeWeaklyReachableReferences()方法,該方法就是移除已經被GC的引用資訊,在該方法裡就是看queue隊列中是否還存在引用的資訊KeyedWeakReference,如果存在就從retainedKeys中進行移除。在RefWatcher類中的watch()方法中已經将觀察對象watchReference對應的key儲存在retainedKeys中,同時關聯到了ReferenceQueue的執行個體對象queue。當弱引用持有的對象被GC之後,與之關聯的引用資訊KeyedWeakReference的對象reference就會被添加到關聯的隊列中queue.是以可以通過queue中是否存在對應的引用資訊來判斷是否發生記憶體洩漏。下面說一下為什麼在開啟Debug模式下需要傳回RETRY,那是因為在該模式下對象引用的時間會變長。我們繼續向下分析,gone(refreence)方法就是看retainedKeys是否包含相應的引用的資訊,如果不存在則說明就完成了GC,本次檢測也告一段落。否則, 通過gcTrigger.runGc()手動觸發一次GC,并在此通過removeWeaklyReachableReferences()方法進行引用資訊的移除。如果還存在該引用資訊那麼說明app目前發生了記憶體洩漏。後續就是通過heapDumper.dumpHeap()将目前堆記憶體中的資訊儲存下來,并通過heapdumpListener.analyze(heapDump)分析擷取的dump資訊。到此,相信大家就清楚了我們Android開發中記憶體洩漏檢測神器LeakCanary的檢測原理了吧。
後續将在下一篇文章中分析該架構是怎麼分析記憶體洩漏的,敬請期待~