您好,登錄后才能下訂單哦!
LeakCanary中怎么檢測 Activity 是否泄漏,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
LeakCanary 使用方式
為了將 LeakCanary 引入到我們的項目里,我們只需要做以下兩步:
dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'}public class ExampleApplication extends Application { @Override public void onCreate() { super.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.install(this); 這么一句話,正式開啟了 LeakCanary 的大門,未來它就會自動幫我們檢測內存泄漏,并在發生泄漏是彈出通知信息。
從 LeakCanary.install(this); 開始
下面我們來看下它做了些什么?
public static RefWatcher install(Application application) { return install(application, DisplayLeakService.class, AndroidExcludedRefs.createAppDefaults().build()); }public static RefWatcher install(Application application, Class<? extends AbstractAnalysisResultService> listenerServiceClass, ExcludedRefs excludedRefs) { if (isInAnalyzerProcess(application)) { return RefWatcher.DISABLED; } enableDisplayLeakActivity(application); HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass); RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); ActivityRefWatcher.installOnIcsPlus(application, refWatcher); return refWatcher; }
首先,我們先看最重要的部分,就是:
RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
先生成了一個 RefWatcher ,這個東西非常關鍵,從名字可以看出,它是用來 watch Reference 的,也就是用來一個監控引用的工具。然后再把 refWatcher 和我們自己提供的 application 傳入到 ActivityRefWatcher.installOnIcsPlus(application, refWatcher); 這句里面,繼續看。
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); activityRefWatcher.watchActivities(); }
創建了一個 ActivityRefWatcher ,大家應該能感受到,這個東西就是用來監控我們的 Activity 泄漏狀況的,它調用 watchActivities() 方法,就可以開始進行監控了。下面就是它監控的核心原理:
public void watchActivities() { application.registerActivityLifecycleCallbacks(lifecycleCallbacks); }
它向 application 里注冊了一個 ActivitylifecycleCallbacks 的回調函數,可以用來監聽 Application 整個生命周期所有 Activity 的 lifecycle 事件。再看下這個 lifecycleCallbacks 是什么?
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { ActivityRefWatcher.this.onActivityDestroyed(activity); } };
原來它只監聽了所有 Activity 的 onActivityDestroyed 事件,當 Activity 被 Destory 時,調用 ActivityRefWatcher.this.onActivityDestroyed(activity); 函數。
猜測下,正常情況下,當一個這個函數應該 activity 被 Destory 時,那這個 activity 對象應該變成 null 才是正確的。如果沒有變成null,那么就意味著發生了內存泄漏。
因此我們向,這個函數 ActivityRefWatcher.this.onActivityDestroyed(activity); 應該是用來監聽 activity 對象是否變成了 null。繼續看。
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); } RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
可以看出,這個函數把目標 activity 對象傳給了 RefWatcher ,讓它去監控這個 activity 是否被正常回收了,若未被回收,則意味著發生了內存泄漏。
RefWatcher 如何監控 activity 是否被正常回收呢?
我們先來看看這個 RefWatcher 究竟是個什么東西?
public static RefWatcher androidWatcher(Context context, HeapDump.Listener heapDumpListener, ExcludedRefs excludedRefs) { AndroidHeapDumper heapDumper = new AndroidHeapDumper(context, leakDirectoryProvider); heapDumper.cleanup(); int watchDelayMillis = 5000; AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis); return new RefWatcher(executor, debuggerControl, GcTrigger.DEFAULT, heapDumper, heapDumpListener, excludedRefs); }
這里面涉及到兩個新的對象: AndroidHeapDumper 和 AndroidWatchExecutor ,前者用來 dump 堆內存狀態的,后者則是用來 watch 一個引用的監聽器。具體原理后面再看。總之,這里已經生成好了一個 RefWatcher 對象了。
現在再看上面 onActivityDestroyed(Activity activity) 里調用的 refWatcher.watch(activity); ,下面來看下這個最為核心的 watch(activity) 方法,了解它是如何監控 activity 是否被回收的。
private final Set<String> retainedKeys;public void watch(Object activity, String referenceName) { String key = UUID.randomUUID().toString(); retainedKeys.add(key); final KeyedWeakReference reference = new KeyedWeakReference(activity, key, referenceName, queue); watchExecutor.execute(new Runnable() { @Override public void run() { ensureGone(reference, watchStartNanoTime); } }); }final class KeyedWeakReference extends WeakReference<Object> { public final String key; public final String name; }
可以看到,它首先把我們傳入的 activity 包裝成了一個 KeyedWeakReference (可以暫時看成一個普通的 WeakReference),然后 watchExecutor 會去執行一個 Runnable,這個 Runnable 會調用 ensureGone(reference, watchStartNanoTime) 函數。
看這個函數之前猜測下,我們知道 watch 函數本身就是用來監聽 activity 是否被正常回收,這就涉及到兩個問題:
何時去檢查它是否回收?
如何有效地檢查它真的被回收?
所以我們覺得 ensureGone 函數本身要做的事正如它的名字,就是確保 reference 被回收掉了,否則就意味著內存泄漏。
核心函數:ensureGone(reference) 檢測回收
下面來看這個函數實現:
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { removeWeaklyReachableReferences(); if (gone(reference) || debuggerControl.isDebuggerAttached()) { return; } gcTrigger.runGc(); removeWeaklyReachableReferences(); if (!gone(reference)) { File heapDumpFile = heapDumper.dumpHeap(); heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } }private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key); }private void removeWeaklyReachableReferences() { KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } }
這里先來解釋下 WeakReference 和 ReferenceQueue 的工作原理。
1.弱引用 WeakReference
被強引用的對象就算發生 OOM 也永遠不會被垃圾回收機回收;被弱引用的對象,只要被垃圾回收器發現就會立即被回收;被軟引用的對象,具備內存敏感性,只有內存不足時才會被回收,常用來做內存敏感緩存器;虛引用則任意時刻都可能被回收,使用較少。
2.引用隊列 ReferenceQueue
我們常用一個 WeakReference<Activity> reference = new WeakReference(activity); ,這里我們創建了一個 reference 來弱引用到某個 activity ,當這個 activity 被垃圾回收器回收后,這個 reference 會被放入內部的 ReferenceQueue 中。也就是說,從隊列 ReferenceQueue 取出來的所有 reference ,它們指向的真實對象都已經成功被回收了。
然后再回到上面的代碼。
在一個 activity 傳給 RefWatcher 時會創建一個***的 key 對應這個 activity,該key存入一個集合 retainedKeys 中。也就是說,所有我們想要觀測的 activity 對應的*** key 都會被放入 retainedKeys 集合中。
基于我們對 ReferenceQueue 的了解,只要把隊列中所有的 reference 取出來,并把對應 retainedKeys 里的key移除,剩下的 key 對應的對象都沒有被回收。
ensureGone 首先調用 removeWeaklyReachableReferences 把已被回收的對象的 key 從 retainedKeys 移除,剩下的 key 都是未被回收的對象;
if (gone(reference)) 用來判斷某個 reference 的key是否仍在 retainedKeys 里,若不在,表示已回收,否則繼續;
gcTrigger.runGc(); 手動出發 GC,立即把所有 WeakReference 引用的對象回收;
removeWeaklyReachableReferences(); 再次清理 retainedKeys,如果該 reference 還在 retainedKeys里 (if (!gone(reference))),表示泄漏;
利用 heapDumper 把內存情況 dump 成文件,并調用 heapdumpListener 進行內存分析,進一步確認是否發生內存泄漏。
如果確認發生內存泄漏,調用 DisplayLeakService 發送通知。
至此,核心的內存泄漏檢測機制便看完了。
內存泄漏檢測小結
從上面我們大概了解了內存泄漏檢測機制,大概是以下幾個步驟:
利用 application.registerActivityLifecycleCallbacks(lifecycleCallbacks) 來監聽整個生命周期內的 Activity onDestoryed 事件;
當某個 Activity 被 destory 后,將它傳給 RefWatcher 去做觀測,確保其后續會被正常回收;
RefWatcher 首先把 Activity 使用 KeyedWeakReference 引用起來,并使用一個 ReferenceQueue 來記錄該 KeyedWeakReference 指向的對象是否已被回收;
AndroidWatchExecutor 會在 5s 后,開始檢查這個弱引用內的 Activity 是否被正常回收。判斷條件是:若 Activity 被正常回收,那么引用它的 KeyedWeakReference 會被自動放入 ReferenceQueue 中。
判斷方式是:先看 Activity 對應的 KeyedWeakReference 是否已經放入 ReferenceQueue 中;如果沒有,則手動GC: gcTrigger.runGc(); ;然后再一次判斷 ReferenceQueue 是否已經含有對應的 KeyedWeakReference 。若還未被回收,則認為可能發生內存泄漏。
利用 HeapAnalyzer 對 dump 的內存情況進行分析并進一步確認,若確定發生泄漏,則利用 DisplayLeakService 發送通知。
探討一些關于 LeakCanary 有趣的問題
在學習了 LeakCanary 的源碼之后,我想再提幾個有趣的問題做些探討。
LeakCanary 項目目錄結構為什么這樣分?
下面是整個 LeakCanary 的項目結構:
對于開發者而言,只需要使用到 LeakCanary.install(this); 這一句即可。那整個項目為什么要分成這么多個 module 呢?
實際上,這里面每一個 module 都有自己的角色。
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-sample : 這個很簡單,就是提供了一個用法 sample。
當 Activity 被 destory 后,LeakCanary 多久后會去進行檢查其是否泄漏呢?
在源碼中可以看到,LeakCanary 并不會在 destory 后立即去檢查,而是讓一個 AndroidWatchExecutor 去進行檢查。它會做什么呢?
@Override public void execute(final Runnable command) { if (isOnMainThread()) { executeDelayedAfterIdleUnsafe(command); } else { mainHandler.post(new Runnable() { @Override public void run() { executeDelayedAfterIdleUnsafe(command); } }); } }void executeDelayedAfterIdleUnsafe(final Runnable runnable) { // This needs to be called from the main thread. Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { backgroundHandler.postDelayed(runnable, delayMillis); return false; } }); }
可以看到,它首先會向主線程的 MessageQueue 添加一個 IdleHandler 。
什么是 IdleHandler ?我們知道 Looper 會不斷從 MessageQueue 里取出 Message 并執行。當沒有新的 Message 執行時,Looper 進入 Idle 狀態時,就會取出 IdleHandler 來執行。
換句話說, IdleHandler 就是 優先級別較低的 Message ,只有當 Looper 沒有消息要處理時才得到處理。而且,內部的 queueIdle() 方法若返回 true ,表示該任務一直存活,每次 Looper 進入 Idle 時就執行;反正,如果返回 false ,則表示只會執行一次,執行完后丟棄。
那么,這件優先級較低的任務是什么呢? backgroundHandler.postDelayed(runnable, delayMillis); ,runnable 就是之前 ensureGone() 。
也就是說,當主線程空閑了,沒事做了,開始向后臺線程發送一個延時消息,告訴后臺線程,5s(delayMillis)后開始檢查 Activity 是否被回收了。
所以,當 Activity 發生 destory 后,首先要等到主線程空閑,然后再延時 5s(delayMillis),才開始執行泄漏檢查。
知識點:
1. 如何創建一個優先級低的主線程任務,它只會在主線程空閑時才執行,不會影響到app的性能?
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // do task return false; // only once } });
2. 如何快速創建一個主/子線程handler?
//主線程handlermainHandler = new Handler(Looper.getMainLooper());//子線程handlerHandlerThread handlerThread = new HandlerThread(“子線程任務”); handlerThread.start(); Handler backgroundHandler = new Handler(handlerThread.getLooper());
3. 如何快速判斷當前是否運行在主線程?
Looper.getMainLooper().getThread() == Thread.currentThread();
System.gc() 可以觸發立即 gc 嗎?如果不行那怎么才能觸發即時 gc 呢?
在 LeakCanary 里,需要立即觸發 gc,并在之后立即判斷弱引用是否被回收。這意味著該 gc 必須能夠立即同步執行。
常用的觸發 gc 方法是 System.gc() ,那它能達到我們的要求嗎?
我們來看下其實現方式:
/** * Indicates to the VM that it would be a good time to run the * garbage collector. Note that this is a hint only. There is no guarantee * that the garbage collector will actually be run. */public static void gc() { boolean shouldRunGC; synchronized(lock) { shouldRunGC = justRanFinalization; if (shouldRunGC) { justRanFinalization = false; } else { runGC = true; } } if (shouldRunGC) { Runtime.getRuntime().gc(); } }
注釋里清楚說了, System.gc() 只是建議垃圾回收器來執行回收,但是 不能保證真的去回收 。從代碼也能看出,必須先判斷 shouldRunGC 才能決定是否真的要 gc。
知識點:
那要怎么實現 即時 GC 呢?
LeakCanary 參考了一段 AOSP 的代碼
// System.gc() does not garbage collect every time. Runtime.gc() is// more likely to perfom a gc.Runtime.getRuntime().gc(); enqueueReferences(); System.runFinalization();public static void enqueueReferences() { /* * Hack. We don't have a programmatic way to wait for the reference queue * daemon to move references to the appropriate queues. */ try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } }
可以怎樣來改造 LeakCanary 呢?
忽略某些已知泄漏的類或Activity
LeakCanary 提供了 ExcludedRefs 類,可以向里面添加某些主動忽略的類。比如已知 Android 源代碼里有某些內存泄漏,不屬于我們 App 的泄漏,那么就可以 exclude 掉。
另外,如果不想監控某些特殊的 Activity,那么可以在 onActivityDestroyed(Activity activity) 里,過濾掉特殊的 Activity,只對其它 Activity 調用 refWatcher.watch(activity) 監控。
把內存泄漏數據上傳至服務器
在 LeakCanary 提供了 AbstractAnalysisResultService ,它是一個 intentService,接收到的 intent 內包含了 HeapDump 數據和 AnalysisResult 結果,我們只要繼承這個類,實現自己的 listenerServiceClass ,就可以將堆數據和分析結果上傳到我們自己的服務器上。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。