好的項目離不開健壯的代碼,對於想要寫出健壯的代碼,解決內存泄漏是必須的。app
對於LeakCanary,對於大多人是不陌生的,也就是檢測內存泄漏的工具。可能在代碼中咱們會這樣引入LeakCanary:dom
//檢查leakCanary和APP是否在同一個進程,若是是同一個進程就返回,不在同一個進程,就註冊。
//由於再也不同一個進程,不會對APP進程形成消極影響(如:APP進程變慢或者out of memory)
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);ide
簡簡單單的兩句話,背後的原理倒是一大堆。
在開始源碼以前,先說幾個知識點:工具
強引用,軟引用,弱引用,GC線程掃描它所管轄的內存區域時,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。
垃圾回收器是一個優先級很低的線程,即便有弱引用的存在,也不必定會執行。
咱們手動調用GC,不必定成功調用垃圾回收器,由於咱們僅僅是建議JVM執行GC,最終執不執行,仍是得看JVM的最終決策。
LeakCanary是在主進程進行內存檢測,而且是在主進程空閒的時候進行的。 這就是爲何會出現咱們關閉Activity了,過了一段時間,才顯示內存泄漏了。
Android4.0以後,新加了ActivityLifecycleCallbacks,做用就是,同一進程下,每一個Activity的的生命週期都會進入到ActivityLifecycleCallbacks回調的相應方法中。ui
以上知識點是咱們須要知道的。接下來看源碼:this
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}.net
方法返回的是RefWatcher ,RefWatcher 是分析內存泄露的核心類,裏面提供了相應的方法用戶分析內存泄漏。其中refWatcher(application)返回的是AndroidRefWatcherBuilder對象,listenerServiceClass方法裏面的DisplayLeakService參數是:通知給開發者顯示內存泄漏的詳細信息,其繼承自IntentService, 不理解的能夠看這個:
Android進階2:線程和線程池(3)—— IntentService原理解析
而後就是鏈式調用了excludedRefs方法,做用是忽略系統底層的內存泄漏,也就是說不是咱們開發者形成的內存泄漏。最後調用了buildAndInstall方法,這個方法最終返回的是RefWatcher對象:線程
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}debug
執行ActivityRefWatcher.install,看下:對象
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
調用了watchActivities(), 繼續深究:
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
//給Application註冊監聽事件;
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
到這裏咱們就明白了原來就是LeakCanary內部實際上用的也就是ActivityLifecycleCallbacks,那麼,想一下lifecycleCallbacks對象的實現,既然咱們須要檢測內存泄漏,那麼確定是在Activity ‘銷燬’ 以後,纔可以檢測的,那麼名義上的銷燬,調用的方法也就是onActivityDestroyed,因此咱們猜測LeakCanary的檢測內存泄漏的邏輯是寫在onActivityDestroyed中的。
接下來看下源碼,驗證一下:
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);
}
};
經過源碼看,和咱們的猜測同樣的,檢測內存泄漏的邏輯確實是在onActivityDestroyed方法中的。
繼續,看下onActivityDestroyed方法,注意傳遞的參數是當前銷燬的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中
retainedKeys.add(key);
//將Activity和惟一標識以及ReferenceQueue做爲參數,生成弱引用KeyedWeakReference
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//開始內存泄漏檢測
ensureGoneAsync(watchStartNanoTime, reference);
}
watchedReference 也就是咱們傳入的Activity對象,上面源碼主要是先生成了惟一標識key,而後將Activity和惟一標識以及ReferenceQueue做爲參數生成弱引用對象reference 。ReferenceQueue的做用是:在適當的時候檢測到對象的可達性發生改變後,垃圾回收器就將已註冊的引用對象添加到此隊列中。後面代碼裏使用一個弱引用鏈接到你須要檢測的對象,而後使用ReferenceQueue來監測這個弱引用可達性的改變;說白了ReferenceQueue就是一個弱引用隊列。而後垃圾回收器會自動的回收此隊列中的對象。KeyedWeakReference 本質也就是將咱們的Activity封裝了一層,變成了惟一的一個弱引用對象。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
//開啓子線程進行內存分析
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
能夠看出,實際上LeakCanary分析內存是在子線程中的。
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//刪除弱引用
removeWeaklyReachableReferences();
//是否處於debugger狀態,若是是就返回
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 看是否此弱引用對象還存在
if (gone(reference)) {
//若是不存在,就返回,代表沒有內存泄漏
return DONE;
}
//若是還存在,就手動調用GC,
gcTrigger.runGc();
//再次刪除弱引用
removeWeaklyReachableReferences();
//再次判斷此弱引用對象是否存在
if (!gone(reference)) {
//若是還存在,就分析Dump內存快照。
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);
//分析analyze方法;
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
從上述代碼能夠看出,LeakCanary檢測是否內存泄漏的步驟是:
先刪除弱引用
判斷弱引用是否還存在,若是不存在,說明已經沒有內存泄漏;若是存在,就執行3步驟。
手動調用GC ,而後再次刪除弱引用
再次判斷弱引用是否還存在,若是不存在,就代表沒有內存泄漏;若是存在,就開始深層次的分析,這裏仍是疑似內存泄漏,由於手動觸發GC,僅僅是建議JVM觸發垃圾回收,真正執不執行仍是得看JVM。
注意這裏判斷弱引用是否存在的標識就是:上述源碼生成的惟一標識key。
最後若是進行到第四步驟,也就是深度分析,此時升讀分析的也就是Hprof文件。 這裏的heapdumpListener也就是ServiceHeapDumpListener對象。關於爲何是ServiceHeapDumpListener對象,能夠看下上面源碼的install方法內的listenerServiceClass方法:
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
繼續深刻:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
//單獨開一個進程,開始深度分析
//HeapAnalyzerService類的註釋:此服務在單獨的進程中運行,以免放慢應用程序進程或使其運行
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
也就是說heapdumpListener.analyze最終調用的也就是ServiceHeapDumpListener的analyze方法。HeapAnalyzerService繼承自IntentService, runAnalysis方法的內部作的也就是開啓Service(也就是它本身),那麼咱們知道對於IntentService,暴露了onHandleIntent抽象方法,共開發者處理相關的業務邏輯,因此看下onHandleIntent內部的代碼:
@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);
//而後調用checkForLeak方法分析,並返回分析結果
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
//
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
上述代碼流程是先建立了一個堆分析對象,而後開始分析,最後返回分析結果,並調用AbstractAnalysisResultService.sendResultToListener發送結果,展現給開發者。值得注意的是listenerClassName參數也就是上面源碼的DisplayLeakService對象,而後在AbstractAnalysisResultService的sendResultToListener方法內,經過反射獲取DisplayLeakService對象:
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
//經過反射獲取ServiceHeapDumpListener對象
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}
經過反射獲取到DisplayLeakService對象,而後開啓本身,
@Override protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
//最終調用的仍是DisplayLeakService的方法。
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
看下DisplayLeakService的onHeapAnalyzed方法:
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
.......
//展現通知
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}
至此LeakCanary流程分析大體完成了。
接下來講下開發中對於ActivityLifecycleCallbacks的使用,在實際開發中,經常會須要埋點,咱們項目中用的是友盟,後來公司運營須要一套本身的經營分析系統,也就是說友盟一套埋點數據,咱們本身一套埋點數據,埋點需求:點擊,頁面停留時長等,這時候使用ActivityLifecycleCallbacks監聽Activity的狀態,就很是方便了。