Administrator
发布于 2026-08-25 / 19 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 41 篇:Android 蓝牙 BLE 开发实战

上一篇聊了依赖管理,这篇进入一个特定领域——蓝牙低功耗(BLE)。智能手表、健康设备、IoT 传感器都走 BLE 协议,但 Android 的 BLE API 出了名的难用:回调嵌套、状态机复杂、不同手机行为不一致。这篇梳理 BLE 开发的核心流程和常见坑。

BLE 基本概念

BLE 通信涉及几个概念:

  • Central(中心设备):你的手机,发起连接

  • Peripheral(外围设备):手环、传感器等,广播等待连接

  • Service(服务):一组相关功能的集合,用 UUID 标识

  • Characteristic(特征值):数据的最小单位,可读/可写/可通知

  • Descriptor(描述符):特征的配置信息

数据流向:手机连接设备 → 发现 Service → 获取 Characteristic → 读/写数据或监听通知。

权限配置

<!-- Android 12 以下 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 扫描需要 -->

<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

Android 12 把蓝牙权限拆分了,SCAN 和 CONNECT 分开申请。建议同时声明新旧权限,用 maxSdkVersion 兼容:

<uses-permission android:name="android.permission.BLUETOOTH"
    android:maxSdkVersion="30" />

扫描设备

val bluetoothAdapter = (context.getSystemService(Context.BLUETOOTH_SERVICE) 
    as BluetoothManager).adapter

// Android 5.0+
val scanner = bluetoothAdapter.bluetoothLeScanner
val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        val device = result.device
        val rssi = result.rssi // 信号强度
        Log.d("BLE", "Found: ${device.address}, RSSI: $rssi")
    }
}

val settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()

scanner.startScan(emptyList(), settings, scanCallback)

// 5-10 秒后停止
handler.postDelayed({
    scanner.stopScan(scanCallback)
}, 10000)

扫描的坑:

  1. 必须加超时。不主动停止扫描会持续耗电,且某些 ROM 会限制扫描时间。

  2. SCAN_MODE_LOW_LATENCY 功耗大,适合首次连接。持续扫描用 BALANCED 或 LOW_POWER。

  3. 过滤规则。按 UUID 或设备名过滤可以减少回调频率,但不同手机对过滤的支持有差异。建议不过滤,在回调中手动筛。

  4. Android 7+ 重复扫描限制。30 秒内扫描超过 5 次会被系统限流,回调延迟。

连接设备

device.connectGatt(context, false, object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
        when (newState) {
            BluetoothProfile.STATE_CONNECTED -> {
                gatt.discoverServices() // 连接后必须发现服务
            }
            BluetoothProfile.STATE_DISCONNECTED -> {
                gatt.close() // 断开后必须 close
            }
        }
    }

    override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        val service = gatt.getService(UUID.fromString(SERVICE_UUID))
        val characteristic = service?.getCharacteristic(UUID.fromString(CHAR_UUID))
        // 可以开始读/写/订阅
    }
})

连接的坑:

  1. connectGatt 的 autoConnect 参数。传 true 会自动重连,但延迟可能几秒到几分钟。传 false 连接快但不自动重连。建议手动管理重连。

  2. onServicesDiscovered 可能失败。status != GATT_SUCCESS 时要重试或断开重连。

  3. GATT 133 错误。最常见也最让人头疼,通常是连接超时、设备不在线、或协议不匹配。重试 2-3 次仍失败就放弃。

  4. 不能同时连接多个设备操作同一特征值。串行操作,等上一个回调回来再发下一个。

读写数据

// 读
gatt.readCharacteristic(characteristic)

override fun onCharacteristicRead(gatt, characteristic, value, status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        val data = characteristic.value // Android 12 以下
        // 或 value 参数(Android 13+)
    }
}

// 写
characteristic.value = data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
gatt.writeCharacteristic(characteristic)

override fun onCharacteristicWrite(gatt, characteristic, status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        // 写入成功,可以发下一包
    }
}

读写的关键:串行化。BLE 不支持并发操作,必须等上一个操作的回调返回后才能发下一个。用一个队列管理:

class BleOperationQueue {
    private val queue = ArrayDeque<BleOperation>()
    private var pending: BleOperation? = null

    fun enqueue(op: BleOperation) {
        queue.addLast(op)
        if (pending == null) executeNext()
    }

    fun notifyComplete() {
        pending = null
        executeNext()
    }

    private fun executeNext() {
        pending = queue.removeFirstOrNull()
        pending?.execute()
    }
}

通知(Notify)

持续接收数据用 Notify:

// 开启通知
gatt.setCharacteristicNotification(characteristic, true)
val descriptor = characteristic.getDescriptor(
    UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG)
)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)

override fun onCharacteristicChanged(gatt, characteristic, value) {
    // 收到设备推送的数据
    val data = value // Android 13+
}

Notify 是 BLE 最常用的数据接收方式。写 Descriptor 开启通知是必须的,否则收不到数据。

Android 13 的 API 变更

Android 13(API 33)对 BLE API 做了较大调整,readCharacteristic、writeCharacteristic、writeDescriptor 等方法签名变了,直接传入 value 而不是设到对象上:

// Android 13+
gatt.writeCharacteristic(characteristic, data, WRITE_TYPE_DEFAULT)

// 旧 API
characteristic.value = data
gatt.writeCharacteristic(characteristic)

兼容方案:用 @RequiresApi 注解或封装一个 BleHelper 做版本判断。

小结

Android BLE 开发的核心是理解状态机 + 串行化操作。回调嵌套虽然丑但没好办法,用协程或队列封装可以改善。连接状态管理、GATT 133 错误处理、多设备并发是三大难点。建议在正式开发前先用 nRF Connect 之类的工具验证设备协议,再写代码。

下一篇聊聊 CameraX:从 Camera2 到 CameraX 的迁移。


评论