Android BLE 能否不使用 GATT 建立 L2CAP CoC?
结论
BLE 链路建立后,可以使用 L2CAP Connection oriented Channel(CoC)传输数据,不需要通过 GATT 传输 CoC 数据。Android 可以调用 BluetoothDevice.createL2capChannel(psm) 发起 CoC 连接。若双方事先约定了 PSM,且外设已在该 PSM 上注册并监听,理论上无需通过 GATT 交换 PSM。
不过,createL2capChannel() 返回 socket,并不表示外设已经接受连接。socket.connect() 抛出 IOException,说明 CoC 连接未能建立。仅凭现有日志,还无法判断原因来自 Android、蓝牙栈还是外设配置。
先核对两端的 CoC 条件
- 确认 Android API 和设备支持。
BluetoothDevice.createL2capChannel(int psm)从 Android API 29 开始提供。运行设备也必须支持 BLE CoC。若应用运行在更早的 API 版本,应先检查 API 是否可用。 - 确认外设已注册并监听同一个 LE PSM。
0x0081必须是外设蓝牙栈接受的 LE PSM,且 CoC 服务端已成功注册并处于可接受连接的状态。只在 Android 端填写这个数值,不会自动在外设上创建监听端点。 - 确认连接事件来自 CoC 层。 GAP 连接成功只说明 BLE 链路已建立,不代表 CoC 已连接。请检查外设 SDK 和所用 SoftDevice 的文档及示例,确认对应栈支持 LE CoC,并查看其 L2CAP 注册、连接请求、接受或拒绝事件。仅凭“使用 nrf5 SDK”无法判断具体的 SDK 和 SoftDevice 组合是否支持所需功能。
- 核对 PSM 约定和连接顺序。 如果 PSM 不是固定值,外设需要把它提供给 Android。可以通过 GATT 等方式交换 PSM,但这只是传递参数的一种方式,CoC 数据仍可通过 CoC 传输。使用固定值时,客户端和服务端也必须使用同一个值,并确保服务端已注册该 PSM。
根据现有日志排查
Android 找到设备后调用 connect(),随后出现 read ret: -1。外设记录到 BLE_GAP_EVT_CONNECTED,Android 等待期间又出现 BLE_GAP_EVT_DISCONNECTED。这些日志表明 BLE 链路曾建立,之后断开;但没有显示 CoC 服务端是否注册、是否收到连接请求,或是否拒绝了请求。因此,现有日志不足以认定 Android 蓝牙栈没有发送请求。
建议增加或检查外设 CoC 层的日志,至少确认服务端初始化和 PSM 注册结果、是否收到 CoC 连接请求、接受或拒绝的结果,以及断开原因。如果外设没有 CoC 层日志,先核实当前 SDK/SoftDevice 是否支持 CoC,以及服务端配置是否正确。如果请求已到达但被拒绝,再检查 PSM、服务端状态和栈返回的原因。
Android 端调用注意事项
BluetoothSocket.connect() 会阻塞,应放在工作线程中执行。还需要处理设备断连、socket 关闭和异常,并根据应用的目标 Android 版本声明和请求相应的蓝牙权限。不要用 !! 隐藏 socket 创建失败的情况。
private fun connectL2cap(device: BluetoothDevice, psm: Int) {
Thread {
val socket = device.createL2capChannel(psm)
try {
socket.connect()
// 连接成功后再通过 socket.inputStream / socket.outputStream 通信
} catch (e: IOException) {
Timber.e(e, "L2CAP CoC connection failed")
} finally {
try {
socket.close()
} catch (e: IOException) {
Timber.w(e, "Failed to close L2CAP socket")
}
}
}.start()
}
这段示例展示了调用方式和异常处理。实际接收数据时,应等 connect() 成功后再读写流。使用线程池或协程时,也要确保连接和 I/O 操作不在主线程运行。
关于是否需要 GATT
GAP 用于发现设备并建立 BLE 连接;GATT 是另一种应用层数据通道,L2CAP CoC 则是独立的面向连接通道。如果已知设备地址和 PSM,可以不通过 GATT 交换连接参数。但这不能代替外设 CoC 服务端注册,也无法证明目标硬件和软件栈支持该功能。
备注:内容仅供参考。
