Android BLE indication 回调中写入返回 201 怎么处理?
结论
在支持该状态码的 Android API 中,201 对应 BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY,表示发出写入请求时远端设备正忙。这个状态码本身不能证明设备忙是由 Indication confirmation 引起的。Android 没有为 GATT 客户端应用提供专门通知“Indication confirmation 已完成”的回调;收到 onCharacteristicChanged 也不代表应用收到了确认完成事件。
应用层不应依赖某个回调来判断 stack 是否空闲。应将 GATT 操作放入串行队列,避免同时发起多个操作。若写入立即返回 busy,就把命令留在队列中,稍后重试。Android API 文档说明,写操作完成时会调用 onCharacteristicWrite。这个回调对应应用发起的写入,不是远端 indication 的确认。BluetoothStatusCodes · BluetoothGatt
处理方式
在 onCharacteristicChanged 中记录收到的数据并将其入队,不要把回调到达当作 BLE 链路已空闲的信号。由统一的队列调度器发起写入,并确保同一时刻最多只有一个尚未完成的 GATT 操作。写入完成后,在 onCharacteristicWrite 中检查状态,再处理队列中的下一项。
若使用 API 33 及以上版本的写入接口,可以根据返回状态处理 busy:
private val pendingWrites = ArrayDeque<ByteArray>()
private var writeInFlight = false
private fun enqueueWrite(value: ByteArray) {
pendingWrites.addLast(value)
drainWrites()
}
private fun drainWrites() {
if (writeInFlight || pendingWrites.isEmpty()) return
val value = pendingWrites.first()
val status = gatt.writeCharacteristic(
characteristic,
value,
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
)
when (status) {
BluetoothStatusCodes.SUCCESS -> writeInFlight = true
BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY -> {
// 保留队首,稍后重试;实际项目可使用有上限的退避策略。
}
else -> {
// 记录错误并按应用策略处理,避免队列永久卡住。
}
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
writeInFlight = false
if (status == BluetoothGatt.GATT_SUCCESS && pendingWrites.isNotEmpty()) {
pendingWrites.removeFirst()
} else {
// 根据 status 决定重试、丢弃或结束当前会话。
}
drainWrites()
}
示例省略了线程切换、重试计时器和队列清理。BLE 回调和队列状态应由同一线程管理,或通过线程安全机制保护。出现 busy 时不要立即递归重试;可以等回调返回后再把重试任务放入队列,也可以使用逐步增加且有上限的延迟。还应设置最大重试次数,避免持续占用连接。
版本与状态处理
API 33 的 writeCharacteristic(characteristic, value, writeType) 返回状态码;较旧版本常用的重载返回布尔值,表示操作是否成功启动。这两种返回值不能混为一谈。具体调用应根据项目的 minSdk 和目标运行环境适配。无论使用哪个重载,都应等待相应的 onCharacteristicWrite 结果,再发起下一项 GATT 操作。
原问题中的代码在 onCharacteristicChanged 内直接调用 gatt.writeCharacteristic(...)。将命令改为入队,并由调度器串行发送,可以处理这种时序冲突,但不能据此断定每次返回 201 都是由 indication confirmation 引起的。如果 busy 持续出现,还应检查是否有其他并发 GATT 调用,以及 Peripheral 是否仍在处理请求。
备注:内容仅供参考。
