前言
一直在使用這個(gè)框架,但是一直沒有注意這個(gè)框架的實(shí)現(xiàn)原理。使用過這款框架的人應(yīng)該都知道,LeakCanary是一款能夠幫助開發(fā)者檢查內(nèi)存泄漏的開源庫,只需要簡單配置,就可以當(dāng)使用過程中產(chǎn)生內(nèi)存泄漏時(shí),彈出通知,并且可以我們可以查看詳細(xì)的引用鏈,幫助我們進(jìn)行內(nèi)存泄漏的分析。
項(xiàng)目預(yù)覽

這里專門放了一張LeakCanary的項(xiàng)目的目錄結(jié)構(gòu),可以看到,LeakCanary的項(xiàng)目目錄還是很考究的,總體分為了5個(gè)模塊,每個(gè)都專門負(fù)責(zé)一個(gè)功能。
1.analyzer 內(nèi)存泄漏分析功能模塊,內(nèi)存泄漏的路徑分析就是該模塊,haha庫就是在該模塊中使用
2.watcher 內(nèi)存泄漏監(jiān)控模塊,監(jiān)控并且發(fā)現(xiàn)內(nèi)存泄漏的就是該模塊
3.android 將內(nèi)存泄漏監(jiān)控和Android的生命周期綁定,實(shí)現(xiàn)Android中的內(nèi)存泄漏監(jiān)聽
4.android-no-op 空殼模塊,實(shí)現(xiàn)真正發(fā)布realeas包后,不進(jìn)行內(nèi)存泄漏監(jiān)控,損耗性能
5.sample demo模塊
從上面的分析可以看出,LeakCanary這款內(nèi)存泄漏框架的目錄結(jié)構(gòu)真的很考究,按照LeakCanary的功能體積,完全沒必要拆分的這么散,但是這樣做,充分將LeakCanary的功能發(fā)揮到最大化,為什么這么說哪?
熟悉LeakCanary原理的都知道,LeankCanary其實(shí)是利用Java的弱引用特性,加上JVM回收一定的機(jī)制,實(shí)現(xiàn)內(nèi)存泄漏的監(jiān)控的,也就是說,LeakCanary并不是一款和Android死綁定的一款開源框架,所以這時(shí)候LeakCanary這樣的目錄結(jié)構(gòu)就體現(xiàn)出了它的優(yōu)點(diǎn),我們完全可以將watcher模塊或者analyzer模塊拆分出來,處理其他以Java語言作為開發(fā)語言的項(xiàng)目的內(nèi)存泄漏分析。
源碼分析
還是老方式,一個(gè)好的框架肯定有一個(gè)好的外觀類來統(tǒng)領(lǐng)入口,LeakCanary當(dāng)然不能少,一般我們的使用方式就是:
protected void setupLeakCanary() {
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);
}
當(dāng)然最核心的就是這句代碼LeakCanary.install(this);前面的LeakCanary.isInAnalyzerProcess(this)這里先暫不分析(當(dāng)然后面會(huì)分析的,這里面有一個(gè)很棒的方法)
public static RefWatcher install(Application application) {
//創(chuàng)建RefWatcher
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
//設(shè)置已知的內(nèi)存泄漏問題,或者系統(tǒng)的內(nèi)存泄漏問題
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
這里可以看到,總體上來看分為了四個(gè)步驟:
- 創(chuàng)建了RefWatcher(也就是檢測內(nèi)存泄漏的類)
- 設(shè)置了內(nèi)存泄漏的通知Service(通知)
- 設(shè)置了需要忽略的已知的系統(tǒng)級(jí)別的內(nèi)存泄漏(可以自定義)
- 開始監(jiān)聽
接下來就分別看看每一個(gè)步驟。
第一個(gè)步驟
/**
* 創(chuàng)建一個(gè)builder對(duì)象
*/
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
第一個(gè)步驟沒什么好說的,創(chuàng)建了一個(gè)專門為Android使用的Watcher的Builder類。后面我們看的很多方法都基于這個(gè)類。
第二個(gè)步驟
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public ServiceHeapDumpListener(Context context,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabled(context, listenerServiceClass, true);
setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
@SuppressWarnings("unchecked")
protected final T self() {
return (T) this;
}
接下來這個(gè)地方還是需要我們注意到,這里new了一個(gè)ServiceHeapDumpListener,在heapDumpListener方法里將new到listener賦值給了this.heapDumpListener。
這里有一個(gè)小細(xì)節(jié)挺值得我們注意的,這里由于是泛型,所以不能直接返回this,這里統(tǒng)一返回了self()方法,統(tǒng)一在self方法里做強(qiáng)制轉(zhuǎn)換和unchecked操作。
通過這里的方法我們可以知道,我們將DisplayLeakService.class類,設(shè)置給了AndroidRefWatcherBuilder的一個(gè)自定義Listener變量。
第三個(gè)步驟:設(shè)置已知的內(nèi)存泄漏問題,或者系統(tǒng)的內(nèi)存泄漏問題
這里我們就看一下AndroidExcludedRefs.createAppDefaults().build()這個(gè)方法。
public static ExcludedRefs.Builder createAppDefaults() {
return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}
public static 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;
}
到這里其實(shí)我們沒必要在往里面繼續(xù)看了,通過這兩個(gè)方法,大概可以看出這個(gè)是通過遍歷AndroidExcludedRefs.class類中定義的已知的一些系統(tǒng)級(jí)別的bug,得到一個(gè)集合,在后面發(fā)現(xiàn)內(nèi)存泄漏的時(shí)候會(huì)進(jìn)行忽略操作。
第四個(gè)步驟:開始進(jìn)行監(jiān)聽操作。
public RefWatcher buildAndInstall() {
//只能創(chuàng)建一次
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//創(chuàng)建RefWatcher
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
//默認(rèn)為true
if (watchActivities) {
//注意,在這里通過監(jiān)聽Application,監(jiān)聽Activity的生命周期
ActivityRefWatcher.install((Application) context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
可以看到,首先進(jìn)行了判空,單例的思想還是很重要的。
下面這個(gè)build()方法還是很重要的。
public final RefWatcher build() {
// 判斷install是否在Analyzer進(jìn)程里,重復(fù)執(zhí)行
if (isDisabled()) {
return RefWatcher.DISABLED;
}
//用于排除某些系統(tǒng)bug導(dǎo)致的內(nèi)存泄露
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
//用于分析生成的dump文件,找到內(nèi)存泄露的原因
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
//用于查詢是否正在調(diào)試中,調(diào)試中不會(huì)執(zhí)行內(nèi)存泄露檢測
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
//用于在產(chǎn)生內(nèi)存泄露室執(zhí)行dump 內(nèi)存heap
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
//執(zhí)行內(nèi)存泄露檢測的executor
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
//創(chuàng)建默認(rèn)的監(jiān)聽內(nèi)存泄漏的線程池
watchExecutor = defaultWatchExecutor();
}
//用于在判斷內(nèi)存泄露之前,再給一次GC的機(jī)會(huì)
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
這個(gè)build方法這里我們需要注意,我們?cè)谧铋_始的生成了AndroidRefWatcherBuilder,這個(gè)是繼承于RefWatcherBuilder類的,但是這里的build()方法是父類的方法,也就是RefWatcherBuilder的方法,但是在build()內(nèi)的許多調(diào)用都是AndroidRefWatcherBuilder重寫的方法。
首先來看第一個(gè)方法isDisabled()
@Override protected boolean isDisabled() {
//用于判斷服務(wù)進(jìn)程是否在前臺(tái),重要
return LeakCanary.isInAnalyzerProcess(context);
}
可以看到,這里又用到了我們前面提到的一個(gè)方法,是用于判斷當(dāng)前進(jìn)程是否在前臺(tái),這里因?yàn)榉治鲋髁鞒?,所以先不做分析?/p>
@Override protected ExcludedRefs defaultExcludedRefs() {
return AndroidExcludedRefs.createAppDefaults().build();
}
接下來看到是設(shè)置了Android特有的一些系統(tǒng)的內(nèi)存泄漏,和前面分析的一致。
//用于分析生成的dump文件,找到內(nèi)存泄露的原因
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
@Override protected HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
因?yàn)樵?code>install()方法中已經(jīng)設(shè)置了用于發(fā)送內(nèi)存泄漏通知的Service,這里變不為null,不然其實(shí)default的和初始化的也是相同的。
//用于查詢是否正在調(diào)試中,調(diào)試中不會(huì)執(zhí)行內(nèi)存泄露檢測
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
@Override protected DebuggerControl defaultDebuggerControl() {
return new AndroidDebuggerControl();
}
public final class AndroidDebuggerControl implements DebuggerControl {
@Override public boolean isDebuggerAttached() {
return Debug.isDebuggerConnected();
}
}
這里其實(shí)也挺值得我們學(xué)習(xí)的,這里當(dāng)是調(diào)試的時(shí)候,便不會(huì)進(jìn)行內(nèi)存泄漏檢測,而如何確定是在進(jìn)行調(diào)試,這里可以看到使用了Debug.isDebuggerConnected()方法。
//用于在產(chǎn)生內(nèi)存泄露室執(zhí)行dump 內(nèi)存heap
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
@Override protected HeapDumper defaultHeapDumper() {
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
return new AndroidHeapDumper(context, leakDirectoryProvider);
}
這里就是生產(chǎn)內(nèi)存泄漏的文件的。
//執(zhí)行內(nèi)存泄露檢測的executor
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
//創(chuàng)建默認(rèn)的監(jiān)聽內(nèi)存泄漏的線程池
watchExecutor = defaultWatchExecutor();
}
@Override protected WatchExecutor defaultWatchExecutor() {
//默認(rèn)線程池,5s
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
public AndroidWatchExecutor(long initialDelayMillis) {
//主線程Handler
mainHandler = new Handler(Looper.getMainLooper());
//這里new了一個(gè)HandlerThread,也就是一個(gè)異步線程,內(nèi)部封裝好了looper.prepare()等操作
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
//handlerThread內(nèi)部的handler
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
可以看到這里雖然叫做看似像是線程池,其實(shí)也是利用了Android官方的基礎(chǔ)組件,這里可以看到快速的創(chuàng)建了一個(gè)主現(xiàn)場的handler和一個(gè)HanlderThread,和HandlerThread內(nèi)部的Handler。具體HanlderThread是什么,這里放上這個(gè)類。
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
}
可以看到就是一個(gè)Thread只不過內(nèi)部封裝好了Android使用多線程Hanlder的一系列操作。
//用于在判斷內(nèi)存泄露之前,再給一次GC的機(jī)會(huì)
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
public interface GcTrigger {
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 every time. Runtime.gc() is
// more likely to perfom a gc.
//這里用的是Runtime.getRuntime().gc()
//注意這里和System.gc()的區(qū)別
Runtime.getRuntime().gc();
//等待100毫秒
enqueueReferences();
System.runFinalization();
}
private 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();
}
}
};
void runGc();
}
接下來算是LeakCanary的一個(gè)比較特殊的地方,接下來看到,設(shè)置了一個(gè)和GC相關(guān)的一個(gè)類,最終我們會(huì)發(fā)現(xiàn)是使用的上面放的DEFAULT,這里可以看到一個(gè)很特殊的一點(diǎn),這里使用了一個(gè)方法Runtime.getRuntime().gc(),而且也看到了官方對(duì)于此處的注釋這里引用了AOSP的一段代碼,System.gc()并不會(huì)每次都真正調(diào)用回收,所以使用Runtime.getRuntime().gc();這里就是我們平常不會(huì)注意到的知識(shí)點(diǎn),這里需要我們區(qū)分一下兩個(gè)的區(qū)別,我自己看了一下兩個(gè)的源碼,并沒有發(fā)現(xiàn)兩個(gè)內(nèi)部的不同(不知道是不是我看的方式的問題),我通過查詢,網(wǎng)上對(duì)于這兩個(gè)方法的區(qū)別總體是這樣解釋的。
/**
* 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();
}
}
以上是引用一篇不錯(cuò)的LeakCanary的源碼分析中關(guān)于gc()源碼(不知道為什么我自己看不到這樣的源碼,如果有人知道,評(píng)論告訴我一下,謝謝~~),從這里就可以看出,System.gc()的實(shí)質(zhì)其實(shí)是調(diào)用Runtime.getRuntime().gc(),只不過做了一些多線程同步的判斷,所以,我們調(diào)用System.gc()并不會(huì)一定出發(fā)JVM的GC操作。
到此build()方法到這里就分析完了,通過上面的分析我們會(huì)發(fā)現(xiàn),到目前為止基本上都是做的準(zhǔn)備工作,接下來就是LeakCanary的核心操作,檢測內(nèi)存泄漏。
//開啟LeakCanary的Activity,使圖標(biāo)顯示
LeakCanary.enableDisplayLeakActivity(context);
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
public static void setEnabled(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
下面這行代碼其實(shí)作用是開啟LeakCanary的應(yīng)用圖標(biāo),使其顯示。我們可以看到,這里傳入了DisplayLeakActivity.class類,最后通過packageManager.setComponentEnabledSetting這個(gè)方法,將Activity設(shè)置為COMPONENT_ENABLED_STATE_ENABLED狀態(tài)。這樣設(shè)置有什么作用哪,我們來看一下AndroidManifest.xml文件。
<activity
android:theme="@style/leak_canary_LeakCanary.Base"
android:name=".internal.DisplayLeakActivity"
android:process=":leakcanary"
android:enabled="false"
android:label="@string/leak_canary_display_activity_label"
android:icon="@mipmap/leak_canary_icon"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
可以看到,這里在清單文件中,將DisplayLeakActivity的enabled。這里還有一個(gè)需要我們注意到點(diǎn),這里使用了線程池。
private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");
public static void executeOnFileIoThread(Runnable runnable) {
fileIoExecutor.execute(runnable);
}
public static Executor newSingleThreadExecutor(String threadName) {
return Executors.newSingleThreadExecutor(new LeakCanarySingleThreadFactory(threadName));
}
可以看到這里創(chuàng)建了Java中的newSingleThreadExecutor線程池,具體特點(diǎn)這里就不詳細(xì)介紹了,簡單的說就是一個(gè)唯一的線程,順序的執(zhí)行任務(wù)。
監(jiān)聽生命周期
//默認(rèn)為true
if (watchActivities) {
//注意,在這里通過監(jiān)聽Application,監(jiān)聽Activity的生命周期
ActivityRefWatcher.install((Application) context, refWatcher);
}
前面的一系列分析,這里終于可以開始監(jiān)聽生命周期,也就是檢測內(nèi)存泄漏的地方了。
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
//注冊(cè)監(jiān)聽回調(diào)
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
可以看到這里避免重復(fù)監(jiān)聽,因?yàn)閮?nèi)部是使用一個(gè)ArrayList進(jìn)行保存lifecycleCallbacks,所以為了和之前的單例保持一致,這里就做移除操作。這里其實(shí)我們就要關(guān)注,這里是如果實(shí)現(xiàn)監(jiān)聽的。
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) {
//onDestroy的時(shí)候回調(diào)
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
可以看到這里利用了Android中的Application的特性,注冊(cè)了Application.ActivityLifecycleCallbacks監(jiān)聽器,在Activity的onDestroy方法中,調(diào)用了ActivityRefWatcher.this.onActivityDestroyed(activity);方法。
void onActivityDestroyed(Activity activity) {
//Activity在onDestroy的時(shí)候回調(diào)
refWatcher.watch(activity);
}
這里就調(diào)用了我們之前構(gòu)建的refWatcher對(duì)象的watch方法。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
//獲得當(dāng)前時(shí)間
final long watchStartNanoTime = System.nanoTime();
//生成一個(gè)唯一的key
String key = UUID.randomUUID().toString();
//保存這個(gè)key
retainedKeys.add(key);
//將檢查內(nèi)存泄漏的對(duì)象保存為一個(gè)弱引用,注意queue
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//異步開始分析這個(gè)弱引用
ensureGoneAsync(watchStartNanoTime, reference);
}
這里可以看到,這里使用了當(dāng)前時(shí)間作為唯一標(biāo)示,這里獲取時(shí)間的方法也很講究,System.nanoTime()這個(gè)和System.currentTimeMillis()的區(qū)別也很簡答,這里引用別人的分析簡單的說明一下:
平時(shí)產(chǎn)生隨機(jī)數(shù)時(shí)我們經(jīng)常拿時(shí)間做種子,比如用System.currentTimeMillis的結(jié)果,但是在執(zhí)行一些循環(huán)中使用了System.currentTimeMillis,那么每次的結(jié)果將會(huì)差別很小,甚至一樣,因?yàn)楝F(xiàn)代的計(jì)算機(jī)運(yùn)行速度很快。后來看到j(luò)ava中產(chǎn)生隨機(jī)數(shù)函數(shù)以及線程池中的一些函數(shù)使用的都是System.nanoTime,下面說一下這2個(gè)方法的具體區(qū)別。
System.nanoTime提供相對(duì)精確的計(jì)時(shí),但是不能用他來計(jì)算當(dāng)前日期
System.nanoTime與System.currentTimeMillis的區(qū)別
接下來利用UUID.randomUUID().toString()生成了一個(gè)唯一的key保存在retainedKeys集合中,而這個(gè)retainedKeys是一個(gè)Set數(shù)據(jù)類型。
// 用于判斷弱引用所持有的對(duì)象是否已被GC,如果被回收,會(huì)存在隊(duì)列中,反之,沒有存在隊(duì)列中則泄漏了
private final ReferenceQueue<Object> queue;
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
下面這個(gè)就是重點(diǎn)了,可以說是LeakCanary的核心,可以看到這里new了一個(gè)KeyedWeakReference對(duì)象,這里傳入了我們觀察的對(duì)象,也就是Activity,傳入了一個(gè)queue,而這個(gè)queue可以看到是一個(gè)ReferenceQueue。而這里KeyedWeakReference繼承了WeakReference,也就是我們熟知的弱引用,熟悉弱引用特性的應(yīng)該都知道,當(dāng)弱引用被回收的時(shí)候,會(huì)被放入一個(gè)隊(duì)列里,這里就是利用這個(gè)特性,使用弱引用持有一個(gè)Activity對(duì)象。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
接下來就開始了真正開始分析的過程了,可以看到這里使用了我們前面創(chuàng)建的HandlerThread這個(gè)異步線程進(jìn)行操作。
// 避免因?yàn)間c不及時(shí)帶來的誤判,leakcanay會(huì)手動(dòng)進(jìn)行g(shù)c,進(jìn)行二次確認(rèn)進(jìn)行保證
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//System.currentTimeMillis,那么每次的結(jié)果將會(huì)差別很小,甚至一樣,因?yàn)楝F(xiàn)代的計(jì)算機(jī)運(yùn)行速度很快
//檢測系統(tǒng)的耗時(shí)所用,所以使用System.nanoTime提供相對(duì)精確的計(jì)時(shí)
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//第一次判斷,移除此時(shí)已經(jīng)被回收的對(duì)象
removeWeaklyReachableReferences();
//調(diào)試的的時(shí)候是否開啟內(nèi)存泄漏判斷,默認(rèn)是false
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//如果此時(shí)該對(duì)象已經(jīng)不再retainedKeys中說明第一次判斷時(shí)該對(duì)象已經(jīng)被回收,不存在內(nèi)存泄漏
if (gone(reference)) {
return DONE;
}
//如果當(dāng)前檢測對(duì)象還沒有被回收,則手動(dòng)調(diào)用gc
gcTrigger.runGc();
//再次做一次判斷,移除被回收的對(duì)象
removeWeaklyReachableReferences();
if (!gone(reference)) {
//如果該對(duì)象仍然在retainedKey中,則說明內(nèi)存泄漏了,進(jìn)行分析
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// dump出來heap,此時(shí)認(rèn)為內(nèi)存確實(shí)已經(jīng)泄漏了
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//開始分析
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
這里最先看到了removeWeaklyReachableReferences這個(gè)方法,也就是在Activity執(zhí)行了onDestroy之后,執(zhí)行這個(gè)方法,進(jìn)行第一次判斷
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
//如果此時(shí)已經(jīng)在queue中,說明已經(jīng)被回收
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
//則從retainedKeys中移除
retainedKeys.remove(ref.key);
}
}
這里可以看到,遍歷了剛才傳入了的弱應(yīng)用隊(duì)列,如果弱引用隊(duì)列中存在引用,說明改對(duì)象已經(jīng)被回收,然后通過存儲(chǔ)的唯一性key,從retainedKeys中移除。
//如果此時(shí)該對(duì)象已經(jīng)不再retainedKeys中說明第一次判斷時(shí)該對(duì)象已經(jīng)被回收,不存在內(nèi)存泄漏
if (gone(reference)) {
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
//retainedKeys不存在該對(duì)象的key
return !retainedKeys.contains(reference.key);
}
執(zhí)行完第一次判斷后,這里就判斷retainedKeys中是否存在該對(duì)象的key,如果不存在,說明該對(duì)象已經(jīng)成功被GC回收,則表明這時(shí)是不存在內(nèi)存泄漏的,則直接return.
//如果當(dāng)前檢測對(duì)象還沒有被回收,則手動(dòng)調(diào)用gc
gcTrigger.runGc();
//再次做一次判斷,移除被回收的對(duì)象
removeWeaklyReachableReferences();
if (!gone(reference)) {
...
}
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 every time. Runtime.gc() is
// more likely to perfom a gc.
//這里用的是Runtime.getRuntime().gc()
//注意這里和System.gc()的區(qū)別
Runtime.getRuntime().gc();
//等待100毫秒
enqueueReferences();
System.runFinalization();
}
。。。
};
如果這時(shí)還存在retainedKeys說明可能存在內(nèi)存泄漏,熟悉GC的應(yīng)該都知道,GC的操作并不是實(shí)時(shí)的,所以第一次雖然該對(duì)象還沒有被回收,也可能是由于GC沒有觸發(fā)導(dǎo)致的,所以可以看到這里手動(dòng)觸發(fā)了GC操作,這里就要聯(lián)系到我們前面分析的Runtime.getRuntime().gc()。這樣就通了,這里手動(dòng)調(diào)用了Runtime.getRuntime().gc()方法,強(qiáng)制觸發(fā)GC。然后在執(zhí)行一次removeWeaklyReachableReferences();方法。再重復(fù)做一次判斷,弱引用是否被回收,存在于引用隊(duì)列中。
if (!gone(reference)) {
//如果該對(duì)象仍然在retainedKey中,則說明內(nèi)存泄漏了,進(jìn)行分析
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// dump出來heap,此時(shí)認(rèn)為內(nèi)存確實(shí)已經(jīng)泄漏了
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//開始分析
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
可以看到,當(dāng)強(qiáng)制GC后,進(jìn)行第二次判斷后,還是存在retainedKey中,這里就認(rèn)為產(chǎn)生了內(nèi)存泄漏,這時(shí)候就開始進(jìn)行分析,這里就利用了LeakCanary使用到的另一個(gè)庫Haha庫,用于分析引用路徑。首先這里的heapdumpListener的實(shí)現(xiàn)類就是我們前面提到的ServiceHeapDumpListener。
@Override protected HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
//開啟HeapAnalyzerService,是一個(gè)HandlerService
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
//開啟一個(gè)IntentService用于分析內(nèi)存泄漏
Intent intent = new Intent(context, HeapAnalyzerService.class);
//將回調(diào)的監(jiān)聽Service的class傳入,分析完成,回調(diào)到這個(gè)service
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
//收集的文件
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
這里我們就注意幾個(gè)關(guān)鍵點(diǎn)就行:
- 默認(rèn)創(chuàng)建的是
ServiceHeapDumpListener,傳入了DisplayLeakService.class類對(duì)象。- 執(zhí)行
analyze方法的實(shí)質(zhì)就是開啟HeapAnalyzerService這個(gè)Service,并且將收集的heapDump傳入用于分析。
@Override protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
//分析獲得結(jié)果,haha庫就在內(nèi)部調(diào)用的,注意分析
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
//回調(diào)結(jié)果
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
這時(shí)我們看一下HeapAnalyzerService的onHandleIntent方法,這里我們需要注意的就是heapAnalyzer.checkForLeak,這個(gè)方法就是LeakCanary內(nèi)部分析引用路徑的方法,內(nèi)部使用了Haha庫,當(dāng)然這個(gè)過程在一個(gè)IntentService中,當(dāng)然是異步的。
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
//啟動(dòng)Service通知,抽象類,DisplayLeakService
Intent intent = new Intent(context, listenerServiceClass);
//將分析的信息傳回給Service,發(fā)出內(nèi)存泄漏的通知
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}
當(dāng)分析完結(jié)果后,可以看到這里,利用反射,創(chuàng)建了我們之前傳入的DisplayLeakService對(duì)象,然后將分析接口發(fā)送給DisplayLeakService。
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
if (!shouldSaveResult) {
//無泄露
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
//獲得一個(gè)pendingIntent
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
//顯示一個(gè)通知,顯示內(nèi)存泄漏
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}
這里由于DisplayLeakService繼承了AbstractAnalysisResultService,而AbstractAnalysisResultService繼承了IntentService,最終會(huì)調(diào)onHeapAnalyzed方法,這里可以看到當(dāng)存在內(nèi)存泄漏的時(shí)候,會(huì)創(chuàng)建一個(gè)pendingIntent用于后面通知的點(diǎn)擊事件跳轉(zhuǎn),而后發(fā)送了一個(gè)通知notification。
LeakCanary.isInAnalyzerProcess(context);
這里我們?cè)賮砜匆幌虑懊嫣岬降?code>LeakCanary提供給我們的一個(gè)比較不錯(cuò)的工具類,用于判斷當(dāng)前進(jìn)程是否在后臺(tái)。
public static boolean isInAnalyzerProcess(Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// This only needs to be computed once per process.
if (isInAnalyzerProcess == null) {
//判斷進(jìn)程是否在后臺(tái),重要
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
return false;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
if (serviceInfo.processName.equals(mainProcess)) {
//如果服務(wù)進(jìn)程和主進(jìn)程是同一個(gè)進(jìn)程,那就不對(duì)了
CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
// Technically we are in the service process, but we're not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> runningProcesses;
try {
runningProcesses = activityManager.getRunningAppProcesses();
} catch (SecurityException exception) {
// https://github.com/square/leakcanary/issues/948
CanaryLog.d("Could not get running app processes %d", exception);
return false;
}
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
//獲取當(dāng)前正在前臺(tái)對(duì)進(jìn)程
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d("Could not find running process for %d", myPid);
return false;
}
return myProcess.processName.equals(serviceInfo.processName);
}
思路還是比較清晰的,遍歷當(dāng)前的所有運(yùn)行中的進(jìn)程,獲得當(dāng)前運(yùn)行的主進(jìn)程,然后和對(duì)應(yīng)的進(jìn)程名稱做對(duì)比。
總結(jié)
到此LeakCanary的整個(gè)流程已經(jīng)走完了,可能寫的比較瑣碎,但是大體流程還是比較清晰的。
1.通過Application監(jiān)聽Activity的生命周期
2.在Activity的Destroy時(shí),進(jìn)行內(nèi)存泄漏分析。
3.利用弱應(yīng)用的特性使用一個(gè)引用隊(duì)列保存Activity的引用,如果onDestroy后引用隊(duì)列中存在該Activity的實(shí)例則說明成功回收。
4.若不存在,則手動(dòng)利用Runtime.getRuntime().gc()方法手動(dòng)觸發(fā)GC,執(zhí)行完后再進(jìn)行一次判斷。
5.若此時(shí)還沒有在隊(duì)列中存在,說明沒有被回收,則認(rèn)定此時(shí)發(fā)生內(nèi)存泄漏。
6.異步執(zhí)行Haha庫進(jìn)行引用鏈分析,然后通知Service發(fā)出通知。