在分析Leak Canary原理以前,咱們先來簡單瞭解WeakReference和ReferenceQueue的做用,爲何要了解這些知識呢?Leak Canary其實內部就是使用這個機制來監控對象是否被回收了,固然Leak Canary的監控僅僅針對Activity和Fragment,因此這塊有引入了ActivityLifecycleCallBack,後面會說,這裏的回收是指JVM在合適的時間觸發GC,並將回收的WeakReference對象放入與之關聯的ReferenceQueue中表示GC回收了該對象,Leak Canary經過上賣弄的檢測返現有些對象的生命週期本該已經結束了,可是任然在佔用內存,這時候就斷定是已經泄露了,那麼下一步就是開始解析析headump文件,分析引用鏈,至此就結束了,其中須要注意的是這是WeakReference.get方法獲取到的對象是null,因此Leak Canary使用了繼承WeakReference.類,並把傳入的對象做爲成員變量保存起來,這樣當GC發生時雖然把WeakReference中引用的對象置爲了null也不會把WeakReference中咱們拓展的類的成員變量置爲null,這樣咱們就能夠作其餘的操做,好比:Leak Canary中把WeakReference存放在Set集合中,在恰當的時候須要移除Set中的WeakReference的引用,這個機制Glide中的內存緩存 也是使用了該機制,關於WeakReference和ReferenceQueue機制就很少說網上有不少能夠了解一下。java
/**
* 監控對象被回收,由於若是被回收就會就如與之關聯的隊列中
*/
private void monitorClearedResources() {
Log.e("tag", "start monitor");
try {
int n = 0;
WeakReference k;
while ((k = (WeakReference) referenceQueue.remove()) != null) {
Log.e("tag", (++n) + "回收了:" + k + " object: " + k.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private ReferenceQueue<WeakRefrencesBean> referenceQueue = new ReferenceQueue<>();
class WeakRefrencesBean {
private String name;
public WeakRefrencesBean(String name) {
this.name = name;
}
}
new Thread() {
@Override
public void run() {
monitorClearedResources();
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
new WeakReference<WeakRefrencesBean>(new WeakRefrencesBean("aaaa"), referenceQueue);
}
}
}.start();
複製代碼
輸出的日誌:android
1回收了:java.lang.ref.WeakReference@21f8376e object: null
2回收了:java.lang.ref.WeakReference@24a74e0f object: null
3回收了:java.lang.ref.WeakReference@39efe9c object: null
4回收了:java.lang.ref.WeakReference@4ee20a5 object: null
3回收了:java.lang.ref.WeakReference@bf45c7a object: null
4回收了:java.lang.ref.WeakReference@b94bc2b object: null
5回收了:java.lang.ref.WeakReference@33eb6888 object: null
複製代碼
上面是一個監控對象回收,由於若是對象被回收就把該對象加入如與之關聯的隊列中,接着開啓線程製造觸發GC,並開啓線程監控對象回收,Leak Canary也是利用這個機制完成對一些對象本該生命週期已經結束,還常駐內存,就算觸發GC也不會回收,Leak Canary就判斷爲泄漏,針對於內存泄漏,咱們知道有些對象是不能被GC回收的,JVM虛擬機的回收就是可達性算法,就是從GC Root開始檢測,若是不可達那麼就會被第一次標誌,再次GC就會被回收。git
步驟無非就是: 一、安裝,實際上就是作一些初始化的操做; 二、檢測時機,好比:回調onActivityDestroyed方法開始檢測; 三、UI的展現;github
Leak Canary的地方就是 LeakCanary.install(this)方法開始,代碼以下:算法
通常咱們使用Leak Canaryu都是在Application中調用:緩存
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
setupLeakCanary();
}
protected void setupLeakCanary() {
enabledStrictMode();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
...
}
複製代碼
在install方法以前有個判斷,這個判斷是用來判斷是不是在LeakCanary的堆統計進程(HeapAnalyzerService),也就是咱們不能在咱們的App進程中初始化LeakCanary,代碼以下:app
/**
* 當前進程是不是運行{@link HeapAnalyzerService}的進程中,這是一個與普通應用程序進程不一樣的進程。
*/
public static boolean isInAnalyzerProcess(@NonNull Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
// 這裏只須要爲每一個進程計算一次。
if (isInAnalyzerProcess == null) {
//把Context和HeapAnalyzerService服務做爲參數傳進isInServiceProcess方法中
isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
複製代碼
在isInAnalyzerProcess方法中有調用了isInServiceProcess方法,代碼以下:dom
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, PackageManager.GET_DISABLED_COMPONENTS);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
//判斷當前HeapAnalyzerService服務進程名和主進程名是否相等,若是相等直接返回false,由於LeakCanary不能再當前進程中運行
if (serviceInfo.processName.equals(mainProcess)) {
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) {
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);
}
複製代碼
實際上LeakCanary最終會調用LeakCanaryInternals.isInServiceProcess方法,經過PackageManager、ActivityManager以及android.os.Process來判斷當前進程是否爲HeapAnalyzerService運行的進程,由於咱們不能在咱們的App進程中初始化LeakCanary。ide
接下來咱們開始LeakCanary真正的實現,從LeakCanary.install(this)方法開始,代碼以下:ui
public static RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
複製代碼
實際上這一步返回的RefWatcher的實現類AndroidRefWatcher,主要是作些關乎初始化的操做,這些不展開講,直接進入buildAndInstall()方法中,代碼以下:
public RefWatcher buildAndInstall() {
//install()方法只能一次調用,屢次調用將拋出異常
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//初始化RefWatcher,這個東西是用來檢查內存泄漏的,包括解析堆轉儲文件這些東西
RefWatcher refWatcher = build();
//若是RefWatcher尚未初始化,就會進入這個分支
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
//setEnabledAsync最終調用了packageManager.setComponentEnabledSetting,
// 將Activity組件設置爲可用,即在manifest中enable屬性。
// 也就是說,當咱們運行LeakCanary.install(this)的時候,LeakCanary的icon才顯示出來
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//ActivityRefWatcher.install和FragmentRefWatcher.Helper.install的功能差很少,註冊了生命週期監聽。
// 不一樣的是,前者用application監聽Activity的生命週期,後者用Activity監聽也就是Activity回調onActivityCreated方法,
// 而後獲取FragmentManager調用registerFragmentLifecycleCallbacks方法註冊,監聽fragment的生命週期,
// 並且用到了leakcanary-support-fragment包,兼容了v4的fragment。
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
複製代碼
在buildAndInstall方法中有幾點:
RefWatcher類是用來監控對象的引用是否可達,當引用變成不可達,那麼就會觸發堆轉儲(HeapDumper),來看看RefWatcherBuilder.build方法的具體實現,代碼以下:
public final RefWatcher build() {
//若是已經初始化了,直接返回RefWatcher.DISABLED表示已經初始化了
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.NONE,HeapDumper內部實現類,
heapDumper = defaultHeapDumper();
}
//建立監控線程池
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
//默認返回 NONE
watchExecutor = defaultWatchExecutor();
}
//默認的Gc觸發器
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);
}
複製代碼
如上代碼知道,其實是爲了建立RefWatcher實例,和一些在檢測中的環境初始化,好比線程池、GC觸發器等等。
回到最初的biuldInstall方法中,知道監控Activity和Fragment是查不到的因此這裏就只分析Activity相關的,也就是ActivityRefWatcher.install方法,代碼以下:
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//註冊ActivityLifecycleCallbacks監聽每個Activity的生命週期
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
複製代碼
能夠知道這裏是使用的裝飾模式,使用ActivityRefWatcher對RefWatcher作了包裝,接着註冊ActivityLifecycleCallbacks監聽每個Activity的生命週期的onActivityDestroyed方法,這也就是檢測泄漏開始的地方,而在onActivityDestroyed方法方法中會調用refWatcher.watch方法把activity做爲參數傳進去,代碼以下:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
//當Activity被銷燬了,那麼應該檢測是否內存泄漏
refWatcher.watch(activity);
}
};
複製代碼
能夠看到在Activity銷燬時會回調onActivityDestroyed方法,而後把該activity做爲參數傳遞給refWatcher.watch(activity)方法,watch方法代碼以下:
public void watch(Object watchedReference, String referenceName) {
..........
final long watchStartNanoTime = System.nanoTime();
//給該引用生成UUID
String key = UUID.randomUUID().toString();
//給該引用的UUID保存至Set中,強引用
retainedKeys.add(key);
//KeyedWeakReference 繼承至WeakReference,因爲KeyedWeakReference若是回收了,那麼當中的對象經過get返回的是null,
// 因此須要保存key和name做爲標識,Glide也是此作法,KeyedWeakReference實現WeakReference
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//開始檢測
ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼
在watch方法中有以下幾點:
ensureGoneAsync方法代碼以下:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
複製代碼
在ensureGoneAsync方法中直接執行線程池(AndroidWatchExecutor),而這個線程池就是在剛開始的時候LeakCanary.install方法中建立RefWatcher的子類AndroidRefWatcher的時候建立的,接着看看ensureGone方法,代碼以下:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//gc 開始的時間
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//從Set中移除不能訪問引用,意思就是GC以後該引用對象是否加入隊列了,若是已經加入隊列說明不會形成泄漏的風險
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
//嘗試GC
gcTrigger.runGc();
//從Set中移除不能訪問引用,意思就是GC以後該引用對象是否加入隊列了,若是已經加入隊列說明不會形成泄漏的風險
removeWeaklyReachableReferences();
//到這一步說明該對象按理來講聲明週期是已經結束了的,可是經過前面的GC卻不能回收,說明已經形成了內存泄漏,那麼解析hprof文件,獲得該對象的引用鏈,也就是要觸發堆轉儲
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;
}
複製代碼
在ensureGone方法中有以下幾點:
在前面說不少檢測GC回收是怎麼作到的呢,接下來看看removeWeaklyReachableReferences方法,代碼以下:
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.
//WeakReferences會在它們指向的對象變得沒法訪問時排隊。 這是在完成或垃圾收集實際發生以前。
//隊列不爲null,說明該對象被收了,加入此隊列
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
複製代碼
這裏直接使用一個while循環從隊列取出元素進行判斷,這裏的queue.poll()是不會阻塞的,因此爲何LeakCanary會作兩次驗證的緣由,爲何LeakCanary不使用queue.remove()方法呢?你想一想queue.remove()方法是阻塞當前線程,從前面知道每次Activity或者Fragment銷燬回調生命週期方法都會建立一個KeyedWeakReference實例,也就是說若是不泄露就一直阻塞當前線程,這樣反而對形成沒必要要的開銷,我也是猜猜而已。