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社区 Google
特点 功能完整,但体积大 轻量,专为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其实就覆盖了蓝牙开发的三个核心场景:

  1. 管理蓝牙开关——检查、开启、关闭
  2. 发现设备——扫描、配对、获取已配对列表
  3. 建立连接——服务端监听、客户端连接

我画了一张图,帮你理清它们之间的关系:

Android蓝牙核心架构图 应用层(你的App) 调用BluetoothAdapter API,处理广播和回调 Android Framework层 BluetoothManager → BluetoothAdapter → BluetoothDevice Fluoride协议栈 Binder通信 → 蓝牙服务进程 → HCI层 蓝牙硬件层 蓝牙芯片(Broadcom、Qualcomm、Realtek等) 数据流向

这张图你看懂了吗?从上到下,你的App通过BluetoothAdapter调用Framework层的API,Framework层通过Binder和Fluoride协议栈通信,协议栈再通过HCI(Host Controller Interface)和蓝牙芯片交互。整个过程就像一层层剥洋葱。

记住这个原则:你永远不需要直接操作蓝牙芯片。所有操作都通过BluetoothAdapter这个入口。它帮你屏蔽了底层协议的复杂性。

最后,分享一个我常用的调试技巧。当你调用BluetoothAdapter的方法没反应时,先检查这三件事:

  • 权限配了吗?——特别是Android 12+的新权限
  • 蓝牙开了吗?——调用isEnabled()确认
  • 主线程了吗?——部分操作需要在子线程执行

这三步能解决80%的蓝牙问题。剩下的20%,嗯,那就得看具体的错误日志了。


公众号:蓝海资料掘金营,微信deep3321