课程概述

大家好,我是这门课的主讲。今天咱们从第一章节开始,聊聊这门课到底要干什么。你可能遇到过这样的场景: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 持有了一大堆回调引用,源头是一个没取消注册的广播接收器。

小技巧: MAT 里的 OQL 很像 SQL,可以写查询过滤对象。比如 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。

注意: BLE 的 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泄漏模式 MAT分析工具 内存泄漏链(核心概念) 1. 复现泄漏 2. 抓取hprof 3. MAT导入分析 4. 定位泄漏链 说明:箭头表示知识依赖关系,虚线表示分析流程回溯到核心概念。 最终目标:从MAT堆转储中识别并修复Kotlin BLE泄漏链。

课程学习路径总结:

  1. 先理解 BLE 通信模型中可能造成泄漏的点(如回调、生命周期)。
  2. 再掌握 Kotlin 中几种隐式引用(lambda 捕获、object、协程)。
  3. 最后用 MAT 把两者串起来,通过堆转储找到泄漏链的源头。

整门课会围绕一个真实的 BLE 通信 Demo 逐步演练,你可以边看边动手。