看完這篇 LeakCanary 原理分析,又可以虐面試官了!
前言
成為一名優秀的Android開發,需要一份完備的知識體系,在這裡,讓我們一起成長為自己所想的那樣~。
在Android主流三方庫原始碼分析系列的前幾篇文章中,筆者已經對網路、圖片、資料庫、響應式程式設計中最熱門的第三方開源框架進行了較為深入地講解,如果有朋友對這四塊感興趣的話,可以去了解下。本篇,我將會對Android中的記憶體洩露檢測框架Leakcanary的原始碼流程進行詳細地講解。
一、原理概述
首先,筆者仔細查看了Leakcanary官方的github倉庫,最重要的便是對Leakcanary是如何起作用的 (即原理)這一問題進行了闡述,我自己把它翻譯成了易於理解的文字,主要分為如下7個步驟:
-
1、RefWatcher.watch()建立了一個KeyedWeakReference用於去觀察物件。
-
2、然後,在後臺執行緒中,它會檢測引用是否被清除了,並且是否沒有觸發GC。
-
3、如果引用仍然沒有被清除,那麼它將會把堆疊資訊儲存在檔案系統中的.hprof檔案裡。
-
4、HeapAnalyzerService被開啟在一個獨立的程序中,並且HeapAnalyzer使用了HAHA開源庫解析了指定時刻的堆疊快照檔案heap dump。
-
5、從heap dump中,HeapAnalyzer根據一個獨特的引用key找到了KeyedWeakReference,並且定位了洩露的引用。
-
6、HeapAnalyzer為了確定是否有洩露,計算了到GC Roots的最短強引用路徑,然後建立了導致洩露的鏈式引用。
-
7、這個結果被傳回到app程序中的DisplayLeakService,然後一個洩露通知便展現出來了。
官方的原理簡單來解釋就是這樣的:在一個Activity執行完onDestroy()之後,將它放入WeakReference中,然後將這個WeakReference型別的Activity物件與ReferenceQueque關聯。這時再從ReferenceQueque中檢視是否有沒有該物件,如果沒有,執行gc,再次檢視,還是沒有的話則判斷髮生記憶體洩露了。最後用HAHA這個開源庫去分析dump之後的heap記憶體。
二、簡單示例
下面這段是Leakcanary官方倉庫的示例程式碼:
首先在你專案app下的build.gradle中配置:
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' }
然後在你的Application中配置:
public class WanAndroidApp extends Application { private RefWatcher refWatcher; public static RefWatcher getRefWatcher(Context context) { WanAndroidApp application = (WanAndroidApp)context.getApplicationContext(); return application.refWatcher; } @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // 1 return; } // 2 refWatcher = LeakCanary.install(this); } }
在註釋1處,會首先判斷當前程序是否是Leakcanary專門用於分析heap記憶體的而建立的那個程序,即HeapAnalyzerService所在的程序,如果是的話,則不進行Application中的初始化功能。如果是當前應用所處的主程序的話,則會執行註釋2處的LeakCanary.install(this)進行LeakCanary的安裝。只需這樣簡單的幾行程式碼,我們就可以在應用中檢測是否產生了記憶體洩露了。當然,這樣使用只會檢測Activity是否發生記憶體洩漏,如果要檢測Fragment在執行完onDestroy()之後是否發生記憶體洩露的話,則需要在Fragment的onDestroy()方法中加上如下兩行程式碼去監視當前的Fragment:
RefWatcher refWatcher = WanAndroidApp.getRefWatcher(_mActivity); refWatcher.watch(this);
上面的RefWatcher其實就是一個引用觀察者物件,是用於監測當前例項物件的引用狀態的。從以上的分析可以瞭解到,核心程式碼就是LeakCanary.install(this)這行程式碼,接下來,就從這裡出發將LeakCanary一步一步進行拆解。
三、原始碼分析
1、LeakCanary#install()
public static @NonNull RefWatcher install(@NonNull Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
在install()方法中的處理,可以分解為如下四步:
-
1、refWatcher(application)
-
2、鏈式呼叫listenerServiceClass(DisplayLeakService.class)
-
3、鏈式呼叫excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
-
4、鏈式呼叫uildAndInstall()
首先,我們來看下第一步,這裡呼叫了LeakCanary類的refWatcher方法,如下所示:
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) { return new AndroidRefWatcherBuilder(context); }
然後新建了一個AndroidRefWatcherBuilder物件,再看看AndroidRefWatcherBuilder這個類。
2、AndroidRefWatcherBuilder
/** A {@link RefWatcherBuilder} with appropriate Android defaults. */ public final class AndroidRefWatcherBuilder extendsRefWatcherBuilder<AndroidRefWatcherBuilder> { ... AndroidRefWatcherBuilder(@NonNull Context context) { this.context = context.getApplicationContext(); } ... }
在AndroidRefWatcherBuilder的構造方法中僅僅是將外部傳入的applicationContext物件儲存起來了。AndroidRefWatcherBuilder是一個適配Android平臺的引用觀察者構造器物件,它繼承了RefWatcherBuilder,RefWatcherBuilder是一個負責建立引用觀察者RefWatcher例項的基類構造器。繼續看看RefWatcherBuilder這個類。
3、RefWatcherBuilder
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> { ... public RefWatcherBuilder() { heapDumpBuilder = new HeapDump.Builder(); } ... }
在RefWatcher的基類構造器RefWatcherBuilder的構造方法中新建了一個HeapDump的構造器物件。其中HeapDump就是一個儲存heap dump資訊的資料結構。
接著來分析下install()方法中的鏈式呼叫的listenerServiceClass(DisplayLeakService.class)這部分邏輯。
4、AndroidRefWatcherBuilder#listenerServiceClass()
public @NonNull AndroidRefWatcherBuilder listenerServiceClass( @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) { return heapDumpListener(new ServiceHeapDumpListener(context,listenerServiceClass)); }
在這裡,傳入了一個DisplayLeakService的Class物件,它的作用是展示洩露分析的結果日誌,然後會展示一個用於跳轉到顯示洩露介面DisplayLeakActivity的通知。在listenerServiceClass()這個方法中新建了一個ServiceHeapDumpListener物件,看看它內部的操作。
5、ServiceHeapDumpListener
public final class ServiceHeapDumpListener implements HeapDump.Listener { ... public ServiceHeapDumpListener(@NonNull final Context context, @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) { this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass"); this.context = checkNotNull(context, "context").getApplicationContext(); } ... }
可以看到這裡僅僅是在ServiceHeapDumpListener中儲存了DisplayLeakService的Class物件和application物件。它的作用就是接收一個heap dump去分析。
然後我們繼續看install()方法鏈式呼叫.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())的這部分程式碼。先看AndroidExcludedRefs.createAppDefaults()。
6、AndroidExcludedRefs#createAppDefaults()
public enum AndroidExcludedRefs { ... public static @NonNull ExcludedRefs.Builder createAppDefaults() { return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class)); } public static @NonNull 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; } ... }
先來說下AndroidExcludedRefs這個類,它是一個enum類,它聲明瞭Android SDK和廠商定製的SDK中存在的記憶體洩露的case,根據AndroidExcludedRefs這個類的類名就可看出這些case都會被Leakcanary的監測過濾掉。目前這個版本是有46種這樣的case被包含在內,後續可能會一直增加。然後EnumSet.allOf(AndroidExcludedRefs.class)這個方法將會返回一個包含AndroidExcludedRefs元素型別的EnumSet。Enum是一個抽象類,在這裡具體的實現類是通用正規型的RegularEnumSet,如果Enum裡面的元素個數大於64,則會使用儲存大資料量的JumboEnumSet。最後,在createBuilder這個方法裡面構建了一個排除引用的建造器excluded,將各式各樣的case分門別類地儲存起來再返回出去。
最後,我們看到鏈式呼叫的最後一步buildAndInstall()。
7、AndroidRefWatcherBuilder#buildAndInstall()
private boolean watchActivities = true; private boolean watchFragments = true; public @NonNull RefWatcher buildAndInstall() { // 1 if (LeakCanaryInternals.installedRefWatcher != null) { throw new UnsupportedOperationException("buildAndInstall() should only be called once."); } // 2 RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { // 3 LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true); if (watchActivities) { // 4 ActivityRefWatcher.install(context, refWatcher); } if (watchFragments) { // 5 FragmentRefWatcher.Helper.install(context, refWatcher); } } // 6 LeakCanaryInternals.installedRefWatcher = refWatcher; return refWatcher; }
首先,在註釋1處,會判斷LeakCanaryInternals.installedRefWatcher是否已經被賦值,如果被賦值了,則會丟擲異常,警告buildAndInstall()這個方法應該僅僅只呼叫一次,在此方法結束時,即在註釋6處,該LeakCanaryInternals.installedRefWatcher才會被賦值。再來看註釋2處,呼叫了AndroidRefWatcherBuilder其基類RefWatcherBuilder的build()方法,我們它是如何建造的。
8、RefWatcherBuilder#build()
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); }
可以看到,RefWatcherBuilder包含了7個組成部分:
-
1、excludedRefs : 記錄可以被忽略的洩漏路徑。
-
2、heapDumpListener : 轉儲堆資訊到hprof檔案,並在解析完 hprof 檔案後進行回撥,最後通知 DisplayLeakService 彈出洩漏提醒。
-
3、debuggerControl : 判斷是否處於除錯模式,除錯模式中不會進行記憶體洩漏檢測。為什麼呢?因為在除錯過程中可能會保留上一個引用從而導致錯誤資訊上報 。
-
4、heapDumper : 堆資訊轉儲者,dump 記憶體洩漏處的 heap 資訊到 hprof 檔案
-
5、watchExecutor : 執行緒控制器,在 onDestroy() 之後並且主執行緒空閒時執行記憶體洩漏檢測
-
6、gcTrigger : 用於 GC,watchExecutor 首次檢測到可能的記憶體洩漏,會主動進行 GC,GC 之後會再檢測一次,仍然洩漏的判定為記憶體洩漏,最後根據heapDump資訊生成相應的洩漏引用鏈。
-
7、reachabilityInspectorClasses : 用於要進行可達性檢測的類列表。
最後,會使用建造者模式將這些組成部分構建成一個新的RefWatcher並將其返回。
我們繼續看回到AndroidRefWatcherBuilder的註釋3處的 LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true)這行程式碼。
9、LeakCanaryInternals#setEnabledAsync()
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); } }); }
在這裡使用了AsyncTask內部自帶的THREAD_POOL_EXECUTOR執行緒池進行阻塞式地顯示DisplayLeakActivity。
然後我們再繼續看AndroidRefWatcherBuilder的註釋4處的程式碼。
10、ActivityRefWatcher#install()
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) { Application application = (Application) context.getApplicationContext(); // 1 ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); // 2 application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks); }
可以看到,在註釋1處建立一個自己的activityRefWatcher例項,並在註釋2處呼叫了application的registerActivityLifecycleCallbacks()方法,這樣就能夠監聽activity對應的生命週期事件了。繼續看看activityRefWatcher.lifecycleCallbacks裡面的操作。
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =new ActivityLifecycleCallbacksAdapter() {@Override public void onActivityDestroyed(Activity activity) {refWatcher.watch(activity);}};public abstract class ActivityLifecycleCallbacksAdapterimplements Application.ActivityLifecycleCallbacks {}
很明顯,實現並重寫了Application的ActivityLifecycleCallbacks的onActivityDestroyed()方法,這樣便能在所有Activity執行完onDestroyed()方法之後呼叫 refWatcher.watch(activity)這行程式碼進行記憶體洩漏的檢測了。
我們再看會註釋5處的FragmentRefWatcher.Helper.install(context, refWatcher)這行程式碼,
11、FragmentRefWatcher.Helper#install()
public interface FragmentRefWatcher { void watchFragments(Activity activity); 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<>(); // 1 if (SDK_INT >= O) { fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher)); } // 2 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); // 3 Application application = (Application) context.getApplicationContext(); application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks); } ... }
這裡面的邏輯很簡單,首先在註釋1處將Android標準的Fragment的RefWatcher類,即AndroidOFragmentRefWatcher新增到新建立的fragmentRefWatchers中。在註釋2處使用反射將leakcanary-support-fragment包下面的SupportFragmentRefWatcher新增進來,如果你在app的build.gradle下沒有新增下面這行引用的話,則會拿不到此類,即LeakCanary只會兼顧監測Activity和標準Fragment這兩種情況。
debugImplementation'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
繼續看到註釋3處helper.activityLifecycleCallbacks裡面的程式碼。
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { for (FragmentRefWatcher watcher : fragmentRefWatchers) { watcher.watchFragments(activity); } } };
可以看到,在Activity執行完onActivityCreated()方法之後,會呼叫指定watcher的watchFragments()方法,注意,這裡的watcher可能有兩種,但不管是哪一種,都會使用當前傳入的activity獲取到對應的FragmentManager/SupportFragmentManager物件,呼叫它的registerFragmentLifecycleCallbacks()方法,在對應的onDestroyView()和onDestoryed()方法執行完後,分別使用refWatcher.watch(view)和refWatcher.watch(fragment)進行記憶體洩漏的檢測,程式碼如下所示。
@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); }
注意,下面到真正關鍵的地方了,接下來分析refWatcher.watch()這行程式碼。
12、RefWatcher#watch()
public void watch(Object watchedReference, String referenceName) { if (this == DISABLED) { return; } checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); final long watchStartNanoTime = System.nanoTime(); // 1 String key = UUID.randomUUID().toString(); // 2 retainedKeys.add(key); // 3 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); // 4 ensureGoneAsync(watchStartNanoTime, reference); }
注意到在註釋1處使用隨機的UUID保證了每個檢測物件對應的
key 的唯一性。在註釋2處將生成的key新增到型別為CopyOnWriteArraySet的Set集合中。在註釋3處新建了一個自定義的弱引用KeyedWeakReference,看看它內部的實現。
13、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"); } }
可以看到,在KeyedWeakReference內部,使用了key和name標識了一個被檢測的WeakReference物件。在註釋1處,將弱引用和引用佇列 ReferenceQueue 關聯起來,如果弱引用referent持有的物件被GC回收,JVM就會把這個弱引用加入到與之關聯的引用佇列referenceQueue中。即 KeyedWeakReference 持有的 Activity 物件如果被GC回收,該物件就會加入到引用佇列 referenceQueue 中。
接著我們回到RefWatcher.watch()裡註釋4處的ensureGoneAsync()方法。
14、RefWatcher#ensureGoneAsync()
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { // 1 watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { // 2 return ensureGone(reference watchStartNanoTime); } }); }
在ensureGoneAsync()方法中,在註釋1處使用 watchExecutor 執行了註釋2處的 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的例項。
下面看看watchExecutor內部的邏輯。
15、AndroidWatchExecutor
public final class AndroidWatchExecutor implements WatchExecutor { ... public AndroidWatchExecutor(long initialDelayMillis){ mainHandler = new Handler(Looper.getMainLooper()); HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME); handlerThread.start(); // 1 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); } } ... }
在註釋1處AndroidWatchExecutor的構造方法中,注意到這裡使用HandlerThread的looper新建了一個backgroundHandler,後面會用到。在註釋2處,會判斷當前執行緒是否是主執行緒,如果是,則直接呼叫waitForIdle()方法,如果不是,則呼叫postWaitForIdle(),來看看這個方法。
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) { mainHandler.post(new Runnable() { @Override public void run() { waitForIdle(retryable, failedAttempts); } }); }
很清晰,這裡使用了在構造方法中用主執行緒looper構造的mainHandler進行post,那麼waitForIdle()最終也會在主執行緒執行。接著看看waitForIdle()的實現。
private void waitForIdle(final Retryable retryable,final int failedAttempts) { Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { postToBackgroundWithDelay(retryable, failedAttempts); return false; } }); }
這裡MessageQueue.IdleHandler()回撥方法的作用是當 looper 空閒的時候,會回撥 queueIdle 方法,然後執行內部的postToBackgroundWithDelay()方法。接下來看看它的實現。
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) { long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts),maxBackoffFactor); // 1 long delayMillis = initialDelayMillis * exponentialBackoffFactor; // 2 backgroundHandler.postDelayed(new Runnable() { @Override public void run() { // 3 Retryable.Result result = retryable.run(); // 4 if (result == RETRY) { postWaitForIdle(retryable, failedAttempts +1); } } }, delayMillis); }
先看到註釋4處,可以明白,postToBackgroundWithDelay()是一個遞迴方法,如果result 一直等於RETRY的話,則會一直執行postWaitForIdle()方法。在回到註釋1處,這裡initialDelayMillis 的預設值是 5s,因此delayMillis就是5s。在註釋2處,使用了在構造方法中用HandlerThread的looper新建的backgroundHandler進行非同步延時執行retryable的run()方法。這個run()方法裡執行的就是RefWatcher的ensureGoneAsync()方法中註釋2處的ensureGone()這行程式碼,繼續看它內部的邏輯。
16、RefWatcher#ensureGone()
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(); removeWeaklyReachableReferences(); // 5 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); HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key) .referenceName(reference.name) .watchDurationMs(watchDurationMs) .gcDurationMs(gcDurationMs) .heapDumpDurationMs(heapDumpDurationMs) .build(); heapdumpListener.analyze(heapDump); } return DONE; }
在註釋1處,執行了removeWeaklyReachableReferences()這個方法,接下來分析下它的含義。
private void removeWeaklyReachableReferences() { KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } }
這裡使用了while迴圈遍歷 ReferenceQueue ,並從 retainedKeys中移除對應的Reference。
再看到註釋2處,當Android裝置處於debug狀態時,會直接返回RETRY進行延時重試檢測的操作。在註釋3處,我們看看gone(reference)這個方法的邏輯。
private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key); }
這裡會判斷 retainedKeys 集合中是否還含有 reference,若沒有,證明已經被回收了,若含有,可能已經發生記憶體洩露(或Gc還沒有執行回收)。前面的分析中我們知道了 reference 被回收的時候,會被加進 referenceQueue 裡面,然後我們會呼叫removeWeaklyReachableReferences()遍歷 referenceQueue 移除掉 retainedKeys 裡面的 refrence。
接著我們看到註釋4處,執行了gcTrigger的runGc()方法進行垃圾回收,然後使用了removeWeaklyReachableReferences()方法移除已經被回收的引用。這裡我們在深入地分析下runGc()的實現。
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 everytime. Runtime.gc() is // more likely to perform a gc. Runtime.getRuntime().gc(); enqueueReferences(); System.runFinalization(); } private void enqueueReferences() { // Hack. We don't have a programmatic way to waitfor the reference queue daemon to move // references to the appropriate queues. try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } } };
這裡並沒有使用System.gc()方法進行回收,因為system.gc()並不會每次都執行。而是從AOSP中拷貝一段GC回收的程式碼,從而相比System.gc()更能夠保證進行垃圾回收的工作。
最後我們分析下注釋5處的程式碼處理。首先會判斷activity 如果還沒有被回收,則證明發生記憶體洩露,進行if判斷裡面的操作。在裡面先呼叫堆資訊轉儲者heapDumper的dumpHeap()生成相應的 hprof 檔案。這裡的heapDumper是一個HeapDumper介面,具體的實現是AndroidHeapDumper。我們分析下AndroidHeapDumper的dumpHeap()方法是如何生成hprof檔案的。
public File dumpHeap() { File heapDumpFile = leakDirectoryProvider.newHeapDumpFile(); if (heapDumpFile == RETRY_LATER) { return RETRY_LATER; } ... try { Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); ... return heapDumpFile; } catch (Exception e) { ... // Abort heap dump return RETRY_LATER; } }
這裡的核心操作就是呼叫了
Android SDK的API Debug.dumpHprofData() 來生成 hprof 檔案。
如果這個檔案等於RETRY_LATER則表示生成失敗,直接返回RETRY進行延時重試檢測的操作。如果不等於的話,則表示生成成功,最後會執行heapdumpListener的analyze()對新建立的HeapDump物件進行洩漏分析。由前面對AndroidRefWatcherBuilder的listenerServiceClass()的分析可知,heapdumpListener的實現
就是ServiceHeapDumpListener,接著看到ServiceHeapDumpListener的analyze方法。
17、ServiceHeapDumpListener#analyze()
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的runAnalysis()方法,為了避免減慢app程序或佔用記憶體,這裡將HeapAnalyzerService設定在了一個獨立的程序中。接著繼續分析runAnalysis()方法裡面的處理。
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull String referenceKey, boolean computeRetainedSize) { ... try { listener.onProgressUpdate(READING_HEAP_DUMP_FILE); // 1 HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); // 2 HprofParser parser = new HprofParser(buffer); listener.onProgressUpdate(PARSING_HEAP_DUMP); Snapshot snapshot = parser.parse(); listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS); // 3 deduplicateGcRoots(snapshot); listener.onProgressUpdate(FINDING_LEAKING_REF); // 4 Instance leakingRef = findLeakingReference(referenceKey, snapshot); // 5 if (leakingRef == null) { return noLeak(since(analysisStartNanoTime)); } // 6 return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } } ... } @Override protected final void onAnalysisResultFailure(String failureMessage) { super.onAnalysisResultFailure(failureMessage); String failureTitle = getString(R.string.leak_canary_result_failure_title); showNotification(null, failureTitle, failureMessage); }
這裡的HeapAnalyzerService實質是一個型別為IntentService的ForegroundService,執行startForegroundService()之後,會回撥onHandleIntentInForeground()方法。註釋1處,首先會新建一個HeapAnalyzer物件,顧名思義,它就是根據RefWatcher生成的heap dumps資訊來分析被懷疑的洩漏是否是真的。在註釋2處,然後會呼叫它的checkForLeak()方法去使用haha庫解析 hprof檔案,如下所示:
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,@NonNull String referenceKey,boolean computeRetainedSize) {...try {listener.onProgressUpdate(READING_HEAP_DUMP_FILE);// 1HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);// 2HprofParser parser = new HprofParser(buffer);listener.onProgressUpdate(PARSING_HEAP_DUMP);Snapshot snapshot = parser.parse();listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);// 3deduplicateGcRoots(snapshot);listener.onProgressUpdate(FINDING_LEAKING_REF);// 4Instance leakingRef = findLeakingReference(referenceKey, snapshot);// 5if (leakingRef == null) {return noLeak(since(analysisStartNanoTime));}// 6return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);} catch (Throwable e) {return failure(e, since(analysisStartNanoTime));}}
在註釋1處,會新建一個記憶體對映快取檔案buffer。在註釋2處,會使用buffer新建一個HprofParser解析器去解析出對應的引用記憶體快照檔案snapshot。在註釋3處,為了減少在Android 6.0版本中重複GCRoots帶來的記憶體壓力的影響,使用deduplicateGcRoots()刪除了gcRoots中重複的根物件RootObj。在註釋4處,呼叫了findLeakingReference()方法將傳入的referenceKey和snapshot物件裡面所有類例項的欄位值對應的keyCandidate進行比較,如果沒有相等的,則表示沒有發生記憶體洩漏,直接呼叫註釋5處的程式碼返回一個沒有洩漏的分析結果AnalysisResult物件。如果找到了相等的,則表示發生了記憶體洩漏,執行註釋6處的程式碼findLeakTrace()方法返回一個有洩漏分析結果的AnalysisResult物件。
最後,我們來分析下HeapAnalyzerService中註釋3處的AbstractAnalysisResultService.sendResultToListener()方法,很明顯,這裡AbstractAnalysisResultService的實現類就是我們剛開始分析的用於展示洩漏路徑資訊得DisplayLeakService物件。在裡面直接建立一個由PendingIntent構建的洩漏通知用於供使用者點選去展示詳細的洩漏介面DisplayLeakActivity。核心程式碼如下所示:
public class DisplayLeakService extends AbstractAnalysisResultService { @Override protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) { ... boolean resultSaved = false; boolean shouldSaveResult = result.leakFound || result.failure != null; if (shouldSaveResult) { heapDump = renameHeapdump(heapDump); // 1 resultSaved = saveResult(heapDump, result); } if (!shouldSaveResult) { ... showNotification(null, contentTitle, contentText); } else if (resultSaved) { ... // 2 PendingIntent pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey); ... showNotification(pendingIntent, contentTitle, contentText); } else { onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text)); } ... }
可以看到,只要當分析的堆資訊檔案儲存成功之後,即在註釋1處返回的resultSaved為true時,才會執行註釋2處的邏輯,即建立一個供使用者點選跳轉到DisplayLeakActivity的延時通知。最後給出一張原始碼流程圖用於回顧:
四、總結
效能優化一直是Android中的進階和深入的方向之一,而記憶體洩漏一直是效能優化中比較重要的一部分,Android Studio自身提供了MAT等工具去分析記憶體洩漏,但是分析起來比較耗時,因而才誕生了LeakCanary,它的使用非常簡單,但是經過對它的深入分析之後,才發現,簡單的API後面往往藏著許多複雜的邏輯處理 。接下來,下篇筆者將會為大家帶來Android中的第三方依賴注入框架ButterKnife的原始碼分析,敬請期待~
參考連結
1、LeakCanary V1.6.2 原始碼
3、深入理解 Android 之 LeakCanary 原始碼解析