開源庫路徑https://github.com/square/leakcanary
原始碼結構
- leakcanary-watcher: 這是一個通用的記憶體檢測器,對外提供一個 RefWatcher#watch(Object watchedReference),它不僅能夠檢測Activity,還能監測任意常規的 Java Object 的洩漏情況。
- leakcanary-android: 這個 module 是與 Android 的接入點,用來專門監測 Activity 的洩漏情況,內部使用了 application#registerActivityLifecycleCallbacks 方法來監聽 onDestory 事件,然後利用 leakcanary-watcher 來進行弱引用+手動 GC 機制進行監控。
- leakcanary-analyzer: 這個 module 提供了 HeapAnalyzer,用來對 dump 出來的記憶體進行分析並返回記憶體分析結果AnalysisResult,內部包含了洩漏發生的路徑等資訊供開發者尋找定位。
- leakcanary-android-no-op: 這個 module 是專門給 release 的版本用的,內部只提供了兩個完全空白的類 LeakCanary 和 RefWatcher,這兩個類不會做任何記憶體洩漏相關的分析。因為 LeakCanary 本身會由於不斷 gc 影響到 app 本身的執行,而且主要用於開發階段的記憶體洩漏檢測。因此對於 release 則可以 disable 所有洩漏分析。
原理簡介
LeakCanary的原理非常簡單。正常情況下一個Activity在執行Destroy之後就要銷燬,LeakCanary做的就是在一個Activity/Fragment Destroy之後將它放在一個WeakReference中,然後將這個WeakReference關聯到一個ReferenceQueue,檢視ReferenceQueue是否存在Activity的引用,如果不在這個佇列中,執行一些GC清洗操作,再次檢視。如果不存在則證明該Activity/Fragment洩漏了,之後Dump出heap資訊,並用haha這個開源庫去分析洩漏路徑。
基本原理圖示
原始碼分析
簡單分析,只分析如何實現記憶體洩漏檢測的基本思路
第一步:入口函式分析LeakCanary.install(Application application)
整合使用LeakCanary基本上是在Application onCreate中呼叫即可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) {
//返回的是AndroidRefWatcherBuilder繼承自RefWatcher物件
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
分析一:返回AndroidRefWatcherBuilder物件
refWatcher(application)
分析二:呼叫返回AndroidRefWatcherBuilder.buildAndInstall
buildAndInstall
複製程式碼
分析一: refWatcher(application)
該方法呼叫的refWatcher返回了AndroidRefWatcherBuilder物件:
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
複製程式碼
分析二:AndroidRefWatcherBuilder.buildAndInstall
進行設定監控所需要的相關係統lifeCycle回撥,包含ActivityLifecycleCallbacks以及FragmentLifecycleCallbacks。
/**
* 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() {
//buildAndInstall只允許呼叫一次
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//建立用於實際處理判斷記憶體洩漏的監控物件RefWatcher,放在內容第二步分析
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
if (watchActivities) {
//watchActivities預設為true,開始activity引用的監控
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
//watchFragments預設為true,開始fragment引用的監控
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
//賦值installedRefWatcher,用於判斷已建立成功
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
分析三:設定activity資源洩漏監控:
ActivityRefWatcher.install
分析四:設定Fragment資源洩漏監控:
FragmentRefWatcher.Helper.install
複製程式碼
分析三:ActivityRefWatcher.install
這邊主要是對app註冊了個ActivityLifecycleCallbacks,在每次activity被銷燬後都會回撥到onActivityDestroyed,在onActivityDestroyed中獲取在理論上即將被銷燬的activity物件,呼叫refWatcher.watch檢測其是否發生洩漏
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//註冊個lifecycleCallbacks,在裡面分析activity的記憶體洩漏問題
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
//app所有activity生命週期結束自動回撥
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
//呼叫的還是refWatcher操作,ActivityRefWatcher只是為了activity週期監聽
refWatcher.watch(activity);
}
};
下文第二步分析activity是否存在記憶體洩漏:
refWatcher.watch(activity)
複製程式碼
分析四:FragmentRefWatcher.Helper.install
這邊主要用於ActivityLifecycleCallbacks中各個activity建立的時候, 獲取到activity對應的FragmentManager註冊FragmentLifecycleCallbacks.後續當有Fragment消耗觸發onFragmentViewDestroyed或者onFragmentDestroyed時,則獲取理論上即將被銷燬的view/fragment物件,呼叫refWatcher.watch檢測其是否發生洩漏。
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
//新增了個AndroidOFragmentRefWatcher用於對android.app.FragmentManager設定FragmentLifecycleCallbacks
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
//反射新增SupportFragmentRefWatcher用於對android.support.v4.app.FragmentManager設定FragmentLifecycleCallbacks
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) {
ignored.printStackTrace();
}
if (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatchers);
//這邊再次註冊了另外一個ActivityLifecycleCallbacks
Application application = (Application) context.getApplicationContext();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
//該ActivityLifecycleCallbacks主要在onActivityCreated回撥的時候執行上面新增的FragmentRefWatcher.watchFragments方法
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
分析五:fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher)):
用於對android.app.FragmentManager設定FragmentLifecycleCallbacks
分析六:fragmentRefWatchers.add(supportFragmentRefWatcher):
用於對android.support.v4.app.FragmentManager設定FragmentLifecycleCallbacks
複製程式碼
分析五:fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher)):
新增了個AndroidOFragmentRefWatcher用於對android.app.FragmentManager設定FragmentLifecycleCallbacks,後續在fragment生命週期結束時獲取並判斷是否存在fragment記憶體洩漏。
AndroidOFragmentRefWatcher.watchFragments:
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
//檢測即將被回收的view是否存在洩漏
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
//檢測即將被回收的fragment是否存在洩漏
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
//對activity註冊FragmentLifecycleCallbacks生命週期監聽
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
複製程式碼
分析六:fragmentRefWatchers.add(supportFragmentRefWatcher):
新增了個SupportFragmentRefWatcher用於對android.support.v4.app.FragmentManager設定FragmentLifecycleCallbacks,後續在fragment生命週期結束時獲取並判斷是否存在fragment記憶體洩漏。
SupportFragmentRefWatcher.watchFragments:
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
//檢測即將被回收的view是否存在洩漏
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
//檢測即將被回收的fragment是否存在洩漏
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
//對activity註冊FragmentLifecycleCallbacks生命週期監聽
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
複製程式碼
第二步:記憶體是否洩漏判斷refWatcher.watch()
首先從RefWatcher物件的建立開始
/** Creates a {@link 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();
}
//預設為null
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
//設定預設的監控執行處理器defaultWatchExecutor,呼叫AndroidRefWatcherBuilder.defaultWatchExecutor()獲取
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
//獲取Gc處理器RefWatcherBuilder.defaultGcTrigger()
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
//返回記憶體洩漏監控處理者RefWatcher
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
分析七:defaultWatchExecutor();
複製程式碼
分析七:defaultWatchExecutor
AndroidWatchExecutor物件的建立:
//預設延時引數5秒
private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);
@Override protected @NonNull WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
複製程式碼
AndroidWatchExecutor實現的功能
AndroidWatchExecutor主要是做了一個簡單的延時功能,因為activity、fragment等處罰ondestroy時,這些物件理論上即將被回收,但是還未被回收,所以AndroidWatchExecutor預設將檢測任務傳送到非同步執行緒中做了個5秒的延時,注意這邊是在非同步執行緒,不阻塞主執行緒。在延時時間到了後,將檢測任務再傳送回主執行緒進行檢測,注意這邊之所以再傳送回主執行緒,是因為gc操作只能在主執行緒觸發。
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
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
//建立執行於後臺執行緒的backgroundHandler
backgroundHandler = new Handler(handlerThread.getLooper());
//預設為5s
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
//當前是主執行緒則執行waitForIdle
waitForIdle(retryable, 0);
} else {
//當前是後臺執行緒則執行postWaitForIdle
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
//將檢測任務Retryable post到主執行緒中去執行
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.
//當主執行緒空閒時則執行postToBackgroundWithDelay
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;
//延時5秒執行Retryable檢測
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
複製程式碼
判斷是否存在記憶體洩漏呼叫RefWatcher.watch:
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
//開始檢測的時間
final long watchStartNanoTime = System.nanoTime();
//產生隨機的key , 作為需要檢測的物件的唯一標識
String key = UUID.randomUUID().toString();
//儲存該key
retainedKeys.add(key);
//建立對應的對需要監控的watchedReference物件的弱引用並與ReferenceQueue繫結
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//開始確認該物件是否被回收了
ensureGoneAsync(watchStartNanoTime, reference);
}
複製程式碼
做了個簡單的執行緒判斷ensureGoneAsync
這邊看到使用到了上面watchExecutor延時5秒後,再執行ensureGone
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
複製程式碼
確認是否回收ensureGone
該函式執行的一個基本操作就是: 1.首先判斷ReferenceQueue是否存在要檢測記憶體洩漏的reference物件,不存在則代表可能發生洩漏
2.主動觸發一次gc,進行記憶體回收
3.再次判斷ReferenceQueue是否存在要檢測記憶體洩漏的reference物件,不存在則代表可能發生洩漏
4.若發生洩漏則dump出記憶體hprof檔案,進行分析,從中分析出記憶體洩漏的路徑
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//gc準備開啟的時間
long gcStartNanoTime = System.nanoTime();
//開始監控到準備gc的時間,大概5秒多,因為前邊延時5秒
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//移除已經被回收記憶體的監控物件的Key
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//判斷該reference物件是否被回收了,如果已經被回收,返回DONE,
if (gone(reference)) {
return DONE;
}
//如果尚未被回收,則主動觸發一次gc
gcTrigger.runGc();
//移除已經被回收記憶體的監控物件的Key
removeWeaklyReachableReferences();
//判斷該reference物件是否被回收了,如果已經被回收,返回DONE,
if (!gone(reference)) {
//該reference物件尚未被回收
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//主動dump出記憶體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();
//將hprof進行分析出洩漏的點並通過ui通知使用者
heapdumpListener.analyze(heapDump);
}
return DONE;
}
複製程式碼
參考
allenwu.itscoder.com/leakcanary-…