课程概述
大家好,我是这门课的主讲。今天咱们从第一章节开始,聊聊这门课到底要干什么。你可能遇到过这样的场景:App 里有个 BLE 连接,用着用着就卡死了,或者一断开就再也连不上。查了半天日志,发现是内存泄漏。更头疼的是,用 LeakCanary 只告诉你泄漏了某个 Activity,但根源在哪?一堆匿名内部类、协程、回调,绕来绕去,谁也说不清。
嗯,这门课就是来解决这个痛点的。我会带你把 MAT(Eclipse Memory Analyzer)、BLE 通信 和 Kotlin 特性 这三件事串成一条链,教你怎么从 MAT 的堆转储里揪出那个隐藏的泄漏根。说白了,就是让工具替你说话,而不是靠猜。
MAT 分析是什么?
MAT 全称 Eclipse Memory Analyzer,它是个 Java 堆转储分析工具。我个人习惯在 Android 上用 hprof 文件,然后丢给 MAT 来啃。为什么不用 LeakCanary?当然可以用,但它只能告诉你「这里有泄漏」,却很难告诉你「这条泄漏链是怎么长出来的」。MAT 能展示对象之间的引用关系,以及 GC Root 的路径。我曾在项目里遇到一个诡异的 BLE 回调泄漏,LeakCanary 报了个 Service,但 MAT 一看,发现有个 Kotlin 的 object 持有了一大堆回调引用,源头是一个没取消注册的广播接收器。
select * from instanceof android.bluetooth.BluetoothGattCallback 就能直接找到所有存活的回调实例。
BLE 通信的坑在哪里?
BLE(低功耗蓝牙)在 Android 上主要靠 BluetoothGatt 和回调。你注册一个 BluetoothGattCallback,然后系统在连接、发现服务、读取特征值等事件触发时调用它。但这个回调的注册生命周期经常被忽略。比如,你在 Activity 里创建了 BluetoothGatt,但 activity 销毁时没调用 close(),或者回调被匿名类持有。Kotlin 里更容易出事——因为 lambda 表达式会捕获 this,一不小心就把整个 Activity 拉进了泄漏链。
我曾经遇到过一个问题:一个 Fragment 里调用了 BLE 扫描,扫描回调里用了个 Handler 延迟执行,Fragment 离开了,但扫描没停。最终那个 Handler 持有了 Fragment 的引用,整个 Fragment 树都泄漏了。你想想看,这种问题在日志里根本看不到任何异常,只有内存不断增长,直到 OOM。
BluetoothGattCallback 是抽象类,你可能会把它写成匿名对象或 lambda。如果这个对象被某个长期存活的 Service 或 Application 引用,泄漏就悄悄发生了。
Kotlin 内存泄漏链是什么?
这个概念是我在做性能优化时总结出来的。传统的内存泄漏视图是单个对象 -> GC Root 的路径,但在 Kotlin 和协程的世界里,泄漏经常是一条链:A(Activity)持有了 B(Fragment),B 持有了 C(ViewModel),C 持有了 D(Repository),D 持有了 E(BluetoothGatt),而 E 的回调又持有了 A——形成了一个闭环。这条链里,每个节点都可能是因为 by lazy、object 声明、companion object、协程作用域未取消等原因造成的。
用 MAT 分析这类链,关键一步是找到「强引用环」的入口,然后看哪个 GC Root 不释放你。我习惯用 MAT 的「Java Collections」视图或者「Biggest Objects」视图先定位大对象,再右键 -> Merge Shortest Paths to GC Roots -> exclude all phantom/soft/weak etc. 剩下的就是真正的问题引用。
| 常见 Kotlin 泄漏模式 | 典型表现 | MAT 查找技巧 |
|---|---|---|
object 或 companion object 持有 View |
Activity 销毁后 View 依然在内存 | 搜索 instanceof android.view.View 看父类引用链 |
| 协程 Job 未取消 | ViewModel 泄漏,但 Activity 已销毁 | 找 kotlinx.coroutines.Job 实例,看它的 parent 是谁 |
| BLE 回调 lambda 捕获外部引用 | 断开连接后回调依然被调用 | 看 BluetoothGattCallback 的 this$0 字段 |
| 内部类 / 匿名类隐式持有外部类 | 单例中注册了匿名回调 | MAT 的 List objects > with outgoing references |
课程目标与学习路径
这门课不讲虚的。学完以后,你能做到以下几点:
- 自己抓取 BLE 通信过程中的堆转储(hprof)。
- 用 MAT 打开并分析 Kotlin 特有的泄漏链。
- 识别并修复常见的 BLE + Kotlin 泄漏模式。
- 建立一个排查泄漏的「标准操作流程」——你以后不用再挠头了。
下面这张图能帮你快速理解三个知识领域之间的关系,以及我们这门课的学习路径。
课程学习路径总结:
- 先理解 BLE 通信模型中可能造成泄漏的点(如回调、生命周期)。
- 再掌握 Kotlin 中几种隐式引用(lambda 捕获、object、协程)。
- 最后用 MAT 把两者串起来,通过堆转储找到泄漏链的源头。
整门课会围绕一个真实的 BLE 通信 Demo 逐步演练,你可以边看边动手。