android LeakCanary 2.7 启动流程 详解
LeakCanary 2.7 启动监听流程基于appDefaultWatchers源码以你给出的appDefaultWatchers()源码为切入点LeakCanary 2.7 的启动流程可以拆解为自动初始化 → 装配 Watchers → 生命周期 Hook → 引用追踪 → Heap Dump五个阶段。下面按源码层级逐步展开。一、架构设计从appDefaultWatchers看 2.7 的接口化改造你贴出的源码是 LeakCanary 2.6 引入的核心 API它体现了 2.x 的模块化设计哲学kotlinfun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher objectWatcher ): ListInstallableWatcher { return listOf( ActivityWatcher(application, reachabilityWatcher), FragmentAndViewModelWatcher(application, reachabilityWatcher), RootViewWatcher(reachabilityWatcher), ServiceWatcher(reachabilityWatcher) ) }关键设计点设计说明ReachabilityWatcher接口2.6 引入的 SAM 接口由ObjectWatcher实现。四个 Watcher 不直接依赖ObjectWatcher而是依赖接口解耦了监听触发与引用追踪InstallableWatcher接口每个 Watcher 实现install()方法负责注册系统回调或反射 Hook。manualInstall()只需遍历调用it.install()即可统一注册共享objectWatcher四个 Watcher 共享同一个AppWatcher.objectWatcher实例确保所有被追踪对象都在同一个watchedObjectsMap 中管理二、自动初始化链路ContentProvider 触发leakcanary-android:2.7依赖的AndroidManifest.xml中注册了自动初始化入口xmlprovider android:nameleakcanary.internal.AppWatcherInstaller$MainProcess android:authorities${applicationId}.leakcanary-installer android:enabledbool/leak_canary_watcher_auto_install android:exportedfalse /对应的AppWatcherInstaller源码kotlininternal sealed class AppWatcherInstaller : ContentProvider() { internal class MainProcess : AppWatcherInstaller() override fun onCreate(): Boolean { val application context!!.applicationContext as Application AppWatcher.manualInstall(application) // 默认调用watchersToInstall appDefaultWatchers(application) return true } }执行时序Application.attachBaseContext()→ContentProvider.onCreate()→Application.onCreate()。LeakCanary 在Application.onCreate()之前就已经完成所有 Watcher 的注册。三、manualInstall内部执行流程kotlinJvmOverloads fun manualInstall( application: Application, retainedDelayMillis: Long TimeUnit.SECONDS.toMillis(5), watchersToInstall: ListInstallableWatcher appDefaultWatchers(application) ) { checkMainThread() // ① 必须在主线程 if (isInstalled) { // ② 防重入保护 throw IllegalStateException(AppWatcher already installed) } // ③ 加载 LeakCanary 分析引擎如果 classpath 中存在 LeakCanaryDelegate.loadLeakCanary(application) // ④ 遍历安装所有 Watcher watchersToInstall.forEach { it.install() } }关键步骤解析LeakCanaryDelegate.loadLeakCanary(application)通过反射查找InternalLeakCanary类并实例化将AppWatcher.objectWatcher注册为OnObjectRetainedListener。这样当ObjectWatcher检测到 retained 对象时能触发后续的 heap dump。watchersToInstall.forEach { it.install() }调用你列出的四个 Watcher 的install()方法分别向系统注册生命周期监听。四、四大 Watcher 的注册与实现细节1.ActivityWatcherkotlininternal class ActivityWatcher( private val application: Application, private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher { private val lifecycleCallbacks object : Application.ActivityLifecycleCallbacks { override fun onActivityDestroyed(activity: Activity) { reachabilityWatcher.expectWeaklyReachable( activity, Activity received onDestroy() callback ) } // 其他生命周期方法为空实现 } override fun install() { application.registerActivityLifecycleCallbacks(lifecycleCallbacks) } }机制通过Application.registerActivityLifecycleCallbacks()监听所有 Activity 的onDestroy()销毁时立即将 Activity 实例交给ObjectWatcher追踪。2.FragmentAndViewModelWatcherkotlininternal class FragmentAndViewModelWatcher( private val application: Application, private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher { override fun install() { application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { // 注册 Fragment 生命周期回调 (activity as? FragmentActivity)?.supportFragmentManager ?.registerFragmentLifecycleCallbacks(fragmentCallbacks, true) } }) } private val fragmentCallbacks object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentDestroyed(fm: FragmentManager, fragment: Fragment) { reachabilityWatcher.expectWeaklyReachable(fragment, Fragment destroyed) // 同时追踪 Fragment 的 View fragment.view?.let { view - reachabilityWatcher.expectWeaklyReachable(view, Fragment view destroyed) } } override fun onFragmentViewDestroyed(fm: FragmentManager, fragment: Fragment) { // ViewModel 监听通过 ViewModelStore 实现 watchViewModel(fragment) } } }机制利用FragmentManager.FragmentLifecycleCallbacks监听Fragment.onDestroy()Fragment 的 View 和 ViewModel 分别独立追踪ViewModel 通过ViewModelStore的onCleared()回调触发追踪3.RootViewWatcher2.7 改进版kotlininternal class RootViewWatcher( private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher { override fun install() { // 2.7 使用 Curtains 库替代直接注册 OnAttachStateChangeListener Curtains.onRootViewsChangedListeners OnRootViewRemovedListener { rootView - if (rootView.windowType WindowType.PHONE_WINDOW) { // 2.7 默认忽略 PopupWindow 和 Dialog 的 root view // 可通过资源 bool leak_canary_watcher_watch_dismissed_dialogs 开启 if (rootView.watchDismissedDialogs || rootView !is DialogRootView) { reachabilityWatcher.expectWeaklyReachable( rootView, View detached from window ) } } } } }2.7 关键改进引入Curtains库来统一监听 Window/RootView 的添加和移除解决了 2.6 中误报 PopupWindow/Dialog 的问题。默认行为✅ 继续检测 Toast 的 root view 泄漏❌ 忽略PopupWindow创建的 root view❌ 忽略Dialog创建的 root view可通过leak_canary_watcher_watch_dismissed_dialogs资源布尔值开启4.ServiceWatcherkotlininternal class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher { override fun install() { // Android SDK 没有 Service 生命周期公开 API通过反射 Hook IActivityManager val activityThreadClass Class.forName(android.app.ActivityThread) val activityThread activityThreadClass.getMethod(currentActivityThread).invoke(null) // Hook AMS 的 serviceDoneExecuting 方法 val iActivityManager ActivityManager.getService() val proxy Proxy.newProxyInstance( iActivityManager.javaClass.classLoader, arrayOf(Class.forName(android.app.IActivityManager)) ) { _, method, args - if (method.name serviceDoneExecuting) { val token args[0] as IBinder // 通过 token 找到对应的 Service 实例 val service servicesByToken[token] service?.let { reachabilityWatcher.expectWeaklyReachable( it, Service received Service#onDestroy() callback ) } } method.invoke(iActivityManager, *args) } // 替换 ActivityThread 中的 mInstrumentation 或直接使用反射注入 proxy } }机制由于 Android SDK 未暴露 Service 销毁的公开监听 APIServiceWatcher通过反射 动态代理 HookIActivityManager来拦截serviceDoneExecuting()调用从而感知 Service 销毁。五、ObjectWatcher引用追踪核心四个 Watcher 最终都调用reachabilityWatcher.expectWeaklyReachable(watchedObject, description)而默认的reachabilityWatcher就是AppWatcher.objectWatcherkotlinval objectWatcher ObjectWatcher( clock { SystemClock.uptimeMillis() }, checkRetainedExecutor { // 默认延迟 5s 后执行检查 mainHandler.postDelayed(it, retainedDelayMillis) }, isEnabled { true } )ObjectWatcher内部使用ReferenceQueueRetainedObjectTrackerkotlinclass ObjectWatcher : ReachabilityWatcher { private val watchedObjects mutableMapOfString, KeyedWeakReference() private val queue ReferenceQueueAny() private val onObjectRetainedListeners mutableListOfOnObjectRetainedListener() override fun expectWeaklyReachable(watchedObject: Any, description: String) { val key UUID.randomUUID().toString() // 1. 创建带 key 的弱引用绑定到 ReferenceQueue val weakRef KeyedWeakReference(watchedObject, key, description, queue) watchedObjects[key] weakRef // 2. 延迟 5s 后检查 checkRetainedExecutor.execute { checkRetainedObjects(key) } } private fun checkRetainedObjects(key: String) { removeWeaklyReachableObjects() // 清除已入队的引用 val retainedRef watchedObjects[key] if (retainedRef ! null) { // 3. 手动触发 GC gcTrigger.runGc() removeWeaklyReachableObjects() if (watchedObjects.containsKey(key)) { // 4. GC 后仍未回收 → 通知 LeakCanary onObjectRetainedListeners.forEach { it.onObjectRetained() } } } } }检测逻辑目标对象销毁时创建KeyedWeakReference带唯一 key延迟 5s 后检查ReferenceQueue若引用已入队 → 对象被回收无泄漏若未入队 → 手动触发Runtime.getRuntime().gc()GC 后仍未入队 → 对象被保留retained通知所有OnObjectRetainedListener六、从 Retained 到 Heap Dump 的衔接LeakCanaryDelegate.loadLeakCanary()会将InternalLeakCanary注册为OnObjectRetainedListenerkotlininternal object InternalLeakCanary : OnObjectRetainedListener { override fun onObjectRetained() { scheduleRetainedObjectCheck() } private fun scheduleRetainedObjectCheck() { backgroundHandler.postDelayed({ val retainedCount AppWatcher.objectWatcher.retainedObjectCount when { retainedCount 0 - return // 前台且未达到阈值仅显示通知 appVisible retainedCount LeakCanary.config.retainedVisibleThreshold - showRetainedCountNotification(retainedCount) // 后台或达到阈值执行 heap dump else - dumpHeap() } }, 2000) } }触发条件应用进入后台 → 立即 dump前台运行 → retained 对象数达到阈值默认 5 个才 dump七、手动安装与自定义关闭自动初始化如需关闭自动初始化并自定义 Watchersxml!-- res/values/leak_canary.xml -- resources bool nameleak_canary_watcher_auto_installfalse/bool /resources然后在Application.onCreate()中手动安装kotlinclass DebugApplication : Application() { override fun onCreate() { super.onCreate() // 示例 1排除 ServiceWatcher避免反射 Hook 带来的 ANR 风险 val watchersToInstall AppWatcher.appDefaultWatchers(this) .filter { it !is ServiceWatcher } AppWatcher.manualInstall( application this, watchersToInstall watchersToInstall ) // 示例 2自定义 ReachabilityWatcher 过滤特定类 val delegate ReachabilityWatcher { watchedObject, description - if (watchedObject !is BadSdkLeakingFragment) { AppWatcher.objectWatcher.expectWeaklyReachable(watchedObject, description) } } val customWatchers AppWatcher.appDefaultWatchers(this, delegate) AppWatcher.manualInstall(application this, watchersToInstall customWatchers) } }八、完整启动时序图应用进程启动 │ ▼ Application.attachBaseContext() │ ▼ ContentProvider.onCreate() ──► AppWatcherInstaller$MainProcess │ │ │ ▼ │ AppWatcher.manualInstall(application) │ │ │ ┌───────────────┼───────────────┐ │ ▼ ▼ ▼ │ LeakCanaryDelegate.loadLeakCanary() appDefaultWatchers(application) │ │ │ │ ▼ ▼ │ InternalLeakCanary.registerListener() [ActivityWatcher, FragmentAndViewModelWatcher, │ RootViewWatcher, ServiceWatcher] │ │ │ ▼ │ watchers.forEach { it.install() } │ │ │ ┌───────────────────────────────┼───────────────┐ │ ▼ ▼ ▼ ▼ │ registerActivityLifecycle registerFragmentLifecycle Curtains.onRootViewsChanged Hook IActivityManager │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ Application.onCreate() ◄──────────────────────────────────────────────────────────────────── │ ▼ 应用正常运行 │ ▼ Activity/Fragment/View/Service 销毁 │ ▼ reachabilityWatcher.expectWeaklyReachable(obj, desc) │ ▼ ObjectWatcher ──► KeyedWeakReference ReferenceQueue │ ▼ 延迟 5s ──► 检查 ReferenceQueue │ ├── 已清除 ──► 无泄漏 │ └── 未清除 ──► 触发 GC ──► 再次检查 │ ├── 已清除 ──► 无泄漏 │ └── 仍保留 ──► InternalLeakCanary.onObjectRetained() │ ▼ retainedCount threshold? │ ├── 否前台──► 通知栏提示 retained 数量 │ └── 是 ──► dumpHeap() ──► Shark 分析 ──► 泄漏报告总结LeakCanary 2.7 的启动流程以appDefaultWatchers()为装配中心通过ContentProvider实现零代码侵入自动初始化通过ReachabilityWatcher接口实现 Watcher 与追踪引擎的解耦通过InstallableWatcher.install()统一注册系统生命周期回调。四个默认 Watcher 覆盖了 Android 四大组件/核心对象中的 Activity、Fragment、ViewModel、View 和 Service构成了完整的内存泄漏自动检测体系。

相关新闻

GBase 8a MPP数据库单机部署实战:从环境准备到服务验证

GBase 8a MPP数据库单机部署实战:从环境准备到服务验证

1. 项目概述最近在帮一个做数据分析的朋友搭建测试环境,他那边有个项目需要用到国产的分析型数据库,点名要GBase 8a。说实话,虽然之前接触过不少数据库,但GBase 8a这种大规模并行处理(MPC)架构的数据库&…

2026/9/20 16:26:29 阅读更多 →
从工具到思维:Kali Linux渗透测试实战工作流深度解析

从工具到思维:Kali Linux渗透测试实战工作流深度解析

上周,一个刚转行做安全运维的朋友深夜发来消息,说在网上看了很多“三天速成渗透测试”的视频,工具装了一堆,命令也敲了不少,但真遇到一个模拟的靶机环境,还是不知道从哪里下手。他问我:“是不是…

2026/9/23 17:57:53 阅读更多 →
Git安装配置全指南:从入门到实战

Git安装配置全指南:从入门到实战

1. Git安装与配置全指南作为分布式版本控制系统的标杆,Git已经成为开发者日常工作中不可或缺的工具。无论是个人项目还是团队协作,Git都能高效管理代码变更历史。不同于集中式版本控制系统,Git的分布式架构让每个开发者都拥有完整的代码仓库副…

2026/9/23 18:42:06 阅读更多 →

最新新闻

告别配置地狱:11110实战最佳实践

告别配置地狱:11110实战最佳实践

告别配置地狱:11110实战最佳实践 配置环境就卡半天?这是无数开发者在接手新项目时的真实写照。依赖版本冲突、环境变量缺失、本地与生产环境差异巨大,这些琐碎问题往往比写业务逻辑更耗时。想要彻底解决这个痛点,不能只靠玄学,必须建立一套可复现、…

2026/9/23 18:41:52 阅读更多 →
基于Python的人脸识别门禁系统:从环境搭建到答辩演示

基于Python的人脸识别门禁系统:从环境搭建到答辩演示

简介:基于Python的人脸识别智能门禁系统是一套面向计算机相关专业学生的完整毕业设计项目,适合用作毕业设计、期末大作业或课程设计。代码注释较全,前后端架构清晰,关键模块包含人脸识别与门禁管理流程,且已经过调试&a…

2026/9/23 18:41:51 阅读更多 →
5个最佳实践搞定手机微信打不开

5个最佳实践搞定手机微信打不开

5个最佳实践搞定手机微信打不开 复制来的代码跑不通,报错信息像天书,新手常陷调试泥潭。本文拆解手机微信打不开的高频考点,用最佳实践帮你从入门到精通,面试不慌。 考点梳理…

2026/9/23 18:41:51 阅读更多 →
小米盒子mini折腾全记录:3步搞定,新手避坑指南

小米盒子mini折腾全记录:3步搞定,新手避坑指南

小米盒子mini折腾全记录:3步搞定,新手避坑指南 配置环境就卡半天?别急,很多兄弟买回小米盒子mini,对着说明书发呆,连投屏都连不上。 这真不是你的问题。硬件是死的,系统是活的,网络环境更是千差万别。今天不整虚的,直接上干货。…

2026/9/23 18:41:51 阅读更多 →
融合知识图谱与生成式AI的智能食谱推荐系统构建

融合知识图谱与生成式AI的智能食谱推荐系统构建

简介:这是一个基于知识图谱和生成式AI的智能食谱推荐系统完整工程,面向正在做毕业设计的计算机专业学生,也适合需要项目实战练习的入门者作为课程设计、期末大作业使用。项目采用前后端分离结构,前端以TypeScript/React技术栈呈现…

2026/9/23 18:41:51 阅读更多 →
8683性能优化:告别代码跑不通,高频面试题实战拆解

8683性能优化:告别代码跑不通,高频面试题实战拆解

8683性能优化:告别代码跑不通,高频面试题实战拆解 复制来的代码跑不通,是不是经常卡在这里?不知道哪里错了,调了三天没结果,最后只能硬着头皮去问同事。这其实是很多开发者的日常噩梦,尤其是在准备面试或者接手新项目时,这种“黑盒”状态最让人焦…

2026/9/23 18:40:50 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →