LeakCanary 是一个轻量级、易用的 Android 内存泄露检测库,其源码设计清晰且架构合理。以下是 LeakCanary 源码的详细解析,从核心模块到关键流程。
1. 源码结构概览
LeakCanary 的源码主要分为以下几个核心模块:
(1) LeakCanary Entry Point
AppWatcher
:初始化入口,监控组件(如 Activity、Fragment)的生命周期。ObjectWatcher
:核心类,用于监控对象并检测是否发生泄露。
(2) 内存泄露检测
HeapAnalyzer
:用于解析堆转储文件(.hprof
)。Shark
:LeakCanary 内置的高效堆分析引擎。
(3) 内存泄露报告
HeapAnalysis
:堆分析结果的封装类,包含泄露对象的引用链等信息。HeapAnalysisSuccess
/HeapAnalysisFailure
:分别表示堆分析成功或失败的结果。
2. 初始化流程
LeakCanary 的入口在 AppWatcher
类。通过 AppWatcher
,LeakCanary 自动注册生命周期监听器并监控特定对象。
2.1 AppWatcher 初始化
LeakCanary
不需要手动写代码初始化,只需要在 gradle
中添加依赖就好了,然后 App
在启动时就会自动运行,年轻的时候我也非常好奇是怎么实现的,其实就是通过 ContentProvider
实现的,它可能是存在感最低的四大组建,但是 ContentProvider
他有一个特点,在 App
初始化的过程中也会初始化 ContentProvider
,这部分代码我就不细说了,感兴趣的同学可去网上找找相关的资料。
LeakCanary
对应的初始化的 ContentProvider
是 MainProcessAppWatcherInstaller
, 对应的 manifest
配置如下:
<application>
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
然后我们再看看 MainProcessAppWatcherInstaller
的源码:
Kotlin
代码解读
复制代码
internal class MainProcessAppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
override fun query(
uri: Uri,
projectionArg: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? = null
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? = null
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
override fun update(
uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
): Int = 0
}
在启动的时候调用了 AppWatcher#manualInstall()
方法,其他的都是模版代码。
继续看看 AppWatcher#manualInstall()
方法的实现:
Kotlin
代码解读
复制代码
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
// 默认的 Watchers
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
// 检查必须在主线程
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// 初始化 LeakCanaryDelegate,这个对象非常重要
// Requires AppWatcher.objectWatcher to be set
LeakCanaryDelegate.loadLeakCanary(application)
// 安装每个 Watcher
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}
AppWatcher#manualInstall()
主要初始化两个东西:一个是 LeakCanaryDelegate
;另外一个是各种 Watcher
。Watcher
就是用来监控各种已经进入 destroy
生命周期的 Android
组件;当 Watcher
发现组建已经 destory
了,但是它所对应的 Java
实例并没有被销毁就表示当前已经出现了泄漏,Watcher
就会通知 LeakCanaryDelegate
去 dump
内存,并分析内存,找到泄漏对象的 gc root
,锁定导致对象泄漏的真正凶手。
本篇文章主要分析 Watchers
,后面再分析 LeakCanaryDelegate
。
通过 appDefaultWatchers()
方法获取默认的 Watchers
:
Kotlin
代码解读
复制代码
fun appDefaultWatchers(
application: Application,
deletableObjectReporter: DeletableObjectReporter = objectWatcher.asDeletableObjectReporter()
): List<InstallableWatcher> {
// Use app context resources to avoid NotFoundException
// https://github.com/square/leakcanary/issues/2137
val resources = application.resources
val watchDismissedDialogs = resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
return listOf(
ActivityWatcher(application, deletableObjectReporter),
FragmentAndViewModelWatcher(application, deletableObjectReporter),
RootViewWatcher(deletableObjectReporter, WindowTypeFilter(watchDismissedDialogs)),
ServiceWatcher(deletableObjectReporter)
)
}
分别包含以下几种 Watchers
:
ActivityWatcher
:监控Activity
的销毁FragmentAndViewModelWatcher
:监控Fragment
和ViewModel
的销毁RootViewWatcher
:监控Window
中的RootView
的销毁ServiceWatcher
:监控Service
的销毁
Watcher
会把已经处于 destroy
的组件通知 DeletableObjectReporter
,后面我们再分析这个对象。
我们先分析各种功能的 Watchers
。
2.2 监听器注册
监控 Activity 的销毁
我们直接看 ActivityWatcher
源码:
Kotlin
代码解读
复制代码
class ActivityWatcher(
private val application: Application,
private val deletableObjectReporter: DeletableObjectReporter
) : InstallableWatcher {
// Kept for backward compatibility.
constructor(
application: Application,
reachabilityWatcher: ReachabilityWatcher
) : this(application, reachabilityWatcher.asDeletableObjectReporter())
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
deletableObjectReporter.expectDeletionFor(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
因为 Android
本身就提供了监控 Activity
的生命周期的方法,所以上面的代码实现非常简单,当 Activity
destroy
后会通知 DeletableObjectReporter#expectDeletionFor()
方法,所有的组件销毁后都会调用这个方法。这里还有一个非常有意思的点就是通过 noOpDelegate()
方法干掉了 Java
中接口中需要强制实现的方法,我前面写文章介绍过:Kotlin 干掉接口需要强制实现的方法
监控 Fragment 和 ViewModel 的销毁
直接看 FragmentAndViewModelWatcher#install()
方法:
Kotlin
代码解读
复制代码
// ...
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
// ...
监听 Activity
的生命周期,当 Activity
创建时依次通知 fragmentDestroyWatchers
,我们来看看 fragmentDestroyWatchers
的实现:
Kotlin
代码解读
复制代码
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
// Android 系统默认的 Fragment Watcher
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(deletableObjectReporter)
)
}
// Androidx 的 Fragment Watcher
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
deletableObjectReporter
)?.let {
fragmentDestroyWatchers.add(it)
}
// TODO turn this off by default.
// Support 的 Fragment Watcher.
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
deletableObjectReporter
)?.let {
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}
fragment
的监控分为三种,系统的 fragment
,androidx
的 fragment
和 support
的 fragment
,我们只分析 androidx
的 fragment
就好,其他两种大家自行分析。
androidx
的 fragment
的监控类是 AndroidXFragmentDestroyWatcher
,我们来看看它的入口函数:
Kotlin
代码解读
复制代码
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
val supportFragmentManager = activity.supportFragmentManager
//监控 Fragment 销毁
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
// 监控 ViewModel 销毁
ViewModelClearedWatcher.install(activity, deletableObjectReporter)
}
}
这里分为两块儿逻辑,一是监控 fragment
的销毁,二是监控 ViewModel
的销毁,ViewModel
又分为 fragment
和 activity
的 ViewModel
。
先来看看 fragment
销毁的监控:
Kotlin
代码解读
复制代码
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated(
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
// 监控 ViewModel 的销毁
ViewModelClearedWatcher.install(fragment, deletableObjectReporter)
}
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
// 监控 Fragment 对应的 view 的销毁
val view = fragment.view
if (view != null) {
deletableObjectReporter.expectDeletionFor(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
// 监控 Fragment 的销毁
deletableObjectReporter.expectDeletionFor(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
上面分为三块逻辑,分别是:执行监控 fragment
的 ViewModel
,监控 fragment
的 View
的销毁,监控 fragment
的销毁。
无论是 Activity
还是 Fragment
的 ViewModel
销毁监控,都在他们创建的时候调用了 ViewModelClearedWatcher.install()
方法,我们来看看它的实现:
Kotlin
代码解读
复制代码
fun install(
storeOwner: ViewModelStoreOwner,
deletableObjectReporter: DeletableObjectReporter
) {
val provider = ViewModelProvider(storeOwner, object : Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ViewModelClearedWatcher(storeOwner, deletableObjectReporter) as T
})
provider.get(ViewModelClearedWatcher::class.java)
}
Activity
和 Fragment
他们都是 ViewModelStoreOwner
,上面的代码是创建了一个 ViewModel
,实现类是 ViewModelClearedWatcher
。
Kotlin
代码解读
复制代码
// ...
// We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,
// however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0
// does not have ViewModelStore#keys. All versions currently have the mMap field.
private val viewModelMap: Map<String, ViewModel>? = try {
val storeClass = ViewModelStore::class.java
val mapField = try {
storeClass.getDeclaredField("map")
} catch (exception: NoSuchFieldException) {
// Field name changed from mMap to map with Kotlin conversion
// https://cs.android.com/androidx/platform/frameworks/support/+/8aa6ca1c924ab10d263b21b99b8790d5f0b50cc6
storeClass.getDeclaredField("mMap")
}
mapField.isAccessible = true
@Suppress("UNCHECKED_CAST")
mapField[storeOwner.viewModelStore] as Map<String, ViewModel>
} catch (ignored: Exception) {
SharkLog.d(ignored) { "Could not find ViewModelStore map of view models" }
null
}
override fun onCleared() {
viewModelMap?.values?.forEach { viewModel ->
deletableObjectReporter.expectDeletionFor(
viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
)
}
}
// ...
当 ViewModel
被销毁的时候,ViewModelStoreOwner
会回调 ViewModel
的 onCleared()
方法,这里它通过反射的方式去到 ViewModelStore
去拿所有的 ViewModel
,对应的成员变量是 map
或者 mMap
。 然后通知 DeletableObjectReporter
对应的 ViewModelStoreOwner
的所有的 ViewModel
都已经被销毁。
RootView 销毁监控
RootView
的销毁监控这段代码我认为有很大的学习价值,首先 Android
并没有直接提供 API
供我们监控 RootView
的创建和销毁。有人可能会懵逼,RootView
是什么?你所见到的所有的 UI,都会通过 WindowManager
添加,然后通过 binder
通知 WMS
,最终完成 UI 的绘制。无论是 Activity
(Fragment
是在 Activity
的 View
树中),Dialog
还是 PopupWindow
,而且还包括自定义的悬浮窗。除了 Activity
之外,其他的 UI 组件都是没有提供全局监控的 API。但是我们通过监控 RootView
就能够做到全局监控这些组件。
为了更好的理解 RootView
监控的这段代码,先看看在 WindowManager
中添加 View
的这段代码。(基于 API 35)
先看看 Activity#getSystemService()
方法:
Java
代码解读
复制代码
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
我们看到如果是需要 WindowManager
,直接返回的是 mWindowManager
的成员变量。
那么 mWindowManager
是在什么时候初始化的呢?我直接给结论,ActivityThread
收到 AMS
通过 binder
发送过来需要启动 Activity
时,这个时候会通过反射的方式去创建 Activity
实例,然后立即会调用 Activity#attach()
方法,在这个方法中就会完成 mWindowManager
的初始化,我们来看看这个方法:
Java
代码解读
复制代码
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken, IBinder initialCallerInfoAccessToken) {
// ...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
// ...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
// ...
}
我们看到 mWindowManager
是通过 PhoneWindow#getWindowManager()
方法获取的,前面还有一个关键的方法 PhoneWindow#setWindowManager()
,这里又调用了参数中的 context#getSystemService()
方法获取 WindowManager
。那这个 Context
是什么呢??这个是 ActivityThread
会为每一个 Activity
创建一个 ContextImpl
,它就是 Activity
中的 baseContext
。
我们再来看看 ContextImpl#getSystemService()
方法的实现:
Java
代码解读
复制代码
@Override
public Object getSystemService(String name) {
// ...
return SystemServiceRegistry.getSystemService(this, name);
}
这里调用了 SystemServiceRegistry#getSystemService()
方法,在这里就注册了所有的我们需要用的 Service
。我们继续看源码:
Java
代码解读
复制代码
public static Object getSystemService(@NonNull ContextImpl ctx, String name) {
final ServiceFetcher<?> fetcher = getSystemServiceFetcher(name);
if (fetcher == null) {
return null;
}
final Object ret = fetcher.getService(ctx);
// ...
return ret;
}
首先是获取到对应 Service
的 featcher
,然后获取对应 Service
的实例,这个 fetcher
是在 SystemServiceRegistry
的 static
代码块注册的。我们来看看这块代码:
Java
代码解读
复制代码
static {
// ...
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
// ...
}
这里注册了所有的 Android
的 Service
,如果你需要看其他的 Service
的源码,也来这里找就好了。WindowManager
的实现类是 WindowManagerImpl
。
我们再来看看 PhoneWindow#setWindowManager()
方法:
Java
代码解读
复制代码
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
然后我们看到 Activity
中使用的 mWindowManager
其实是调用 WindowManagerImpl#createLocalWindowManager()
生成的:
Java
代码解读
复制代码
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
}
WindowManagerImpl
其实是和 Activity
一一对应的,只是上面的方法添加了一个 parentWindow
,也就是 PhoneWindow
,他这样来表示 WindowManager
的子窗口的关系。顶级的 WindowManagerImpl
他的 parentWindow
就是空的。
我们要展示 UI 就需要调用 WindowManagerImpl#addView()
方法,我们来看看它的实现:
Java
代码解读
复制代码
// ...
@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
// ...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
// ...
我们看到这里是调用了 WindowManagerGlobal#addView()
方法,WindowManagerGlobal
实例的获取是通过 getInstance()
方法:
Java
代码解读
复制代码
@UnsupportedAppUsage
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
从这里可以看出 WindowManagerGlobal
也是单例的。
继续看看 WindowManagerGlobal#addView()
方法的实现:
Java
代码解读
复制代码
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
// ...
synchronized (mLock) {
// ...
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession, new WindowlessWindowLayout());
}
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// ...
}
}
我这里省略了部分逻辑,我们看到 view
被添加到成员变量 mViews
中,这里还会创建一个 ViewRootImpl
实例,并被添加到 mRoots
变量中。
Android
的源码我们就看到这里,到这里我们知道所有的 UI 的 root view
都被添加到了 WindowManagerGlobal#mView
中,而它也是一个单例.LeakCanary
也是反射的这个变量,我们来看 LeakCanary
的代码。
首先看 RootViewWatcher#install()
方法:
Kotlin
代码解读
复制代码
class WindowTypeFilter(private val watchDismissedDialogs: Boolean) : Filter {
override fun shouldExpectDeletionOnDetached(rootView: View): Boolean {
return when (rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
// Activities are already tracked by ActivityWatcher
is Activity -> false
is Dialog -> watchDismissedDialogs
// Probably a DreamService
else -> true
}
}
// Android widgets keep detached popup window instances around.
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
}
}
private val listener = OnRootViewAddedListener { rootView ->
if (rootViewFilter.shouldExpectDeletionOnDetached(rootView)) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
val watchDetachedView = Runnable {
deletableObjectReporter.expectDeletionFor(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}
override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}
override fun install() {
Curtains.onRootViewsChangedListeners += listener
}
首先在 Curtains
中添加对 root view
添加时的监听,当有 root view
被添加时来通过 WindowTypeFilter
来过滤需要处理的 root view
,如果需要处理的话,会想 root view
中添加一个 attach/detach 的监听,当 root view
被移除时就表示已经回收了,然后通知 DeletableObjectReporter
。
我们再简单看看他是怎么判断 root view
是属于哪个组件的:
Kotlin
代码解读
复制代码
class WindowTypeFilter(private val watchDismissedDialogs: Boolean) : Filter {
override fun shouldExpectDeletionOnDetached(rootView: View): Boolean {
return when (rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
// Activities are already tracked by ActivityWatcher
is Activity -> false
is Dialog -> watchDismissedDialogs
// Probably a DreamService
else -> true
}
}
// Android widgets keep detached popup window instances around.
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
}
}
val View.windowType: WindowType
get() {
val rootView = rootView
if (WindowSpy.attachedToPhoneWindow(rootView)) {
return PHONE_WINDOW
}
val windowLayoutParams = rootView.layoutParams as? WindowManager.LayoutParams
return if (windowLayoutParams == null) {
UNKNOWN
} else {
val title = windowLayoutParams.title
when {
title == "Toast" -> TOAST
title == tooltipString -> TOOLTIP
// App compat tooltip uses the class simple name.
title == "TooltipPopup" -> TOOLTIP
title.startsWith("PopupWindow:") -> POPUP_WINDOW
else -> UNKNOWN
}
}
}
private val decorViewClass by lazy(NONE) {
val sdkInt = Build.VERSION.SDK_INT
val decorViewClassName = when {
sdkInt >= 24 -> "com.android.internal.policy.DecorView"
sdkInt == 23 -> "com.android.internal.policy.PhoneWindow$DecorView"
else -> "com.android.internal.policy.impl.PhoneWindow$DecorView"
}
try {
Class.forName(decorViewClassName)
} catch (ignored: Throwable) {
Log.d(
"WindowSpy", "Unexpected exception loading $decorViewClassName on API $sdkInt", ignored
)
null
}
}
fun attachedToPhoneWindow(maybeDecorView: View): Boolean {
return decorViewClass?.let { decorViewClass ->
decorViewClass.isInstance(maybeDecorView)
} ?: false
}
首先判断 root view
是否是通过 PhoneWindow
添加,判断方法是检查 root view
的实现类是不是 DecorView
,如果是通过 PhoneWindow
添加又分为两种情况,一种是 Activity
一种是 Dialog
,他们俩的区分方式是通过 wrappedCallback
来判断。
其他的情况就通过 LayoutParams
的 title
来判断,具体看代码。
继续看看 Curtains.onRootViewsChangedListeners
是如何监听 root view
的添加的:
Kotlin
代码解读
复制代码
private val rootViewsSpy by lazy(NONE) {
RootViewsSpy.install()
}
@JvmStatic
val onRootViewsChangedListeners: MutableList<OnRootViewsChangedListener>
get() {
return rootViewsSpy.listeners
}
这里的又是从 RootViewSpy
中获取的,RootViewSpy#install()
方法返回了 RootViewSpy
实例。
Kotlin
代码解读
复制代码
internal class RootViewsSpy private constructor() {
val listeners = CopyOnWriteArrayList<OnRootViewsChangedListener>()
fun copyRootViewList(): List<View> {
return if (Build.VERSION.SDK_INT >= 19) {
delegatingViewList.toList()
} else {
WindowManagerSpy.windowManagerMViewsArray().toList()
}
}
private val delegatingViewList = object : ArrayList<View>() {
override fun add(element: View): Boolean {
listeners.forEach { it.onRootViewsChanged(element, true) }
return super.add(element)
}
override fun removeAt(index: Int): View {
val removedView = super.removeAt(index)
listeners.forEach { it.onRootViewsChanged(removedView, false) }
return removedView
}
}
companion object {
fun install(): RootViewsSpy {
return RootViewsSpy().apply {
WindowManagerSpy.swapWindowManagerGlobalMViews { mViews ->
delegatingViewList.apply { addAll(mViews) }
}
}
}
}
}
我们看到 RootViewSpy
创建的时候又调用了 WindowManagerSpy.swapWindowManagerGlobalMViews()
方法,这个方法中就把上面我们提到的 WindowManagerGlobal
中的成员变量 mViews
替换成了 delegatingViewList
,然后 delegatingViewList
是继承于 ArrayList
不过在它的 add()
和 removeAt()
中添加了 listener
的回调。
继续看 WindowManagerSpy.swapWindowManagerGlobalMViews()
是如何完成 hook
的。
Kotlin
代码解读
复制代码
private val windowManagerClass by lazy(NONE) {
val className = if (SDK_INT > 16) {
"android.view.WindowManagerGlobal"
} else {
"android.view.WindowManagerImpl"
}
try {
Class.forName(className)
} catch (ignored: Throwable) {
Log.w("WindowManagerSpy", ignored)
null
}
}
private val windowManagerInstance by lazy(NONE) {
windowManagerClass?.let { windowManagerClass ->
val methodName = if (SDK_INT > 16) {
"getInstance"
} else {
"getDefault"
}
windowManagerClass.getMethod(methodName).invoke(null)
}
}
private val mViewsField by lazy(NONE) {
windowManagerClass?.let { windowManagerClass ->
windowManagerClass.getDeclaredField("mViews").apply { isAccessible = true }
}
}
@SuppressLint("PrivateApi", "ObsoleteSdkInt", "DiscouragedPrivateApi")
fun swapWindowManagerGlobalMViews(swap: (ArrayList<View>) -> ArrayList<View>) {
if (SDK_INT < 19) {
return
}
try {
windowManagerInstance?.let { windowManagerInstance ->
mViewsField?.let { mViewsField ->
@Suppress("UNCHECKED_CAST")
val mViews = mViewsField[windowManagerInstance] as ArrayList<View>
mViewsField[windowManagerInstance] = swap(mViews)
}
}
} catch (ignored: Throwable) {
Log.w("WindowManagerSpy", ignored)
}
}
上面的代码也是朴实无华,就不多说了。
Service 销毁监控
同样的 Android
也没有提供 API 去全局监控 Service
的生命周期,又只有用 hook
的方法了。
Service
我们在开发中使用的量不是很多,我们就简单看看吧,就不像 root view
一样讲得那么详细了。它的 hook
分为两块儿。
首先是 hook
ActivityThread
的命名为 mH
的 Handler
成员变量。然后 hook
Hander
的 callback
,在收到 STOP_SERVICE
时就表示 Service
要被销毁了,然后对应的 msg
的 waht
就是对应 Service
的 token
,然后再 hook
ActivityThread
中的 mServices
成员变量,它表示当前所有存活的 Service
,通过上面的 token
找到对应的 Service
,然后把它添加到即将销毁的 Service
集合中。
我们来看看这部分代码:
Kotlin
代码解读
复制代码
// Hook ActivityThread#mServices 拿到所有存活的 Services
private val activityThreadServices by lazy {
val mServicesField =
activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true }
@Suppress("UNCHECKED_CAST")
mServicesField[activityThreadInstance] as Map<IBinder, Service>
}
override fun install() {
// ...
try {
// Hook ActivityThread#mH 中的 mCallback
swapActivityThreadHandlerCallback { mCallback ->
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
// 替换的新的 Callback
Handler.Callback { msg ->
// https://github.com/square/leakcanary/issues/2114
// On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord
// instead of an IBinder. This crashes on a ClassCastException. Adding a type check
// here to prevent the crash.
if (msg.obj !is IBinder) {
return@Callback false
}
// Service 销毁的消息
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
activityThreadServices[key]?.let {
// 将即将被销毁的 Service 添加集合中
onServicePreDestroy(key, it)
}
}
mCallback?.handleMessage(msg) ?: false
}
}
// ...
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not watch destroyed services" }
}
}
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
private val activityThreadInstance by lazy {
activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!!
}
private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
val mHField =
activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
val mH = mHField[activityThreadInstance] as Handler
val mCallbackField =
Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
val mCallback = mCallbackField[mH] as Handler.Callback?
mCallbackField[mH] = swap(mCallback)
}
private fun onServicePreDestroy(
token: IBinder,
service: Service
) {
servicesToBeDestroyed[token] = WeakReference(service)
}
另外一块儿 hook
就是替换 ActivityManager
,通过动态代理生成一个新的 ActivityManager
对象,所有的方法处理还是原来的 ActivityManager
,只是用来监控 ActivityManager#serviceDoneExecuting()
方法的调用,这个方法调用了就表示 Service
已经寿终正寝了,然后从上面获取到即将被销毁的 Service
集合中去查找这个 Service
,然后回调 Service
已经销毁给 DeletableObjectReporter
。
继续看代码:
Java
代码解读
复制代码
override fun install() {
// ...
try {
// ...
// Hook ActivityManager.
swapActivityManager { activityManagerInterface, activityManagerInstance ->
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
// 动态代理一个新的 ActivityManager
Proxy.newProxyInstance(
activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
) { _, method, args ->
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
// 这里表示对应的 Service 已经销毁
val token = args!![0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
// 通知 Service 已经销毁
onServiceDestroyed(token)
}
}
// 使用原来的 ActivityManager 实现
try {
if (args == null) {
method.invoke(activityManagerInstance)
} else {
method.invoke(activityManagerInstance, *args)
}
} catch (invocationException: InvocationTargetException) {
throw invocationException.targetException
}
}
}
} catch (ignored: Throwable) {
SharkLog.d(ignored) { "Could not watch destroyed services" }
}
}
@SuppressLint("PrivateApi")
private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
val singletonClass = Class.forName("android.util.Singleton")
val mInstanceField =
singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }
val singletonGetMethod = singletonClass.getDeclaredMethod("get")
val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
"android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
"android.app.ActivityManagerNative" to "gDefault"
}
val activityManagerClass = Class.forName(className)
val activityManagerSingletonField =
activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]
// Calling get() instead of reading from the field directly to ensure the singleton is
// created.
val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)
val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
mInstanceField[activityManagerSingletonInstance] =
swap(iActivityManagerInterface, activityManagerInstance!!)
}
private fun onServiceDestroyed(token: IBinder) {
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
serviceWeakReference.get()?.let { service ->
deletableObjectReporter.expectDeletionFor(
service, "${service::class.java.name} received Service#onDestroy() callback"
)
}
}
}
上面的代码比较简单就不细说了。
3. 泄露检测
当所有组件销毁时都会通知 DeletableObjectReporter#expectDeletionFor()
方法,我们来看看这个对象是怎么创建的:
Java
代码解读
复制代码
fun appDefaultWatchers(
application: Application,
deletableObjectReporter: DeletableObjectReporter = objectWatcher.asDeletableObjectReporter()
): List<InstallableWatcher> {
// Use app context resources to avoid NotFoundException
// https://github.com/square/leakcanary/issues/2137
val resources = application.resources
val watchDismissedDialogs = resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
return listOf(
ActivityWatcher(application, deletableObjectReporter),
FragmentAndViewModelWatcher(application, deletableObjectReporter),
RootViewWatcher(deletableObjectReporter, WindowTypeFilter(watchDismissedDialogs)),
ServiceWatcher(deletableObjectReporter)
)
}
在获取默认 Watcher
的时候我们就看到了它的身影,希望你还没有忘记上面的代码,他是通过 ObjectWatcher#asDeletableObjectReporter()
方法获取的,ObjectWatcher
已经被标记为弃用了,看来不久的将来会移除它。
Kotlin
代码解读
复制代码
fun asDeletableObjectReporter(): DeletableObjectReporter =
DeletableObjectReporter { target, reason ->
// 这个 Labmbda 其实就是 expectDeletionFor() 方法
expectWeaklyReachable(target, reason)
// This exists for backward-compatibility purposes and as such is unable to return
// an accurate [TrackedObjectReachability] implementation.
object : TrackedObjectReachability {
override val isStronglyReachable: Boolean
get() = error("Use a non deprecated DeletableObjectReporter implementation instead")
override val isRetained: Boolean
get() = error("Use a non deprecated DeletableObjectReporter implementation instead")
}
}
然后主要逻辑是调用了 expectWeaklyReachable()
方法,我们来看看在 ObjectWatcher
中的实现:
Kotlin
代码解读
复制代码
override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
val retainTrigger =
retainedObjectTracker.expectDeletionOnTriggerFor(watchedObject, description)
checkRetainedExecutor.execute {
retainTrigger.markRetainedIfStronglyReachable()
}
}
调用 ReferenceQueueRetainedObjectTracker#expectDeletionOnTriggerFor()
方法,这个方法会返回一个 RetainTrigger
,然后在后台线程池中再执行 RetainTrigger#markRetainedIfStronglyReachable()
方法。
我相信大部分人都使用过 WeakReference
,但是我相信不是所有人都知道如何监听 WeakReference
中的对象何时被回收,那就是在创建 WeakReference
的时候传入一个 ReferenceQueue
,被回收的对象会被添加到 ReferenceQueue
中,这样我们就知道哪些对象被 gc
给回收了。在 LeakCanary
中也是这么判断的,应该被回收的对象都会传给过来,这个过程中还会手动触发 gc
,然后超过一段时间后还是没有被回收 LeakCanary
就认为这个对象泄漏了,然后就会采取下一步措施锁定问题。
在了解了上面的知识后我们继续看 expectDeletionOnTriggerFor()
方法:
Kotlin
代码解读
复制代码
override fun expectDeletionOnTriggerFor(
target: Any,
reason: String
): RetainTrigger {
// 移除已经被回收的对象
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptime = clock.uptime()
// 生成一个自定义的带 key 的 weak reference, 注意这里有传入 ReferenceQueue
val reference =
KeyedWeakReference(target, key, reason, watchUptime.inWholeMilliseconds, queue)
SharkLog.d {
"Watching " +
(if (target is Class<*>) target.toString() else "instance of ${target.javaClass.name}") +
(if (reason.isNotEmpty()) " ($reason)" else "") +
" with key $key"
}
// 保存弱引用到成员变量
watchedObjects[key] = reference
// 构建一个 trigger 对象返回
return object : RetainTrigger {
// 判断强引用是否可以到达
override val isStronglyReachable: Boolean
get() {
removeWeaklyReachableObjects()
val weakRef = watchedObjects[key]
return weakRef != null
}
// 判断当前对象是否还存在,如果这里是 true 那就表示当前对象已经泄漏了
override val isRetained: Boolean
get() {
removeWeaklyReachableObjects()
val weakRef = watchedObjects[key]
return weakRef?.retained ?: false
}
// 触发检查当前对象是否发生泄漏
override fun markRetainedIfStronglyReachable() {
moveToRetained(key)
}
}
}
我们在前面说到会在后台线程触发 RetainTrigger#markRetainedIfStronglyReachable()
方法,这个后台线程的触发的延迟时间是 5s
,内部调用的是 moveToRetained()
方法,这里简单做一个总结,也就是 Android
中的某个组件进入 destroy
生命周期,LeakCanary
会将它对应的 Java
对象创建一个 WeakReference
,并添加 ReferenceQueue
,用于后续检查对应的对象是否已经被回收。然后开启一个延迟 5s
的检查任务,当这个任务触发时去检查目标对象是否被回收,如果没有被回收就开始后续的证据收集任务。
我们先来简单看看 LeakCanary
自定义的 KeyedWeakReference
类:
Kotlin
代码解读
复制代码
class KeyedWeakReference(
referent: Any,
// 对应对象的 key
val key: String,
// 组件的描述
val description: String,
// 组件对象 destory 生命周期时的时间戳
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
) {
/**
* Time at which the associated object ([referent]) was considered retained, or -1 if it hasn't
* been yet.
*/
// 触发回收检查时,并且没有回收时的时间戳
@Volatile
var retainedUptimeMillis = -1L
// 是否被回收
val retained: Boolean
get() = retainedUptimeMillis != -1L
override fun clear() {
super.clear()
retainedUptimeMillis = -1L
}
override fun get(): Any? {
error("Calling KeyedWeakReference.get() is a mistake as it revives the reference")
}
/**
* Same as [WeakReference.get] but does not trigger an intentional crash.
*
* Calling this method will end up creating local references to the objects, preventing them from
* becoming weakly reachable, and creating a leak. If you need to check for identity equality, use
* Reference.refersTo instead.
*/
fun getAndLeakReferent(): Any? {
return super.get()
}
companion object {
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L
}
}
上面的对象并没有做太多的工作,添加了关键的对象的 key
,还使用了一个变量来描述对象是否被回收,或者表示成是否泄漏。
继续看看 removeWeaklyReachableObjects()
方法如何清除被回收的对象。
Kotlin
代码解读
复制代码
private fun removeWeaklyReachableObjects() {
// 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.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
方法很简单,直接去读取 ReferenceQueue
中的对象,这里面的对象都是被回收了的,然后把它们从 watchedObjects
中移除。上面的方法在很多的地方都会调用。
我们继续看看 moveToRetained()
方法是如何强制检查某个对象是否泄漏了:
Kotlin
代码解读
复制代码
private fun moveToRetained(key: String) {
// 清除已经被回收的对象
removeWeaklyReachableObjects()
// 获取对应对象的 WeakRef
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
// 这里就表示发生了泄漏。
// 更新泄漏检查的时间
retainedRef.retainedUptimeMillis = clock.uptime().inWholeMilliseconds
// 通知已经发生泄漏
onObjectRetainedListener.onObjectRetained()
}
}
检查某个对象是否泄漏也非常简单,先清除一次被回收的对象,然后去查找对应对象是否还存在,如果存在就表示发生了泄漏,发生泄漏后会更新泄漏时的时间戳,然后通过 listener
通知别的地方发生了泄漏,那么这个 listener
是在哪儿添加的呢?
在第一篇文章中讲 LeakCannary
初始化的时候我就说过,主要初始化分为两块逻辑,一是初始化各种 Watchers
去监控 Android
组件的销毁,还有就是初始化 LeakCanaryDelegate
,你可能已经忘了,我们再回忆下:
Kotlin
代码解读
复制代码
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
// 初始化 LeakCanaryDelegate
LeakCanaryDelegate.loadLeakCanary(application)
// 初始化各种 watchers
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}
继续看看 LeakCanaryDelegate.loadLeakCanary()
方法的实现:
Kotlin
代码解读
复制代码
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
这里又通过反射的方法调用 InternalLeakCanary
的方法:
Kotlin
代码解读
复制代码
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
// 添加泄漏监听
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// gc 触发器
val gcTrigger = GcTrigger.inProcess()
val configProvider = { LeakCanary.config }
// 后台线程初始化
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// 内存 dump 触发器
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
configProvider
)
// APP 可见性监听
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
// 添加 Android shortcut 的UI
LeakCanaryAndroidInternalUtils.addLeakActivityDynamicShortcut(application)
// We post so that the log happens after Application.onCreate() where
// the config could be updated.
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}
首先注册最重要的泄漏监听,这也解决了我们上面提出的问题;初始化 gc
触发器;初始化内存 dump
触发器;监听 APP
的可见性;添加 Android
shortcut
的 UI。
我们来看看发生泄漏后它会怎么处理:
Kotlin
代码解读
复制代码
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
直接调用 HeapDumpTrigger#scheduleRetainedObjectCheck()
方法:
Kotlin
代码解读
复制代码
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
直接在后台线程中调用 checkRetainedObjects()
方法,继续追踪:
Kotlin
代码解读
复制代码
private fun checkRetainedObjects() {
// 检查当前是否可以 dump
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
if (iCanHasHeap is Nope) {
// 不能 dump
// ...
return
}
// 泄漏的对象数量
var retainedReferenceCount = retainedObjectTracker.retainedObjectCount
if (retainedReferenceCount > 0) {
// 当泄漏的对象大于 0 时,触发 gc
gcTrigger.runGc()
retainedReferenceCount = retainedObjectTracker.retainedObjectCount
}
// 检查泄漏对象的数量是否达到配置的上限,如果没有达到上限,跳过 dump, 默认上限值是 5
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// 检查两次 dump 的时间间隔,如果小于最小的间隔开启一个延时任务等到间隔时间了再 dump,最小间隔是 60s,
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
// 关闭通知 UI
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
// 执行 dump
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
- 检查当前是否能够
dump
,不能直接退出。 - 检查泄漏对象数量,如果大于 0 通过
GCTracker
触发一次GC
。 - 检查泄漏的对象是否达到配置的泄漏对象的上限,默认是 5,如果没有达到上限跳过
dump
。 - 检查上次
dump
到现在的间隔,如果小于 60s,那么需要等到 60s 后再次执行该任务。 - 执行
dump
。
看看 GCTracker
的实现:
Kotlin
代码解读
复制代码
override fun 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()
enqueueReferences()
System.runFinalization()
System.gc()
}
private fun 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 (e: InterruptedException) {
throw AssertionError()
}
}
看这个描述,这段代码是在 AOSP
中的测试代码中抄过来的,我们也可以直接拿过来直接用。
我们再来看看 dumpHeap()
方法的实现:
Kotlin
代码解读
复制代码
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
val directoryProvider =
InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
// 获取 dump 输出的文件
val heapDumpFile = directoryProvider.newHeapDumpFile()
val durationMillis: Long
if (currentEventUniqueId == null) {
currentEventUniqueId = UUID.randomUUID().toString()
}
try {
// 发送开始 dump 的事件
InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
if (heapDumpFile == null) {
throw RuntimeException("Could not create heap dump file")
}
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
durationMillis = measureDurationMillis {
// 执行 dump
configProvider().heapDumper.dumpHeap(heapDumpFile)
}
if (heapDumpFile.length() == 0L) {
throw RuntimeException("Dumped heap file is 0 byte length")
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
retainedObjectTracker.clearObjectsTrackedBefore(heapDumpUptimeMillis.milliseconds)
currentEventUniqueId = UUID.randomUUID().toString()
// 发送 dump 成功的事件
InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
} catch (throwable: Throwable) {
// 出错
InternalLeakCanary.sendEvent(HeapDumpFailed(currentEventUniqueId!!, throwable, retry))
if (retry) {
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
}
// 展示 dump 出错的通知栏。
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
return
}
}
上面的代码非常简单,dump
开始,结束和出错都会以事件的形式通知 InternalLeakCanary
,针对不同的事件做出不同的处理,dump
的具体实现是 heapDumper
。 下面是它的接口:
Kotlin
代码解读
复制代码
fun interface HeapDumper {
/**
* Dumps the heap. The implementation is expected to be blocking until the heap is dumped
* or heap dumping failed.
*
* Implementations can throw a runtime exception if heap dumping failed.
*/
fun dumpHeap(heapDumpFile: File)
/**
* This allows external modules to add factory methods for implementations of this interface as
* extension functions of this companion object.
*/
companion object
}
fun HeapDumper.withGc(gcTrigger: GcTrigger = GcTrigger.inProcess()): HeapDumper {
val delegate = this
return HeapDumper { file ->
gcTrigger.runGc()
delegate.dumpHeap(file)
}
}
HeapDumper
这三个实现我认为可以看看:AndroidDebugHeapDumper
,HotSpotHeapDumper
和 UiAutomatorShellHeapDumper
。
AndroidDebugHeapDumper
也就是默认 Android
中 Debug
时使用,只有 Debug
可以用:
Kotlin
代码解读
复制代码
/**
* Dumps the Android heap using [Debug.dumpHprofData].
*
* Note: despite being part of the Debug class, [Debug.dumpHprofData] can be called from non
* debuggable non profileable builds.
*/
object AndroidDebugHeapDumper : HeapDumper {
override fun dumpHeap(heapDumpFile: File) {
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
}
fun HeapDumper.Companion.forAndroidInProcess() = AndroidDebugHeapDumper
朴实无华的代码。
HotSpotHeapDumper
HotSpot
虚拟机使用,我看到在测试的代码中有用到。
Kotlin
代码解读
复制代码
object HotSpotHeapDumper : HeapDumper {
private val hotspotMBean: HotSpotDiagnosticMXBean by lazy {
val mBeanServer = ManagementFactory.getPlatformMBeanServer()
ManagementFactory.newPlatformMXBeanProxy(
mBeanServer,
"com.sun.management:type=HotSpotDiagnostic",
HotSpotDiagnosticMXBean::class.java
)
}
override fun dumpHeap(heapDumpFile: File) {
val live = true
hotspotMBean.dumpHeap(heapDumpFile.absolutePath, live)
}
}
fun HeapDumper.Companion.forJvmInProcess() = HotSpotHeapDumper
同样朴实无华的代码。
UiAutomatorShellHeapDumper
是 Android
中通过命令行的方式进行 dump
。
Kotlin
代码解读
复制代码
class UiAutomatorShellHeapDumper(
private val withGc: Boolean,
private val dumpedAppPackageName: String
) : HeapDumper {
override fun dumpHeap(heapDumpFile: File) {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val device = UiDevice.getInstance(instrumentation)
val processId = device.getPidsForProcess(dumpedAppPackageName)
// TODO Figure out what to do when we get more than one.
.single()
SharkLog.d { "Dumping heap for "$dumpedAppPackageName" with pid $processId to ${heapDumpFile.absolutePath}" }
val forceGc = if (withGc && Build.VERSION.SDK_INT >= 27) {
"-g "
} else {
""
}
device.executeShellCommand("am dumpheap $forceGc$processId ${heapDumpFile.absolutePath}")
// Make the heap dump world readable, otherwise we can't read it.
device.executeShellCommand("chmod +r ${heapDumpFile.absolutePath}")
}
// Based on https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt;l=467;drc=8f2ba6a5469f67b7e385878d704f97bde22419ce
private fun UiDevice.getPidsForProcess(processName: String): List<Int> {
if (Build.VERSION.SDK_INT >= 23) {
return pgrepLF(pattern = processName)
.mapNotNull { (pid, fullProcessName) ->
if (fullProcessNameMatchesProcess(fullProcessName, processName)) {
pid
} else {
null
}
}
}
val processList = executeShellCommand("ps")
return processList.lines()
.filter { psLineContainsProcess(it, processName) }
.map {
val columns = SPACE_PATTERN.split(it)
columns[1].toInt()
}
}
private fun UiDevice.pgrepLF(pattern: String): List<Pair<Int, String>> {
return executeShellCommand("pgrep -l -f $pattern")
.split(Regex("\r?\n"))
.filter { it.isNotEmpty() }
.map {
val (pidString, process) = it.trim().split(" ")
Pair(pidString.toInt(), process)
}
}
private fun psLineContainsProcess(
psOutputLine: String,
processName: String
): Boolean {
return psOutputLine.endsWith(" $processName") || psOutputLine.endsWith("/$processName")
}
private fun fullProcessNameMatchesProcess(
fullProcessName: String,
processName: String
): Boolean {
return fullProcessName == processName || fullProcessName.endsWith("/$processName")
}
private companion object {
private val SPACE_PATTERN = Regex("\s+")
}
}
fun HeapDumper.Companion.forUiAutomatorAsShell(
withGc: Boolean,
dumpedAppPackageName: String = InstrumentationRegistry.getInstrumentation().targetContext.packageName
) = UiAutomatorShellHeapDumper(withGc, dumpedAppPackageName)
这里用到了 androidx
的库来执行命令行,具体的命令行是 am dumpheap -g [pid] [file]
, 最后通过 chmod +r [file]
让文件可读,上面的大量代码是去拿当前应用的 pid
,使用的是命令 pgrep -l -f [process name]
。
4. 内存泄漏报告
4.1. 获取堆转储文件
- 在检测到可能的内存泄漏后,LeakCanary 调用 Android 提供的
Debug.dumpHprofData()
方法生成堆转储文件。 - 该文件记录了当前 JVM 堆中的对象分布、引用关系以及元数据信息。
kotlin
复制代码
Debug.dumpHprofData("path/to/heapdump.hprof")
4.2. Shark 引擎解析 .hprof
文件
LeakCanary 的 Shark 引擎是核心分析工具,其主要任务是解析 .hprof
文件,构建对象引用图,并找到导致内存泄漏的引用路径。
关键步骤
-
加载
.hprof
文件 Shark 引擎加载.hprof
文件并逐行解析,提取堆中对象、类、字段和引用信息。kotlin 复制代码 val heapAnalyzer = HeapAnalyzer(SharkLog.Logger { message -> Log.d(TAG, message) }) val heapAnalysis = heapAnalyzer.analyzeHeap(heapDumpFile)
-
构建引用图 Shark 使用解析后的数据构建引用图(Reference Graph)。这个图展示了堆中所有对象及其引用关系。
- 每个对象是图中的一个节点。
- 节点之间的边表示对象之间的引用。
-
定位 GC Roots GC Root 是 JVM 中一组特殊的对象,这些对象被认为是“活跃”的,任何与它们有路径连接的对象都不会被垃圾回收。
常见的 GC Root 类型:
- 静态变量(静态字段持有的对象)
- 活跃线程中的局部变量
- JNI 引用
-
追踪泄漏路径 从 GC Roots 出发,沿着引用链追踪到未被回收的对象(泄漏对象)。
- 如果对象本应被销毁但仍可通过 GC Roots 引用链访问,则说明该对象发生泄漏。
4.3. 泄漏分析结果
引用链
Shark 引擎的输出结果包括泄漏对象及其引用链。引用链展示了泄漏对象是如何通过其他对象间接或直接与 GC Roots 连接的。
示例引用链:
plaintext
复制代码
GC Root -> Singleton -> Activity -> View
HeapAnalysisSuccess
如果堆文件解析成功,HeapAnalysisSuccess
会返回详细的泄漏信息:
- 泄漏对象的引用路径。
- 泄漏对象的内存占用大小。
- 导致泄漏的源代码位置(如果可能)。
kotlin
复制代码
data class HeapAnalysisSuccess(
val retainedObjectCount: Int,
val leakTraces: List<LeakTrace>
)
HeapAnalysisFailure
如果解析失败,HeapAnalysisFailure
会提供失败的原因,帮助开发者排查问题。
4.4. 引用链可视化
LeakCanary 使用图形化方式显示引用链(例如在通知中展示简化后的引用路径),方便开发者快速定位问题。
示例:Activity 泄漏解析
代码示例
kotlin
复制代码
class LeakActivity : AppCompatActivity() {
companion object {
var leakInstance: LeakActivity? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
leakInstance = this
}
}
泄漏原因
- 静态变量
leakInstance
持有LeakActivity
的引用。 - 即使
LeakActivity
被销毁,leakInstance
仍然存在,导致无法被回收。
Shark 引擎解析过程
-
加载
.hprof
文件- Shark 引擎扫描堆转储文件,找到所有对象及其引用。
-
构建引用图
- 引用链:
GC Root -> leakInstance -> LeakActivity
- 引用链:
-
输出泄漏路径
-
最终生成的泄漏路径为:
plaintext 复制代码 GC Root ↓ leakInstance (Static field) ↓ LeakActivity
-
4.5. 为什么需要解析 GC Root?
GC Root 是 JVM 中垃圾回收的起点,任何从 GC Root 可达的对象都被认为是“活跃”的,不会被回收。LeakCanary 通过 Shark 引擎:
- 找出 GC Roots。
- 确定哪些对象与 GC Root 可达。
- 发现不应该存在的对象(如已销毁的 Activity),从而定位内存泄漏。
4.6. 结论
LeakCanary 通过 Shark 引擎解析堆转储文件,追踪泄漏对象的引用链。GC Root 的分析是整个流程的关键,因为它是垃圾回收的起点,决定了对象的存活状态。
- 获取
.hprof
文件:调用系统方法导出内存快照。 - 解析引用关系:构建引用图,从 GC Root 开始追踪泄漏对象。
- 输出引用链:展示详细路径,帮助开发者快速定位泄漏原因。
这套流程极大地简化了内存泄漏的定位工作,使得 LeakCanary 成为 Android 开发中不可或缺的内存管理工具
5. 关键模块源码解析
(1) ObjectWatcher
ObjectWatcher
是 LeakCanary 的核心类,负责监控对象并检测是否发生泄露。
关键字段
kotlin
复制代码
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val referenceQueue = ReferenceQueue<Any>()
watchedObjects
:存储所有被监控的对象。referenceQueue
:保存被 GC 标记的弱引用对象。
watch() 方法
ObjectWatcher
的核心方法 watch()
将目标对象包裹为 KeyedWeakReference
并添加到监控队列。
kotlin
复制代码
fun watch(watchedObject: Any, description: String) {
val key = UUID.randomUUID().toString()
val weakReference = KeyedWeakReference(watchedObject, key, description, referenceQueue)
watchedObjects[key] = weakReference
ensureReferenceQueueMonitoring()
}
(2) 检测逻辑
引用队列监控
通过一个后台线程不断检查 referenceQueue
是否有对象被回收。
kotlin
复制代码
private fun ensureReferenceQueueMonitoring() {
referenceQueueThread = Thread {
while (true) {
val weakReference = referenceQueue.remove() as KeyedWeakReference
watchedObjects.remove(weakReference.key)
}
}
referenceQueueThread.start()
}
检测内存泄露
如果对象在一定时间内未被回收,则触发内存泄露分析。
(3) 堆转储与分析
Heap Dump
当确定内存泄露时,LeakCanary 触发堆转储操作:
kotlin
复制代码
Debug.dumpHprofData("path/to/heapdump.hprof")
堆分析引擎 Shark
LeakCanary 使用 Shark 引擎解析 .hprof
文件,查找泄露对象的引用链。
kotlin
复制代码
val heapAnalyzer = HeapAnalyzer(SharkLog.Logger { message -> Log.d(TAG, message) })
val analysis = heapAnalyzer.analyzeHeap(heapDumpFile)
analyzeHeap
方法:构建对象引用图(Reference Graph),从 GC Roots 开始查找泄露路径。
(4) 分析结果报告
堆分析结果通过 HeapAnalysis
返回:
HeapAnalysisSuccess
:泄露对象及其引用链信息。HeapAnalysisFailure
:堆分析失败的原因。
引用链构建
LeakCanary 输出泄露对象的引用链:
plaintext
复制代码
GC Root -> Singleton -> Activity -> View
帮助开发者快速定位泄露源。
6. 示例流程
以下为一个 Activity
泄露的完整检测流程:
-
Activity 监控
- 在
onActivityDestroyed()
中调用ObjectWatcher.watch()
。 - 将
Activity
包装为弱引用,并放入watchedObjects
。
- 在
-
GC 触发
- 强制调用
System.gc()
,检查对象是否被回收。
- 强制调用
-
泄露确认
- 如果对象未被回收,触发堆转储。
-
堆分析
- 使用 Shark 分析
.hprof
文件,构建引用链。
- 使用 Shark 分析
-
生成报告
- 输出泄露对象及其引用路径,开发者可以根据路径修复问题。
7. 源码架构优势
(1) 模块化设计
- 各模块职责分明:
ObjectWatcher
负责监控对象,HeapAnalyzer
负责堆分析,Shark
提供高效的堆解析能力。
(2) 高效堆分析
- 使用 Shark 代替传统 MAT 工具,分析效率大幅提升,且支持内存受限的移动设备。
(3) 自动化流程
- 与应用生命周期深度集成,开发者无需额外代码即可检测内存泄露。
8. 总结
LeakCanary 的核心工作流程:
- 监控对象:通过弱引用和引用队列检查对象生命周期。
- 泄露判断:在合适时间点触发 GC,并检测对象是否被回收。
- 堆分析:使用 Shark 引擎分析泄露对象的引用链。
- 输出报告:生成详细的泄露路径报告,方便开发者定位问题。
LeakCanary 的源码设计清晰、高效、易于扩展,是学习 Android 内存管理的绝佳工具。