LeakCanary 是什麼?
LeakCanary是Square公司基於MAT開源的一個工具,用來檢測Android App中的內存泄露問題。官方地址:https://github.com/square/leakcanary
工作原理分析:
1.怎麼檢測內存泄漏?什麼時候去檢測內存泄漏?
//註冊生命週期 在onDestory 的時候 觀察Activity 對象
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
//開始觀察
refWatcher.watch(activity);
}
};
// 此方法爲停止觀察activity 也就是不在監聽回掉
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
自己去跟源碼就會發現,LeakCanary 會 註冊一個ActivityLifecycleCallbacks ,當監聽到activity Ondestory 的時候,就會去觀察這個activity 是不是泄漏了,有沒有被GC回收。
具體怎麼觀察Activity 有沒有被回收?
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
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;
}
我們看到,觀察activity 就是 把 activity 放到WeakRefence 裏面,WeakReference 的第二個參數是一個Qeue 對象,我們手動執行一下GC,然後Qeue.pull 一下,看看Qeue 裏面是不是有WeakRefence ,如果有說明對象被回收了。那麼這個activity 就沒有泄漏,否則就有可能泄露了導致回收不了。
這個機制是WeakReference 的機制。有興趣可以自行了解一下。
怎麼執行GC?
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
上面diam執行之後,我們再看下內存,還有沒有activity 的實例,如果還有,那麼說明這個activity 可能被泄露了。我們需要根據hprof 文件看下具體的調用鏈。
怎麼生成hprof?
如果activity 內存泄露了,我們需要hprof 文件去分析。那麼怎麼拿到hprof?
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
怎麼分析hprof
使用了下面的開源項目:
implementation 'com.squareup.haha:haha:2.0.4'
分析完之後,用界面顯示出來就沒有什麼好說了。
怎麼在桌面有兩個圖標?
<activity
android:label="@string/app_name"
android:name=".MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<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>
我們發現,leackCanary會生成一個自己的桌面圖標,點擊進去可以查看內漏泄露的信息,而且不需要創建桌面快捷方式的權限,就可以做到,他是用上面的方式做到的。清單裏面可以有多個"android.intent.category.LAUNCHER",當然也會有一個默認的。
怎麼設置組件啓用和關閉
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);
}
也就這樣啦,沒有具體怎麼分析hprof,有興趣可以再深入研究了。