1. 课程概述:Coroutine作用域与蓝牙泄漏的背景
做蓝牙开发的朋友,应该都遇到过这种场景——连接连得好好的,突然页面关了,蓝牙却还在后台偷偷跑着。更头疼的是,过一会儿应用就崩了,Logcat里一堆异常。嗯,我早期做蓝牙项目时就被这个问题折磨过好几次。
说白了,这背后藏着一个很典型的问题:Coroutine作用域管理不当导致的蓝牙资源泄漏。这一章我们先来聊聊背景,看看问题到底出在哪儿。
1.1 蓝牙开发中的生命周期痛点
蓝牙通信不像普通的网络请求,发完就完事了。它需要长期维持连接,持续收发数据。这就带来一个核心矛盾:
- 蓝牙连接:需要长期存活,跨页面、跨组件
- Android组件:Activity/Fragment 说销毁就销毁
你想想看,当用户退出页面时,Activity 走了 onDestroy,但蓝牙还在收数据。如果没有及时取消协程、关闭连接,会发生什么?
- 协程继续运行,持有 Activity 引用 → 内存泄漏
- 蓝牙 GATT 连接未释放 → 资源泄漏
- 系统回收资源时 → 应用崩溃
我个人习惯把这种问题叫作「蓝牙僵尸连接」——表面上看连接还在,实际上已经没人管它了。
1.2 Coroutine作用域到底是什么?
很多新手会把 CoroutineScope 当成一个「启动协程的工具」。其实它的核心作用是管理协程的生命周期。
看个最简单的例子:
// 错误示范:全局作用域
class BleManager {
private val scope = CoroutineScope(Dispatchers.IO)
fun connect() {
scope.launch {
// 蓝牙连接逻辑
}
}
}
这段代码问题在哪儿?scope 用的是全局作用域,永远不会被取消。蓝牙协程会一直活着,哪怕页面已经销毁了。
1.3 泄漏是怎么一步步发生的?
从「正常连接」到「泄漏崩溃」,其实就几步路:
- 建立连接:蓝牙连上了,启动协程做数据交互
- 用户退出:Activity 走到
onDestroy - 作用域未取消:协程还在后台跑,持有 View 引用
- 资源无法释放:GATT 连接、Socket、文件描述符全卡着
- 系统回收:触发
OOM或ANR,应用崩溃
我曾经在某个项目中踩过这个坑。那时候刚用协程不久,觉得 launch 就完事了,根本没考虑取消。结果线上反馈说「蓝牙页面退出后再进,越来越卡」。一查内存,好家伙,每个页面泄漏了 3MB 左右。
1.4 一个真实的案例数据
我整理了一下当时项目中的泄漏数据,大家可以看看:
| 场景 | 作用域管理 | 内存占用 | GATT 泄漏数 | 稳定性 |
|---|---|---|---|---|
| 页面 A | ❌ 未绑定生命周期 | 泄漏 3.2 MB | 2 个 | 低 |
| 页面 B | ❌ 手动管理不彻底 | 泄漏 1.1 MB | 1 个 | 中 |
| 页面 C | ✅ 使用 LifecycleScope | 0 泄漏 | 0 个 | 高 |
看到了吧?就差了那么几行代码,稳定性天差地别。
1.5 课程能帮你解决什么?
这套课程不是为了讲理论,而是实打实地帮你把问题搞定。我会带你:
- 搞清楚 CoroutineScope、Job、CoroutineContext 在蓝牙场景下的正确用法
- 掌握 Android 生命周期绑定 的三种常见模式
- 学会 蓝牙 GATT 连接的正确关闭流程
- 用 LeakCanary + StrictMode 排查泄漏问题
嗯,这一章我们就铺垫到这儿。概念清楚了,后面才好动手。
这张图把「正常流程」和「泄漏流程」画得很清楚。左边是安全的——作用域取消了,资源释放了。右边是出事的——作用域没取消,蓝牙泄漏,最终崩溃。
后面的章节,我会针对图中每个节点展开来讲:怎么写代码才能走到左边绿色路径,怎么避免踩进右边红色陷阱。