PAYATHON 2026

Android BLE 按正确 UUID 查找不到 micro:bit 服务,如何排查?

数据阿明

结论:Android 当前发现的服务列表中没有目标 UUID

日志显示,本次 Android 服务发现结果中没有 e95d0753-251d-470a-a062-fa1922dfa9a8。gatt.getService() 返回 null,与这份服务列表一致。

UUID 字符串与打印出的高、低 64 位相互对应,没有看到 UUID 解析错误的证据。调整 UUID 大小写或交换高低位,无法解决当前的服务列表差异。

现有材料还不足以解释电脑和手机为何看到了不同的服务,也不足以将原因归结为某项权限、Android 14、OnePlus 系统或 micro

固件问题。

先区分服务发现成功和目标服务存在

BluetoothGatt.GATT_SUCCESS 表示本次服务发现操作成功完成,但不保证目标服务存在。

设备建立连接并触发 onConnectionStateChange(),也不能证明应用已经具备读取目标 characteristic 的条件。还需要依次确认:

  1. 连接已成功建立。
  2. 服务发现成功完成。
  3. 返回的服务列表包含目标 service UUID。
  4. 该服务包含目标 characteristic UUID。

当前日志显示第 3 步尚未满足,应先查明目标服务为何缺失,再检查读取和 UDP 转发逻辑。

按相同条件比较电脑和手机

电脑曾发现 accelerometer service,只能说明它在那次连接中看到了该服务。建议按以下顺序重新对比:

  1. 断开电脑上的 bleak 连接,并停止可能自动重连的程序。
  2. 确认手机连接的是同一块 micro
    ,同时记录设备地址,避免仅凭设备名称判断。
  3. 保持 micro
    上运行的程序、固件和运行模式一致,分别重新连接电脑与手机。
  4. 两端都在连接后完成 GATT 服务发现,再输出完整的 service 和 characteristic 列表。

比较时应使用连接后发现的 GATT 服务列表。广播数据中的服务 UUID 列表未必完整,不能直接替代服务发现结果。

如果相同条件下电脑仍能发现目标服务,而手机始终缺少它,就有了可重复的对照结果,可以继续调查客户端状态、配对状态和外设行为。

检查外设配置和手机保存的状态

以下几种可能性都需要验证,现有日志尚不能确认其中任何一种。

  • 外设程序或运行模式不同:核对当前 micro
    程序是否启用了 accelerometer service,以及两次测试期间是否重新烧录或切换过模式。
  • 配对或安全状态不同:如果当前固件对服务访问有相关要求,应按照该固件的配置检查两端状态,不能仅凭系统权限已授权就排除这类差异。
  • 手机保留了旧的服务信息:如果曾给同一块 micro
    烧录过不同的 BLE 程序,可以把旧状态作为排查方向,但不能仅凭服务缺失就认定是缓存问题。

可以先结束当前连接,调用旧 BluetoothGatt 实例的 close() 释放资源,再创建新连接并重新发现服务。如果此前已经配对,还可以在明确会清除配对关系的前提下,取消配对、重启外设并重新配对,观察服务列表是否改变。

这些操作有助于缩小排查范围,但不能保证恢复目标服务。也不建议把反射调用隐藏的 refresh() 方法作为正式修复方案。

分别检查 service 和 characteristic

原代码只检查了 service 是否存在。即使找到了 service,getCharacteristic() 仍可能返回 null。此时直接设置 BleStatus.Connected,会让后续读取流程误以为已经准备完成。

可以沿用现有变量和状态类型,将两个检查分开:

override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
    if (status != BluetoothGatt.GATT_SUCCESS) {
        targetCharacteristic = null
        _status.value = BleStatus.Error("Service discovery failed: $status")
        return
    }

    Log.i("mb2o", "device = ${gatt.device.address}")
    gatt.services.forEach { service ->
        Log.i("mb2o", "service = ${service.uuid}")
        service.characteristics.forEach { characteristic ->
            Log.i("mb2o", "  characteristic = ${characteristic.uuid}")
        }
    }

    val serviceUuid = UUID.fromString(targetServiceUuid)
    val characteristicUuid = UUID.fromString(targetCharacteristicUuid)

    val service = gatt.getService(serviceUuid)
    if (service == null) {
        targetCharacteristic = null
        _status.value = BleStatus.Error("Service not found: $serviceUuid")
        return
    }

    val characteristic = service.getCharacteristic(characteristicUuid)
    if (characteristic == null) {
        targetCharacteristic = null
        _status.value = BleStatus.Error(
            "Characteristic not found: $characteristicUuid"
        )
        return
    }

    targetCharacteristic = characteristic
    _status.value = BleStatus.Connected
}

这段代码用于完善诊断和就绪判断,不会让缺失的服务自动出现。应用也需要先取得调用相关 API 所需的权限。

如果应用存在断线重连,还要确认处理的是当前连接对应的 BluetoothGatt 回调,避免将旧连接的结果混入日志。

event service 能否替代 accelerometer service?

不能因为 UUID 接近就直接替换。日志中的 e95d93af-251d-470a-a062-fa1922dfa9a8 对应 event service,它与 accelerometer service 是不同的服务。

event service 能否满足需求,取决于 micro

程序提供的事件及其数据含义。原始需求是反复读取指定 characteristic 并通过 UDP 转发;现有材料没有证明 event service 能提供所需的加速度数据,因此不能把更换 UUID 当作等效修复。

找到目标 characteristic 后,还应确认它支持所需的读取方式。Android GATT 读取是异步操作,轮询时应等待读取回调完成,再安排下一次读取,避免在线程循环中连续提交请求。

备注:内容仅供参考。