您好,登錄后才能下訂單哦!
LeakCanary中怎么檢測(cè) Activity 是否泄漏,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
LeakCanary 使用方式
為了將 LeakCanary 引入到我們的項(xiàng)目里,我們只需要做以下兩步:
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); } }
可以看出,最關(guān)鍵的就是 LeakCanary.install(this); 這么一句話,正式開(kāi)啟了 LeakCanary 的大門,未來(lái)它就會(huì)自動(dòng)幫我們檢測(cè)內(nèi)存泄漏,并在發(fā)生泄漏是彈出通知信息。
從 LeakCanary.install(this); 開(kāi)始
下面我們來(lái)看下它做了些什么?
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);
先生成了一個(gè) RefWatcher ,這個(gè)東西非常關(guān)鍵,從名字可以看出,它是用來(lái) watch Reference 的,也就是用來(lái)一個(gè)監(jiān)控引用的工具。然后再把 refWatcher 和我們自己提供的 application 傳入到 ActivityRefWatcher.installOnIcsPlus(application, refWatcher); 這句里面,繼續(xù)看。
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); activityRefWatcher.watchActivities(); }
創(chuàng)建了一個(gè) ActivityRefWatcher ,大家應(yīng)該能感受到,這個(gè)東西就是用來(lái)監(jiān)控我們的 Activity 泄漏狀況的,它調(diào)用 watchActivities() 方法,就可以開(kāi)始進(jìn)行監(jiān)控了。下面就是它監(jiān)控的核心原理:
public void watchActivities() { application.registerActivityLifecycleCallbacks(lifecycleCallbacks); }
它向 application 里注冊(cè)了一個(gè) ActivitylifecycleCallbacks 的回調(diào)函數(shù),可以用來(lái)監(jiān)聽(tīng) Application 整個(gè)生命周期所有 Activity 的 lifecycle 事件。再看下這個(gè) 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); } };
原來(lái)它只監(jiān)聽(tīng)了所有 Activity 的 onActivityDestroyed 事件,當(dāng) Activity 被 Destory 時(shí),調(diào)用 ActivityRefWatcher.this.onActivityDestroyed(activity); 函數(shù)。
猜測(cè)下,正常情況下,當(dāng)一個(gè)這個(gè)函數(shù)應(yīng)該 activity 被 Destory 時(shí),那這個(gè) activity 對(duì)象應(yīng)該變成 null 才是正確的。如果沒(méi)有變成null,那么就意味著發(fā)生了內(nèi)存泄漏。
因此我們向,這個(gè)函數(shù) ActivityRefWatcher.this.onActivityDestroyed(activity); 應(yīng)該是用來(lái)監(jiān)聽(tīng) activity 對(duì)象是否變成了 null。繼續(xù)看。
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); } RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
可以看出,這個(gè)函數(shù)把目標(biāo) activity 對(duì)象傳給了 RefWatcher ,讓它去監(jiān)控這個(gè) activity 是否被正?;厥樟?,若未被回收,則意味著發(fā)生了內(nèi)存泄漏。
RefWatcher 如何監(jiān)控 activity 是否被正?;厥漳?
我們先來(lái)看看這個(gè) RefWatcher 究竟是個(gè)什么東西?
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); }
這里面涉及到兩個(gè)新的對(duì)象: AndroidHeapDumper 和 AndroidWatchExecutor ,前者用來(lái) dump 堆內(nèi)存狀態(tài)的,后者則是用來(lái) watch 一個(gè)引用的監(jiān)聽(tīng)器。具體原理后面再看??傊?,這里已經(jīng)生成好了一個(gè) RefWatcher 對(duì)象了。
現(xiàn)在再看上面 onActivityDestroyed(Activity activity) 里調(diào)用的 refWatcher.watch(activity); ,下面來(lái)看下這個(gè)最為核心的 watch(activity) 方法,了解它是如何監(jiān)控 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 包裝成了一個(gè) KeyedWeakReference (可以暫時(shí)看成一個(gè)普通的 WeakReference),然后 watchExecutor 會(huì)去執(zhí)行一個(gè) Runnable,這個(gè) Runnable 會(huì)調(diào)用 ensureGone(reference, watchStartNanoTime) 函數(shù)。
看這個(gè)函數(shù)之前猜測(cè)下,我們知道 watch 函數(shù)本身就是用來(lái)監(jiān)聽(tīng) activity 是否被正?;厥眨@就涉及到兩個(gè)問(wèn)題:
何時(shí)去檢查它是否回收?
如何有效地檢查它真的被回收?
所以我們覺(jué)得 ensureGone 函數(shù)本身要做的事正如它的名字,就是確保 reference 被回收掉了,否則就意味著內(nèi)存泄漏。
核心函數(shù):ensureGone(reference) 檢測(cè)回收
下面來(lái)看這個(gè)函數(shù)實(shí)現(xiàn):
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); } }
這里先來(lái)解釋下 WeakReference 和 ReferenceQueue 的工作原理。
1.弱引用 WeakReference
被強(qiáng)引用的對(duì)象就算發(fā)生 OOM 也永遠(yuǎn)不會(huì)被垃圾回收機(jī)回收;被弱引用的對(duì)象,只要被垃圾回收器發(fā)現(xiàn)就會(huì)立即被回收;被軟引用的對(duì)象,具備內(nèi)存敏感性,只有內(nèi)存不足時(shí)才會(huì)被回收,常用來(lái)做內(nèi)存敏感緩存器;虛引用則任意時(shí)刻都可能被回收,使用較少。
2.引用隊(duì)列 ReferenceQueue
我們常用一個(gè) WeakReference<Activity> reference = new WeakReference(activity); ,這里我們創(chuàng)建了一個(gè) reference 來(lái)弱引用到某個(gè) activity ,當(dāng)這個(gè) activity 被垃圾回收器回收后,這個(gè) reference 會(huì)被放入內(nèi)部的 ReferenceQueue 中。也就是說(shuō),從隊(duì)列 ReferenceQueue 取出來(lái)的所有 reference ,它們指向的真實(shí)對(duì)象都已經(jīng)成功被回收了。
然后再回到上面的代碼。
在一個(gè) activity 傳給 RefWatcher 時(shí)會(huì)創(chuàng)建一個(gè)***的 key 對(duì)應(yīng)這個(gè) activity,該key存入一個(gè)集合 retainedKeys 中。也就是說(shuō),所有我們想要觀測(cè)的 activity 對(duì)應(yīng)的*** key 都會(huì)被放入 retainedKeys 集合中。
基于我們對(duì) ReferenceQueue 的了解,只要把隊(duì)列中所有的 reference 取出來(lái),并把對(duì)應(yīng) retainedKeys 里的key移除,剩下的 key 對(duì)應(yīng)的對(duì)象都沒(méi)有被回收。
ensureGone 首先調(diào)用 removeWeaklyReachableReferences 把已被回收的對(duì)象的 key 從 retainedKeys 移除,剩下的 key 都是未被回收的對(duì)象;
if (gone(reference)) 用來(lái)判斷某個(gè) reference 的key是否仍在 retainedKeys 里,若不在,表示已回收,否則繼續(xù);
gcTrigger.runGc(); 手動(dòng)出發(fā) GC,立即把所有 WeakReference 引用的對(duì)象回收;
removeWeaklyReachableReferences(); 再次清理 retainedKeys,如果該 reference 還在 retainedKeys里 (if (!gone(reference))),表示泄漏;
利用 heapDumper 把內(nèi)存情況 dump 成文件,并調(diào)用 heapdumpListener 進(jìn)行內(nèi)存分析,進(jìn)一步確認(rèn)是否發(fā)生內(nèi)存泄漏。
如果確認(rèn)發(fā)生內(nèi)存泄漏,調(diào)用 DisplayLeakService 發(fā)送通知。
至此,核心的內(nèi)存泄漏檢測(cè)機(jī)制便看完了。
內(nèi)存泄漏檢測(cè)小結(jié)
從上面我們大概了解了內(nèi)存泄漏檢測(cè)機(jī)制,大概是以下幾個(gè)步驟:
利用 application.registerActivityLifecycleCallbacks(lifecycleCallbacks) 來(lái)監(jiān)聽(tīng)整個(gè)生命周期內(nèi)的 Activity onDestoryed 事件;
當(dāng)某個(gè) Activity 被 destory 后,將它傳給 RefWatcher 去做觀測(cè),確保其后續(xù)會(huì)被正?;厥?
RefWatcher 首先把 Activity 使用 KeyedWeakReference 引用起來(lái),并使用一個(gè) ReferenceQueue 來(lái)記錄該 KeyedWeakReference 指向的對(duì)象是否已被回收;
AndroidWatchExecutor 會(huì)在 5s 后,開(kāi)始檢查這個(gè)弱引用內(nèi)的 Activity 是否被正?;厥?。判斷條件是:若 Activity 被正常回收,那么引用它的 KeyedWeakReference 會(huì)被自動(dòng)放入 ReferenceQueue 中。
判斷方式是:先看 Activity 對(duì)應(yīng)的 KeyedWeakReference 是否已經(jīng)放入 ReferenceQueue 中;如果沒(méi)有,則手動(dòng)GC: gcTrigger.runGc(); ;然后再一次判斷 ReferenceQueue 是否已經(jīng)含有對(duì)應(yīng)的 KeyedWeakReference 。若還未被回收,則認(rèn)為可能發(fā)生內(nèi)存泄漏。
利用 HeapAnalyzer 對(duì) dump 的內(nèi)存情況進(jìn)行分析并進(jìn)一步確認(rèn),若確定發(fā)生泄漏,則利用 DisplayLeakService 發(fā)送通知。
探討一些關(guān)于 LeakCanary 有趣的問(wèn)題
在學(xué)習(xí)了 LeakCanary 的源碼之后,我想再提幾個(gè)有趣的問(wèn)題做些探討。
LeakCanary 項(xiàng)目目錄結(jié)構(gòu)為什么這樣分?
下面是整個(gè) LeakCanary 的項(xiàng)目結(jié)構(gòu):
對(duì)于開(kāi)發(fā)者而言,只需要使用到 LeakCanary.install(this); 這一句即可。那整個(gè)項(xiàng)目為什么要分成這么多個(gè) module 呢?
實(shí)際上,這里面每一個(gè) module 都有自己的角色。
leakcanary-watcher : 這是一個(gè)通用的內(nèi)存檢測(cè)器,對(duì)外提供一個(gè) RefWatcher#watch(Object watchedReference),可以看出,它不僅能夠檢測(cè) Activity ,還能監(jiān)測(cè)任意常規(guī)的 Java Object 的泄漏情況。
leakcanary-android : 這個(gè) module 是與 Android 世界的接入點(diǎn),用來(lái)專門監(jiān)測(cè) Activity 的泄漏情況,內(nèi)部使用了 application#registerActivityLifecycleCallbacks 方法來(lái)監(jiān)聽(tīng) onDestory 事件,然后利用 leakcanary-watcher 來(lái)進(jìn)行弱引用+手動(dòng) GC 機(jī)制進(jìn)行監(jiān)控。
leakcanary-analyzer : 這個(gè) module 提供了 HeapAnalyzer ,用來(lái)對(duì) dump 出來(lái)的內(nèi)存進(jìn)行分析并返回內(nèi)存分析結(jié)果 AnalysisResult ,內(nèi)部包含了泄漏發(fā)生的路徑等信息供開(kāi)發(fā)者尋找定位。
leakcanary-android-no-op : 這個(gè) module 是專門給 release 的版本用的,內(nèi)部只提供了兩個(gè)完全空白的類 LeakCanary 和 RefWatcher ,這兩個(gè)類不會(huì)做任何內(nèi)存泄漏相關(guān)的分析。為什么?因?yàn)?LeakCanary 本身會(huì)由于不斷 gc 影響到 app 本身的運(yùn)行,而且主要用于開(kāi)發(fā)階段的內(nèi)存泄漏檢測(cè)。因此對(duì)于 release 則可以 disable 所有泄漏分析。
leakcanary-sample : 這個(gè)很簡(jiǎn)單,就是提供了一個(gè)用法 sample。
當(dāng) Activity 被 destory 后,LeakCanary 多久后會(huì)去進(jìn)行檢查其是否泄漏呢?
在源碼中可以看到,LeakCanary 并不會(huì)在 destory 后立即去檢查,而是讓一個(gè) AndroidWatchExecutor 去進(jìn)行檢查。它會(huì)做什么呢?
@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; } }); }
可以看到,它首先會(huì)向主線程的 MessageQueue 添加一個(gè) IdleHandler 。
什么是 IdleHandler ?我們知道 Looper 會(huì)不斷從 MessageQueue 里取出 Message 并執(zhí)行。當(dāng)沒(méi)有新的 Message 執(zhí)行時(shí),Looper 進(jìn)入 Idle 狀態(tài)時(shí),就會(huì)取出 IdleHandler 來(lái)執(zhí)行。
換句話說(shuō), IdleHandler 就是 優(yōu)先級(jí)別較低的 Message ,只有當(dāng) Looper 沒(méi)有消息要處理時(shí)才得到處理。而且,內(nèi)部的 queueIdle() 方法若返回 true ,表示該任務(wù)一直存活,每次 Looper 進(jìn)入 Idle 時(shí)就執(zhí)行;反正,如果返回 false ,則表示只會(huì)執(zhí)行一次,執(zhí)行完后丟棄。
那么,這件優(yōu)先級(jí)較低的任務(wù)是什么呢? backgroundHandler.postDelayed(runnable, delayMillis); ,runnable 就是之前 ensureGone() 。
也就是說(shuō),當(dāng)主線程空閑了,沒(méi)事做了,開(kāi)始向后臺(tái)線程發(fā)送一個(gè)延時(shí)消息,告訴后臺(tái)線程,5s(delayMillis)后開(kāi)始檢查 Activity 是否被回收了。
所以,當(dāng) Activity 發(fā)生 destory 后,首先要等到主線程空閑,然后再延時(shí) 5s(delayMillis),才開(kāi)始執(zhí)行泄漏檢查。
知識(shí)點(diǎn):
1. 如何創(chuàng)建一個(gè)優(yōu)先級(jí)低的主線程任務(wù),它只會(huì)在主線程空閑時(shí)才執(zhí)行,不會(huì)影響到app的性能?
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // do task return false; // only once } });
2. 如何快速創(chuàng)建一個(gè)主/子線程handler?
//主線程handlermainHandler = new Handler(Looper.getMainLooper());//子線程handlerHandlerThread handlerThread = new HandlerThread(“子線程任務(wù)”); handlerThread.start(); Handler backgroundHandler = new Handler(handlerThread.getLooper());
3. 如何快速判斷當(dāng)前是否運(yùn)行在主線程?
Looper.getMainLooper().getThread() == Thread.currentThread();
System.gc() 可以觸發(fā)立即 gc 嗎?如果不行那怎么才能觸發(fā)即時(shí) gc 呢?
在 LeakCanary 里,需要立即觸發(fā) gc,并在之后立即判斷弱引用是否被回收。這意味著該 gc 必須能夠立即同步執(zhí)行。
常用的觸發(fā) gc 方法是 System.gc() ,那它能達(dá)到我們的要求嗎?
我們來(lái)看下其實(shí)現(xiàn)方式:
/** * 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(); } }
注釋里清楚說(shuō)了, System.gc() 只是建議垃圾回收器來(lái)執(zhí)行回收,但是 不能保證真的去回收 。從代碼也能看出,必須先判斷 shouldRunGC 才能決定是否真的要 gc。
知識(shí)點(diǎn):
那要怎么實(shí)現(xiàn) 即時(shí) 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(); } }
可以怎樣來(lái)改造 LeakCanary 呢?
忽略某些已知泄漏的類或Activity
LeakCanary 提供了 ExcludedRefs 類,可以向里面添加某些主動(dòng)忽略的類。比如已知 Android 源代碼里有某些內(nèi)存泄漏,不屬于我們 App 的泄漏,那么就可以 exclude 掉。
另外,如果不想監(jiān)控某些特殊的 Activity,那么可以在 onActivityDestroyed(Activity activity) 里,過(guò)濾掉特殊的 Activity,只對(duì)其它 Activity 調(diào)用 refWatcher.watch(activity) 監(jiān)控。
把內(nèi)存泄漏數(shù)據(jù)上傳至服務(wù)器
在 LeakCanary 提供了 AbstractAnalysisResultService ,它是一個(gè) intentService,接收到的 intent 內(nèi)包含了 HeapDump 數(shù)據(jù)和 AnalysisResult 結(jié)果,我們只要繼承這個(gè)類,實(shí)現(xiàn)自己的 listenerServiceClass ,就可以將堆數(shù)據(jù)和分析結(jié)果上傳到我們自己的服務(wù)器上。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。