Android LeakCanary源碼解析

1 Java和C/C++語言的內存泄漏

在C/C++語言開發過程中,比如C語言 malloc 分配內存 free 釋放內存,C++是 new Object 分配內存 delete object 釋放內存,對象的內存分配回收都需要程序員下意識的去維護,否則很容易出現內存泄漏。

但在Java中卻沒有這種情況,我們使用Java開發很多時候只需要 new Object 創建對象,使用對象後的無效內存都讓GC回收了。但也存在GC無法回收的情況導致JVM內存泄漏:長週期的對象引用一直持有着短週期的對象引用,GC以爲短週期對象引用還被使用,GC沒有回收,所以導致了內存泄漏。

那麼,Java是怎麼判斷對象的引用還被使用?Java垃圾回收機制是怎樣的?

2 垃圾回收機制

2.1 引用計數法

引用計數法 簡單理解就是記錄一個對象被引用的次數,一個引用被使用引用計數器就+1,反之就-1,當引用次數爲0就說明是一個垃圾對象可以被回收了。

引用計數法實現非常簡單,但也有存在的問題:循環引用,即對象a和對象b各自持有雙方的引用,導致GC無法回收,也就導致內存泄漏。

在這裏插入圖片描述

2.2 可達性分析法

可達性分析法 根據是否被GC Root引用確認是否是垃圾對象要被GC回收。

可以作爲GC Root的對象有:

  • 在線程棧中的局部變量(即正在被調用的方法裏面的參數和局部變量)

  • 存活的線程對象

  • JNI的引用

  • Class對象(在Android中Class被加載後是不會被卸載的)

  • 引用類型的靜態變量

在這裏插入圖片描述

3 LeakCanary 1.6.x(1.6.3)

3.1 LeakCanary.install()

3.1.1 LeakCanary.install()源碼分析

LeakCanary 是內存泄漏檢測工具,在使用上一句代碼即可(使用LeakCanary版本爲1.6.3):

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // LeakCanary是在另外一個進程中啓動
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return;
        }
        LeakCanary.install(this);
    }
}

通過一句代碼 LeakCanary.install(this) 就可以實現監聽內存泄漏。具體看下原理:

LeakCanary.java

public final class LeanCanary {
	public static @NonNull RefWatcher install(@NonNull Application application) {
		return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
			.excludeRefs(AndroidExcludedRefs.createAppDefaults().build()
			.buildAndInstall();
	}

	public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
		return new AndroidRefWatcherBuilder(context);
	}
}

AndroidRefWatcherBuilder.java

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
	public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
		@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
		enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
		return headDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
	}
}

上面的代碼有幾點需要說明:

  • DisplayService 是發生內存泄漏時的通知服務

  • excludedRefs() 是排除Android源碼出現的內存泄漏問題

最主要的是 AndroidRefWatcherBuilder.buildAndInstall()

AndroidRefWatcherBuilder.java

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
	private boolean watchActivities = true;
	private boolean watchFragments = true;

	public @NonNull RefWatcher buildAndInstall() {
		if (LeakCanaryInternals.installedRefWatcher != null) {
      		throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    	}
    	RefWatcher refWatcher = build();
    	if (refWatcher != DISABLED) {
    		// 根據app包名生成LeakCanary關聯應用
    		if (enableDisplayLeakActivity) {
				LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
			}
			// 監聽Activity
			if (watchActivities) {
				ActivityRefWatcher.install(context, refWatcher);
			}
			// 監聽Fragment
			if (watchFragments) {
				FragmentRefWatcher.Helper.install(context, refWatcher);
			}
		}
		LeakCanaryInternals.installRefWatcher = refWatcher;
		return refWatcher;
	}
} 

LeakCanaryInternals.java

public final class LeakCanaryInternals {
	public static void setEnabledAsync(Context context, final Class<?> componentClass, final boolean enabled) {
		final Context appContext = context.getApplicationContext();
		// 開啓子線程處理
		AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnnable() {
			@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);
	}
}

ActivityRefWatcher.java

public final class ActivityRefWatcher {
	public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
		Application application = (Application) context.getApplicationContext();
		ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

		// 最終是通過在Application註冊監聽每個Activity的生命週期,然後轉發給RefWatcher
		application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
	}

	private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = 
		new ActivityLifecycleCallbacksAdapter() {
			@Override public void onActivityDestroyed(Activity activity) {
				refWatcher.watch(activity);
			}
		}
}

FragmentRefWatcher.java

public interface FragmentRefWatcher {

	final class Helper {
		public static void install(Context context, RefWatcher refWatcher) {
			List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

			if (SDK_INT >= O) {
				fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
			}

			try {
				Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
				Constructor<?> constructor = fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
				FragmentRefWatcher supportFragmentRefWatcher = 
					(FragmentRefWatcher) constructor.newInstance(refWatcher);
				fragmentRefWatchers.add(supportFragmentRefWatcher);
			} catch (Exception ignored) {
			}

			if (fragmentRefWatchers.size() == 0) {
				return;
			}

			Helper helper = new Helper(fragmentRefWatcher);

			// 和Activity一樣也是監聽Fragment生命週期轉發給RefWatcher
			Application application = (Application) context.getApplication();
			application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
		}

		private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = 
			new ActivityLifecycleCallbacksAdapter() {
			@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
				for (FragmentRefWatcher watcher : fragmentRefWatchers) {
					watcher.watchFragments(activity);
				}
			}
		}
	}
}

AndroidOFragmentRefWatcher.java

class AndroidOFragmentRefWatcher implements FragmentRefWatcher {
	private final RefWatcher refWatcher;

	AndroidOFragmentRefWatcher(RefWatcher refWatcher) {
		this.refWatcher = refWatcher;
	}

	private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks = 
		new FragmentManager.FragmentLifecycleCallbacks() {
			@Override publci void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
				View view = fragment.getView();
				if (view != null) {
					refWatcher.watch(view);
				}
			}

			@Override
			public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
				refWatcher.watch(fragment);
			}
		}

	@Override public void watchFragments(Activity activity) {
		FragmentManager fragmentManager = activity.getFragmentManager();
		fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
	}
}

上面的處理可以用兩個步驟說明:

  • 根據app的包名生成 LeakCanary 的關聯應用

  • 通過 Application.registerActivityLifecycleCallbacks() 監聽Activity以及 FragmentManager.registerFragmentLifecycleCallbacks() 監聽Fragment的生命週期並轉發給對應 RefWatcher 處理

這一步生命週期監聽處理簡單理解就是:

final RefWatcher refWatcher = xxxx;

// 監聽Activity生命週期
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
	@Override
	public void onActivityDestroyed(Activity activity) {
		watcher.watch(activity);
	}
});

// 監聽Fragment生命週期
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        FragmentManager fm = activity.getFragmentManager();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            fm.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
                @Override
                public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
                    View view = f.getView();
                    if (view != null) {
                        watcher.watch(view);
                    }
                }

                @Override
                public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
                    watcher.watch(f);
                }
            }, true);
        }
    }
});

3.1.2 LeakCanary.install()小結

總結一下我們調用 LeakCanary.install() 的處理:

  • 創建 AndroidRefWatcherBuilder 構建 LeakCanary 所需參數,如提供 DisplayLeakService 內存泄漏通知服務,排除Android系統源碼出現的內存泄漏

  • 創建 RefWatcher,關聯app包名生成 LeakCanary 應用,通過 registerXxxLifecycleCallbacks() 監聽生命週期轉發給 RefWatcher 處理

3.2 RefWatcher.watch()

3.2.1 RefWatcher.watch()源碼分析

RefWatcher.java

public final class RefWatcher {
	public void watch(Object watchedReference) {
		watch(watchReference, "");
	}
	
	public void watch(Object watchReference, String referenceName) {
		if (this == DISABLED) {
			return;
		}
		final long watchStartNanoTime = System.nanoTime();
		// 爲監聽引用watchReference(Activity或Fragment)生成唯一ID
		String key = UUID.randomUUID().toString();
		retainedKeys.add(key);
		// watchReference就是監聽的引用對象
		final KeyedWeakReference reference = 
			new KeyedWeakReference(watchReference, key, referenceName, queue);
		ensureGoneAsync(watchStartNanoTime, reference);
	}

	private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
		watchExecutor.execute(new Retryable() {
			@Override public Retryable.Result run() {
				return ensureGone(reference, watchStartNanoTime);
			}
		});
	}
}

上面的代碼有一個對象需要注意:KeyedWeakReference

LeakCanary 是通過 KeyedWeakReference 來確認 watchReference(Activity or Fragment) 是否已經被回收的。我們可以通過弱引用的 ReferenceQueue 確認隊列中是否有數據,如果有就說明 watchReference 被GC回收了。具體可以看我寫的一篇文章:Java的四種引用類型

還有 watchExecutor 又是什麼?這需要回到 RefWatcher 被構建時的代碼:

RefWatcherBuilder.java

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
	public final RefWatcher build() {
		...

		WatchExecutor watchExecutor = this.watchExecutor;
		if (watchExecutor == null) {
			// AndroidWatchExecutor
			watchExecutor = defaultWatchExecutor();
		}

		...

		return new RefWatcher(watchExecutor, ...);
	}
}

AndroidRefWatcherBuilder.java

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
	return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}

AndroidWatchExecutor.java

public final class AndroidWatchExecutor implements WatchExecutor {
	private final Handler mainHandler;
	private final hnadler backgroundHandler;
	private final long initialDelayMillis;
	private final long maxBackoffFactor;
	
	public AndroidWatchExecutor(long initialDelayMillis) {
		mainHandler = new Handler(Looper.getMainLooper());
		HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
		handlerThread.start();
		backgroundHandler = new Handler(handlerThread.getLooper());
		this.initialDelayMillis = initialDelayMillis;
		maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
	}

	@Override public void execute(@NonNull Retryable retryable) {
		// 第一次進入肯定是主線程,因爲RefWatcher是在主線程創建
		if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
			waitForIdle(retryable, 0);
		} else {
			postWaitForIdle(retryable, 0);
		}
	}

	private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
		mainHandler.post(new Runnable() {
			@Override public void run() {
				waitForIdle(retryable, failedAttempts);
			}
		});
	}

	private void waitForIdle(final Retryable retryable, final int failedAttempts) {
		// This needs to be called from the main thread
		// 保證在主線程處於空閒狀態時處理
		// 因爲只有主線程生命週期都走完後纔有可能正常垃圾回收
		Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
			@Override public boolean queueIdle() {
				postToBackgroundWithDelay(retryable, failedAttempts);
				return false; // 返回false表示只會執行一次,否則會一直執行
			}
		});
	}

	private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
		long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
		long delayMillis = initialDelayMillis * exponentialBackoffFactor;
		backgroundHandler.postDelay(new Runnable() {
			@Override public void run() {
				// 在子線程執行Retryable
				Retryable.Result result = retryable.run();
				if (result == RETRY) {
					postWaitForIdle(retryable, failedAttempts + 1);
				}
			}
		}, delayMillis);
	}
}

watchExecutorAndroidWatchExecutor,在 RefWatcher 創建的時候會在子線程執行 Retryable.run()

接下來繼續看 Retryable.run() 往後的操作:

public final class RefWatcher {
	Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
		long gcStartNanoTime = System.nanoTime();
		long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

		// 將GC掉的對象從內存泄漏的懷疑名單中移除
		removeWeaklyReachableReference();
	
		if (debuggerControl.isDebuggerAttached()) {
			// The debugger can create false leaks.
			return RETRY;
		}
		// 如果名單沒有內存泄漏的引用對象
		// 說明在某次GC已經回收對象,沒有內存泄漏,不需要處理
		if (gone(reference)) {
			return DONE;
		}
		// 執行一次GC
		gcTrigger.runGc();

		// 再檢查引用對象是否被回收
		removeWeaklyReachableReferences();
		// 引用對象沒有被回收還在懷疑名單,說明已經內存泄漏,dump
		if (!gone(reference)) {
			long startDumpHeap = System.nanoTime();
			long gcDurationMs = NANOSECONDS.toMills(startDumpHeap - gcStartNanoTime);

			// 創建dump文件,創建通知提示dump
			File heapDumpFile = heapDumper.dumpHeap();
			// dump文件創建失敗,重試
			if (heapDumpFile == RETRY_LATER) {
				// Could not dump the heap.
				return RETRY;
			}
			long heapDumpDurationMs = NANOSECONDS.toMills(System.nanoTime() - startDumpHeap);

			HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referencekey(reference.key)
				.referenceName(reference.name)
				.watchDurationMs(watchDurationMs)
				.gcDurationMs(gcDurationMs)
				.heapDumpDurationMs(heapDumpDurationMs)
				.build();

			// 分析dump文件
			heapdumpListener.analyze(heapDump);
		}
		return DONE;
	}

	private boolean gone(KeyedWeakReference reference) {
		return !retainedKeys.contains(reference.key);
	}

	private void removeWeaklyReachableReference() {
		// WeakReferences are enqueued as soon as the object to which they point to be 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);
		}
	}
}

ensureGone() 主要做的事情:將GC掉的對象從內存泄漏的懷疑名單中移除,如果沒有會執行GC後再檢查是否從懷疑名單中移除,如果沒有說明內存泄漏,創建dump文件,分析dump文件。

3.2.2 RefWatcher.watch()小結

總結一下 RefWatcher.watch() 的處理:

  • 爲每一個監聽引用對象(Activity或Fragment)提供唯一ID,在主線程空閒走完生命週期時通過 Retryable.run() 檢測內存泄漏

  • Retryable.run() 會調用 ensureGone() 檢查,如果引用對象仍沒有被GC回收仍在 ReferenceQueue 隊列說明內存泄漏,創建dump文件

3.3 HeapDumper.dumpHeap()

通過 HeapDumper.dumpHeap() dump文件,那它是怎麼dump文件的?繼續分析代碼:

AndroidHeapDumper.java

public final class AndroidHeapDumper implements HeapDumper {
	@Override @Nullable
	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;
		}

		// 創建通知提示dump文件,分析進度會在啓動前臺分析服務時更新
		Notification.Builder builder = new Notification.Builder(context)
			.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
		Notification notification = LeakCanaryInternals.buildNotification(context, builder);
		NotificationManager notificationManager = 
			(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
		int notificationId = (int) SystemClock.uptimeMillis();
		notificationManager.notify(notificationId, notification);

		Toast toast = waitingForToast.get();
		try {
			// 最終使用的是Android提供的工具dump數據到hprof文件
			Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
			cancelToast(toast);
			notificationManager.cancel(notificationId);
		} catch (Exception e) {
			CanaryLog.d(e, "Could not dump heap");
			// Abort heap dump
			return RETRY_LATER;
		}
	}
}

DefaultLeakDirectoryProvider.java

public final class DefaultLeakDirectoryProvider implements LeakDirectoryProvider {
	// 最多7個dump文件
	private static final int DEFAULT_MAX_STORED_HEAP_DUMPS = 7;

	private static final String HPROF_SUFFIX = ".hprof";
	private static final String PENDING_HEAPDUMP_SUFFIX = "_pending" + HPROF_SUFFIX;

	private final int maxStoredHeapDumps;

	public DefaultLeakDirectoryProvider(@NonNull Context context) {
		this(context, DEFAULT_MAX_STORED_HEAP_DUMPS);
	}

	public DefaultLeakDirectoryProvider(@NonNoll Context context, int maxStoredHeapDumps) {
		if (maxStoredHeapDumps < 1) {
			throw new IllegalArgumentException("maxStoredHeapDumps must be at leasts 1");
		}
		this.context = context.getApplicationContext();
		this.maxStoredHeapDumps = maxStoredHeapDumps;
	}

	@Override public @Nullable File newHeapDumpFile() {
		// 從外部存儲查找對應的後綴dump文件
		List<File> pendingHeapDumps = listFiles(new FilenameFilter() {
			@Override public boolean accept(File dir, String filename) {
				return filename.endsWith(PENDING_HEAPDUMP_SUFFIX);
			}
		});
		
		...
		
		// 查找到hprof後綴的dump文件,如果dump文件多於7個,刪除修改時間較前的hprof文件
		cleanupOldHeapDumps();

		// 檢查外部存儲權限,如果沒有存儲權限LeakCanary無法dump文件
		File storageDirectory = externalStorageDirectory();
		if (!directoryWritableAfterMkdirs(storageDirectory)) {
			if (!hasStoragePermission()) {
				CanaryLog.d("WRITE_EXTERNAL_STORAGE permission not granted");
				requestWritePermissionNotification();
			} else {
				String state = Environment.getExternalStorageState();
				if (!Environment.MEDIA_MOUNTED.equals(state)) {
					CanaryLog.d("External storage not mounted. state: %s", state);
				} else {
					CanaryLog.d("Could not create heap dump directory in external storage: [%s]", storageDirectory.getAbsolutePath());
				}
			}
		}
		
		...
		
		return new File(storageDirectory, UUID.randomUUID().toString() + PENDING_HEAPDUMP_SUFFIX);
	}
}

排除權限和dump文件檢查外,最主要的是一句代碼:Debug.dumpHprofData(),最終是由Android的工具幫助生成hprof文件。

3.4 HeapDumpListener.analyze()

ServiceHeapDumpListener.java

public final class ServiceHeapDumpListener implements HeapDump.Listener {
	@Override public void analyze(@NonNull HeapDump heapDump) {
    	checkNotNull(heapDump, "heapDump");
    	HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  	}
}

HeapAnalyzerService.java

// ForegroundService extends IntentService
public final class HeapAnalyzerService extends ForegroundService
    implements AnalyzerProgressListener {

	public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    	setEnabledBlocking(context, HeapAnalyzerService.class, true);
    	setEnabledBlocking(context, listenerServiceClass, true);
    	// 啓動一個前臺服務分析,跨進程通信Intent提供分析所需參數
    	Intent intent = new Intent(context, HeapAnalyzerService.class);
   	 	intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    	intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    	ContextCompat.startForegroundService(context, intent);
 	 }	

	// LeakCanary是在另一個進程的,所以這裏的分析也是在另一個進程
	@Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses);

		// 開始分析
    	AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        	heapDump.computeRetainedHeapSize);
        // 分析結果回調
    	AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  	}
}

HeapAnalyzer.java

public final class HeapAnalyzer {
	public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull String referenceKey,
      boolean computeRetainedSize) {
    	...
      	// False alarm, weak reference was cleared in between key check and heap dump.
      	if (leakingRef == null) {
        	String className = leakingRef.getClassObj().getClassName();
        	return noLeak(className, since(analysisStartNanoTime)); // 沒有內存泄漏
      	}
      	// 如果有內存泄漏,查找內存泄漏的引用路徑,最終的分析實現LeakCanary是使用haha庫實現
      	return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    	...
  	}
}

HeapDumpListener.analyze() 其實就是啓動了一個前臺服務在其他進程分析內存泄漏的引用路徑。

3.5 LeakCanary源碼分析總結

在應用啓動時,通過 LeakCanary.install() 監聽內存泄漏,LeakCanary的處理過程如下:

  • 構建 RetWatcher 提供內存泄漏分析前的相關參數(如 DisplayService 通知服務,excludeRefs() 排除系統源碼泄漏),通過 Application.registerXxxLifecycleCallback() 監聽Activity或Fragment生命週期轉發給 RefWatcher

  • 在Activity或Fragment回調 onDestroy() 時,監聽引用對象是否還在 ReferenceQueue 中,有則表示內存泄漏,創建dump文件並通過Android工具 Debug.dumpHprofData() 寫入內存泄漏數據,hprof文件將會在另一個前臺服務分析

4 LeakCanary 2.x(2.2)

LeakCanary 1.6.x和2.x有不同的一些地方:

  • 2.x版本全部換爲使用kotlin編碼

  • 內存泄漏引用路徑分析庫由 haha 庫替換爲 shark 庫,據說減少了90%內存佔用,速度提升了6倍

其他的基本原理都和舊版本相同。

內部處理也有所不同:

  • 不需要自己註冊 LeakCanary

如果不需要定製配置,新版本 LeakCanary 只需要引入依賴導入即可使用。不再需要在 Application.onCreate() 手動註冊 LeakCanaryLeakCanary 自動通過 ContentProvider 註冊監聽(ContentProvider 會在應用啓動前創建):

internal sealed class AppWatcherInstaller : ContentProvider() {
	override fun onCreate(): Boolean {
		val application = context!!.applicationContext as Application
		InternalAppWatcher.install(application)
		return true
	}
}
  • RefWatcher.watch() (kotlin不是 RefWatcher,爲了體現兩個版本的區別用這個名稱方便理解)新舊版本處理不同:

舊版本執行完GC後如果懷疑名單隊列中還有監聽的引用對象存在內存泄漏會直接dump並分析;在新版本中會存在一個閾值,如果內存泄漏的對象數量在閾值內是不會dump分析。

internal class HeapDumpTrigger(...) {
	private fun checkRetainedObjects(reason: String) {
		...
		var retainedReferenceCount = objectWatcher.retainedObjectCount

		if (retainedReferenceCount > 0) {
			gcTrigger.runGc()
			retainedReferenceCount = objectWatcher.retainedObjectCount
		}

		// 如果內存泄漏對象數量在閾值內,不生成dump文件分析
		if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
		
		...
	}

	private fun checkRetainedCount(
		retainedKeysCount: Int,
		retainedVisibleThreshold: Int): Boolean {
		val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
		lastDisplayRetainedObjectCount = retainedKeysCount
		if (retainedKeydsCount == 0) {
			...
			return true
		}

		// 引發內存泄漏的對象在閾值內,如果應用在前臺會通知提示內存泄漏
		if (retainedKeysCount < retainedVisibleThreshold) {
			if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
				if (countChanged) { ... }
				// 創建通知
				showRetainedCountNotification(...)
				...
				return true
			}
		}
		return false
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章