Kotlin中BLE设备discoverServices()返回服务列表为空的问题排查求助
问题分析与解决方案
首先,你的代码里存在两个核心问题,直接导致了服务列表始终为空:
1. 误解了discoverServices()的异步特性
BluetoothGatt.discoverServices()是异步发起的操作,它返回的boolean值只是告诉你「请求是否成功提交给蓝牙栈」,而不是「服务已经发现完成」。真正的服务发现结果,必须通过BluetoothGattCallback的onServicesDiscovered()回调来接收——这是Android BLE框架规定的唯一正确时机。
2. 代码时序完全错误
看你的代码片段:
Handler(Looper.getMainLooper()).post { var temp = bluetoothGatt?.discoverServices() addGlog("discordservice() returned ${temp.toString()}") } addGlog("handler discover service reached an end") val gattServices: List<BluetoothGattService> = gatt.getServices() addGlog("Services count: " + gattServices.size)
这里的坑在于:如果当前代码已经在主线程执行,Handler.post()的任务会被放到主线程消息队列的末尾。也就是说,你先执行了后面的gatt.getServices(),然后才会执行post里的discoverServices()——这时候你获取的是发现服务之前的空列表,结果当然是0。
哪怕当前代码在子线程,discoverServices()发起后也需要几十到几百毫秒才能完成,你立刻同步调用getServices(),结果还是空的。
正确的实现方式
第一步:重写BluetoothGattCallback的回调方法
所有BLE操作的结果都要通过回调接收,服务发现也不例外:
private val gattCallback = object : BluetoothGattCallback() { // 先确保设备连接成功 override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { super.onConnectionStateChange(gatt, status, newState) if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接成功后,才发起服务发现 gatt?.let { Handler(Looper.getMainLooper()).post { val isInitiated = it.discoverServices() addGlog("discoverServices() initiated: $isInitiated") } } } } // 服务发现完成的回调——这才是获取服务的正确时机 override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { super.onServicesDiscovered(gatt, status) if (status == BluetoothGatt.GATT_SUCCESS) { gatt?.let { val services = it.services addGlog("Services count: ${services.size}") services.forEach { service -> addGlog("Service uuid ${service.uuid.toString()}") } } } else { addGlog("Service discovery failed, status code: $status") } } // 其他必要的回调(比如特征读写、通知等)按需实现 }
第二步:发起服务发现的正确姿势
不要在发起请求后立刻同步获取服务,而是等待onServicesDiscovered()回调触发。另外要注意:
- 必须在设备成功连接后再调用
discoverServices(),否则请求会失败 - 确保
BluetoothGatt对象在整个操作周期内有效,不要提前调用close()或disconnect()
额外排查点
如果按照上面的方式还是获取不到服务,可以检查这些地方:
- 权限问题:Android 12+需要
BLUETOOTH_CONNECT权限;Android 6-11需要ACCESS_FINE_LOCATION(因为BLE扫描依赖位置权限),并且要在运行时申请 - 设备兼容性:部分BLE设备可能需要先写入特定的特征值或者开启通知才能暴露全部服务,但Realme Buds 2这类消费级耳机应该不需要
- 蓝牙栈状态:有时候重启手机或BLE设备可以解决一些奇怪的蓝牙栈缓存问题
内容的提问来源于stack exchange,提问作者GAETANO SIMONELLI
相关产品推荐
相关产品推荐

