Android低功耗蓝牙(BLE)开发的一点感受

时间:2022-07-19 01:47:33

最近一段时间,因为产品的需要我做了一个基于低功耗蓝牙设备的Android应用,其中碰到了一些困难,使我深深体会到Android开发的难处:不同品牌,不同型号和不同版本之间的差异使得Android应用适配成为一个痛点,尤其是跟硬件相关的,每个厂商在实现Android API的时候,或多或少都会有些差别。这些区别,有些是明显的Bug,有些则是对API理解的差异造成的。

我的开发是基于Android 4.3+ 标准BLE API。Android 4.3之前厂商自己实现的API不在讨论之列。Android 5.0对BLE API进行了改进,但由于基于Android 5.0的智能设备还没有普及,所以我也没有针对Android 5.0进行适配。希望新的API实现可以不仅仅是语义上的统一,在这背后的行为上也应该是一致的,

在这里我分享一些在Android BLE开发过程中遇到的奇怪的事,希望后来者不要走我走过的弯路......

命令执行的顺序

机型 - 红米 1S, Android 4.4.2   / 华为 荣耀6, Android 4.4.2

在BLE设备连接并且做完Service Discovery之后,下一步要做的就是读写设备的Characteristic了,通常在连接后需要读写几个Characteristic。因为我之前做过苹果的开发,这些对BLE读写的命令都是异步,可以并发的,底层实现应该会有一个queue来缓存硬件没有实际执行的命令。于是我想当然的就按照以前的做法实现了对Characteristic的读写,并且在红米 1S上成功运行。可是当我用华为手机测试的时候出错了,我要的数据没有读出来。Debug后发现底层报错,在执行第二个命令时,出现了“有命令在执行中”的错误。请教了伟大的Stack Overflow后才知道,原来有些Android的实现是没有底层的Command Queue的。我在应用层实现了一个简单的Queue,只有当前一个BLE命令执行完成后才进行下一个命令的执行,华为手机的问题就解决了!当然在已经实现了Command Queue的手机上,新的方法也是没有问题的。

所以,同样是实现了Android API,不同的厂商对这背后行为的理解是不同的。如果Google能够不仅对API的语义进行规范,同时也对其行为进行统一,那该多完美!

设备的搜索(Scan)

机型 - Samsung S3 Android 4.3 (机锋ROM)

Android提供了两个API做BLE设备的搜索,一个是带Service UUID过滤,一个不带过滤。我最初的实现选择了带UUID过滤的API,这样效率应该会稍稍高一些吧。在红米1S,华为荣耀6以及魅族MX2上都工作正常,可以搜索到我指定的设备。然而三星S3却无法搜索到任何设备,我百思不得其解,只得再次求助Stack Overflow - 原来不是所有的设备都支持带UUID过滤的设备搜索(怎么会这样?)。我只好使用不带UUID过滤的API了,然后在应用层通过设备名称来过滤我想要的结果。这样一切都好了...吧?

红米1S的奇葩行为

机型 - 红米 1S, Android 4.4.2

俗话说拆了东墙补西墙,用来形容Android开发再恰当不过了,好不容易为一款手机做完了改动,结果原来工作正常的机型却又出了问题。上面为三星S3做过的改动就是一个例子:三星倒是OK了,可是原来一切安好的红米1S却收不到从设备发来的Notification了(读写Characteristic正常)。一开始我很抓狂,不知道为什么红米突然就不工作了,Debug底层也没有报错。万般无奈,只好一点点回退,最后终于发现问题出现在Scan设备的API使用上:如果我用不带UUID过滤的API,红米1S就无法收到从设备发来的Notification!多么奇葩的行为!在红米上我可以搜寻到设备,可以连接,可以发现Service,可以读写Characteristic,却单单无法收到Notification。当我改成带Service UUID过滤的API后,一切就都好了!好吧,这个Bug一般人真的很难理解了,就交给小米去解决吧。

可是我该怎么办呢?只有hack一下,判断机型和版本号,使用不同的BLE Scan API了。太丑陋了!

三星的连接问题

机型 - Samsung Note 2, Android 4.3

三星手机是我做适配时出问题最多的机型了,可能的原因是三星在很早之前就支持了低功耗蓝牙,并且在Android 4.3之前提供了它自己的BLE API。当Android 4.3标准BLE API出来之后,三星做了API的适配,但并不完美。由于条件所限,我们只测试了三星比较老的一些机型,感觉问题还是比较多的,最新的机型以及ROM版本应该会好很多(我们还没有收到关于S5的问题报告)。除了上面讲到的BLE Scan的问题,三星手机对BLE设备的连接也有比较特别的要求:(来自Stack Overflow)某些三星手机在进行BLE连接时,需要在UI thread里调用相应的API。我的应用场景是当手机搜索到设备后自动进行设备连接,在实际的使用中我发现即使是把connect device调用放在UI Thread里,三星手机也是经常不工作的。最后,我在连接设备之前加了一个延时,它就工作了。。。至于是怎么发现的就不啰嗦了,说多了都是泪啊。

Android的BLE API

安卓对于BLE的支持相比iOS来说确实差了许多:API是基于传统Bluetooth API改进的,不支持BLE peripheral模式,在一些API的行为上没有iOS友好,等等。Android 5.0对BLE API进行了重构,并且支持了BLE peripheral模式,希望会大大改善BLE在安卓系统上的体验。

最后贴一段代码,这是用来设置接收设备Notification的,如果不调用writeDescriptor那段代码,notification就不工作。可是谷歌你就不能把它封装在setCharacteristicNotification里么?这个坑不知道害了多少码农啊。。。

  1. /**
  2. * Enables or disables notification on a give characteristic.
  3. *
  4. * @param characteristic Characteristic to act on.
  5. * @param enabled If true, enable notification.  False otherwise.
  6. */
  7. public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
  8. boolean enabled) {
  9. if (mBluetoothAdapter == null || mBluetoothGatt == null) {
  10. Log.w(TAG, "BluetoothAdapter not initialized");
  11. return false;
  12. }
  13. mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
  14. if (enabled && CHARACT_UUID_BATT_LEVEL.equals(characteristic.getUuid().toString()))
  15. {
  16. Log.i(TAG, "setCharacteristicNotification");
  17. BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
  18. UUID.fromString());
  19. descriptor.setValue("00002902-0000-1000-8000-00805f9b34fb");
  20. mBluetoothGatt.writeDescriptor(descriptor);
  21. }
  22. return true;
  23. }