2、Android蓝牙架构:从协议栈到适配器
好,咱们今天聊聊Android蓝牙的底层架构。说实话,这部分内容看起来有点枯燥,但它是你后面所有蓝牙开发的基础。我当年刚接触蓝牙开发时,就是没搞懂这些底层的东西,结果写出来的代码各种莫名其妙的问题。后来花了整整一周啃源码,才算真正弄明白。
2.1 蓝牙协议栈:BlueZ vs Fluoride
先说说蓝牙协议栈。说白了,它就是蓝牙通信的底层实现,负责处理蓝牙射频、基带、L2CAP这些协议层。Android历史上用过两套协议栈:BlueZ和Fluoride。
| 特性 | BlueZ | Fluoride |
|---|---|---|
| 使用时期 | Android 4.2 之前 | Android 4.2 至今 |
| 架构 | 基于D-Bus的进程间通信 | 基于Socket的Binder通信 |
| 代码位置 | 内核空间 + 用户空间 | 主要在用户空间 |
| 维护方 | Linux社区 | |
| 特点 | 功能完整,但体积大 | 轻量,专为Android优化 |
BlueZ是Linux标准的蓝牙协议栈,功能非常强大。但Google觉得它太重了,不适合移动设备。我记得当时有个同事吐槽说:「BlueZ在嵌入式设备上跑起来,光初始化就要好几秒。」所以从Android 4.2开始,Google换成了自研的Fluoride。
Fluoride这个名字你可能不熟悉,但它的核心就是你现在用的BluetoothAdapter。它把协议栈的大部分逻辑移到了用户空间,通过Binder和系统服务通信。这样做的好处是:
- 启动更快——不用等内核模块加载
- 内存占用更小——按需加载协议层
- 更容易定制——厂商可以替换部分实现
核心要点:你现在写的蓝牙代码,最终都是通过Fluoride协议栈和蓝牙芯片交互的。理解这一点,你就知道为什么有些API在模拟器上跑不通——因为模拟器没有真实的蓝牙硬件和协议栈。
2.2 蓝牙权限声明:别小看这几行
权限声明是蓝牙开发最容易踩坑的地方。我见过太多人上来就写代码,结果运行时直接崩溃,就是因为权限没配好。
Android的蓝牙权限经历了多次变化。从Android 6.0到12.0,每个版本都有调整。咱们直接看最新的最佳实践:
<!-- AndroidManifest.xml -->
<!-- 基础蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ 新增的细粒度权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- 位置权限(扫描蓝牙设备需要) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 声明设备需要蓝牙功能 -->
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
我曾经踩过的坑:在Android 12上只声明了BLUETOOTH和BLUETOOTH_ADMIN,结果调用BluetoothAdapter.startDiscovery()直接抛SecurityException。后来查文档才发现,Android 12把扫描权限从BLUETOOTH_ADMIN拆分成了BLUETOOTH_SCAN。所以一定要根据targetSdkVersion做兼容处理。
权限的运行时请求逻辑,我建议这样写:
// 检查Android版本,分别处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+:请求新的蓝牙权限
requestPermissions(
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.ACCESS_FINE_LOCATION
),
REQUEST_CODE_BLUETOOTH_PERMISSIONS
)
} else {
// Android 11及以下:请求位置权限即可
requestPermissions(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION
),
REQUEST_CODE_LOCATION_PERMISSIONS
)
}
嗯,这里要注意:BLUETOOTH_ADMIN权限在Android 12+已经被废弃了,但为了兼容旧版本,你还是要保留它。我个人的习惯是:所有版本都声明,运行时根据SDK版本动态请求。
2.3 蓝牙适配器(BluetoothAdapter)核心API
BluetoothAdapter是你操作蓝牙的入口。它代表设备本地的蓝牙硬件。说白了,它就是你和蓝牙芯片之间的「翻译官」。
先看怎么拿到它:
// 获取默认蓝牙适配器
val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
// 检查设备是否支持蓝牙
if (bluetoothAdapter == null) {
// 设备不支持蓝牙
return
}
// 检查蓝牙是否开启
if (!bluetoothAdapter.isEnabled) {
// 请求开启蓝牙
val enableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableIntent, REQUEST_ENABLE_BT)
}
小技巧:getDefaultAdapter()返回null的情况很少见,但我在一些低端平板上遇到过。所以一定要做空判断,别偷懒。
BluetoothAdapter的核心API,我按功能分了四类:
| 分类 | API | 说明 |
|---|---|---|
| 设备管理 | getDefaultAdapter() | 获取蓝牙适配器实例 |
| isEnabled() | 检查蓝牙是否开启 | |
| enable() / disable() | 开启/关闭蓝牙(需要系统权限) | |
| 设备发现 | startDiscovery() | 开始扫描周围设备 |
| cancelDiscovery() | 取消扫描 | |
| getBondedDevices() | 获取已配对设备列表 | |
| 连接管理 | listenUsingRfcommWithServiceRecord() | 创建RFCOMM服务端 |
| getRemoteDevice() | 根据MAC地址获取远程设备 | |
| 状态监听 | ACTION_STATE_CHANGED | 蓝牙开关状态变化广播 |
你想想看,这些API其实就覆盖了蓝牙开发的三个核心场景:
- 管理蓝牙开关——检查、开启、关闭
- 发现设备——扫描、配对、获取已配对列表
- 建立连接——服务端监听、客户端连接
我画了一张图,帮你理清它们之间的关系:
这张图你看懂了吗?从上到下,你的App通过BluetoothAdapter调用Framework层的API,Framework层通过Binder和Fluoride协议栈通信,协议栈再通过HCI(Host Controller Interface)和蓝牙芯片交互。整个过程就像一层层剥洋葱。
记住这个原则:你永远不需要直接操作蓝牙芯片。所有操作都通过BluetoothAdapter这个入口。它帮你屏蔽了底层协议的复杂性。
最后,分享一个我常用的调试技巧。当你调用BluetoothAdapter的方法没反应时,先检查这三件事:
- 权限配了吗?——特别是Android 12+的新权限
- 蓝牙开了吗?——调用isEnabled()确认
- 主线程了吗?——部分操作需要在子线程执行
这三步能解决80%的蓝牙问题。剩下的20%,嗯,那就得看具体的错误日志了。