“A small leak will sink a great ship.” - Benjamin Franklin
千里之堤, 毀于蟻穴。 -- 《韓非子·喻老》
LeakCanary是什么?可以從LeakCanary的github很容易的得到定義:
Android|Java的內存檢測庫
更多使用方法:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
簡單集成
在的build.gradle
:
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'
}
在你的Application
class:
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);
// Normal app init code...
}
}
然后什么都不用做,當出現內存泄漏的時候LeakCanary就會在你的通知欄發送一條,點進去就是泄漏日志。
Dump泄漏的hprof文件
從初始化入口LeakCanary.install(this)
可以很清楚的看到得到了一個AndroidRefWatcherBuilder
對象
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
而這個對象extends RefWatcherBuilder
進入RefWatcherBuilder
可以看出這是一個采用構造者設計模式所以直接進入build方法進行查看
/** Creates a {@link RefWatcher}. */
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
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 = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
HeapDumper
可以從名字我們可以猜測用于dump hprof文件的、一樣的我們點進去這個類發現是一個接口,所以找到他的實現類AndroidHeapDumper
@Override public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
稍微提及一下LeakDirectoryProvider
這個類主要用于初始化SD卡存放LeakCanary生成的文件目錄,大概位置于/sdcard/download/leakcanary-你的包名
回到我們的AndroidHeapDumper
類、我們只需要關心一行代碼
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
采用Android自帶的Debug
來來doump hprof文件然后保存到LeakDirectoryProvider
初始化的文件目錄下
什么時候Dump
回到LeakCanary
類中的buildAndInstall
方法
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
主要做兩步
第一步:enableDisplayLeakActivity
進入這個方法把DisplayLeakActivity
這個Activity組件恢復正常使用
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);
}
第二步也是主要步驟進入ActivityRefWatcher
類初始化Application.ActivityLifecycleCallbacks
監聽
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執行onDestory的時候進行監聽它
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
自然而然我們需要進入RefWatcher.watch
這個方法去查看具體是怎么分析
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);
}
很巧妙的使用KeyedWeakReference
弱引用,為了確保弱引用能被回收“偷偷”的跑了Runtime.getRuntime().gc()
來實現對弱引用的回收
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);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
我們再watch方法中每次都會把當前需要檢測的對象或者說是Activity組件加入Set<String> retainedKeys
這個Set
容器中,然后系統就會調用gcTrigger.runGc()
來回收KeyedWeakReference
弱引用
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();
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();
}
}
};
那怎么確定這個Activity發生泄漏了呢?
首先我們需要明白一個道理,每次初始化傳入了一個ReferenceQueue
這個隊列是用來存放每當當前的弱引用被GC回收了,那么當前這個弱引用對象就會被存入到這個Queue中去
,所以每次只要能從retainedKeys
把當前的KeyedWeakReference
弱引用對應的key移除那么就證明沒有發生泄漏,而當泄漏的話queue
就沒有供retainedKeys
移除的key值
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;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
可以從這兩個方法很好的看出來就是這么確認是否發生了泄漏。
一旦發生了泄漏就開始Dump 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);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
如何解析hprof
當發生了泄漏就會生成HeapDump
對象然后就會進入下面這個方法去啟動HeapAnalyzerService
Service來進行分析
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
然后這邊想一提帶過就好,因為這邊使用的是Square haha
庫來進行解析dump下來的hprof文件
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
想要了解更多可以去查看haha更多的使用方法。
經過解析之后機會把數據傳遞到DisplayLeakService
這是個Service會根據傳入進來的數據發送通知欄通知,然后并存入數據,當你點擊對應的通知進入DisplayLeakActivity
界面就能顯示泄漏日志了。
差不多這就是LeakCanary
如何監聽泄漏、Dump泄漏、分析泄漏、顯示泄漏的主要流程了,當然更重要的是發生了泄漏要懂得修復才是硬道理。