BLE开发的各种坑

这段时间在做低功耗蓝牙(BLE)应用的开发(并不涉及蓝牙协议栈)。总体感觉 Android BLE 还是不太稳定,开发起来也是各种痛苦。这里记录一些杂项和开发中遇到的问题及其解决方法,避免大家踩坑。本文说的问题有些没有得到官方文档的验证,不过也有一些论坛帖子的支持,也可以算是有一定根据。

  1. Android 从 4.3(API Level 18) 开始支持低功耗蓝牙,但是只支持作为中心设备(Central)模式,这就意味着 Android 设备只能主动扫描和链接其他外围设备(Peripheral)。从 Android 5.0(API Level 21) 开始两种模式都支持。BLE 官方文档在 这里

BluetoothAdapter.startLeScan() 的时候,在 BluetoothAdapter.LeScanCallback.onLeScan() 中不能做太多事情,特别是周围的BLE设备多的时候,非常容易导致出现如下错误:

E/GKILINUX(17741): ##### ERROR : GKIexception: GKIexception(): Task State Table E/GKILINUX(17741): #####
 E/GKILINUX(17741): ##### ERROR : GKIexception: TASK ID [0] task name [BTU] state [1]
 E/GKILINUX(17741): #####
 E/GKILINUX(17741): ##### ERROR : GKIexception: TASK ID [1] task name [BTIF] state [1]
 E/GKILINUX(17741): #####
 E/GKILINUX(17741): ##### ERROR : GKIexception: TASK ID [2] task name [A2DP-MEDIA] state [1]
 E/GKILINUX(17741): #####
 E/GKILINUX(17741): ##### ERROR : GKIexception: GKIexception 65524 getbuf: out of buffers#####
 E/GKILINUX(17741): ##### ERROR : GKIexception:
 E/GKI_LINUX(17741): **********************

开发建议:在 onLeScan() 回调中只做尽量少的工作,可以把扫描到的设备,扔到另外一个线程中去处理,让 onLeScan() 尽快返回。 [参考帖子]

在使用 BluetoothDevice.connectGatt() 或者 BluetoothGatt.connect() 等建立 BluetoothGatt 连接的时候,在任何时刻都只能最多一个设备在尝试建立连接。如果同时对多个蓝牙设备发起建立 Gatt 连接请求。如果前面的设备连接失败了,后面的设备请求会被永远阻塞住,不会有任何连接回调。

开发建议:如果要对多个设备发起连接请求,最好是有一个同一个的设备连接管理,把发起连接请求序列化起来。前一个设备请求建立连接,后面请求在队列中等待。如果连接成功了,就处理下一个连接请求。如果连接失败了(例如出错,或者连接超时失败),就马上调用 BluetoothGatt.disconnect() 来释放建立连接请求,然后处理下一个设备连接请求。 [参考帖子]

对 BluetoothGatt 操作 (read/write)Characteristic(), (read/write)Descriptor()readRemoteRssi() 都是异步操作。需要特别注意的是,同时只能有一个操作(有些贴这说只能同时有一个 writeCharacteristic(),这个我并没有严格验证),也就是等上一个操作回调(例如 onCharacteristicWrite())以后,再进行下一个操作。

开发建议:把这写操作都封装成同步操作,一个操作回调之前,阻塞主其他调用。 [参考帖子]

BLE 设备的建立和断开连接的操作,例如 BluetoothDevice.connectGatt(),  BluetoothGatt.connect(), BluetoothGatt.disconnect()BluetoothGatt.discoverServices()等操作最好都放在主线程中,否则你会遇到很多意想不到的麻烦。

开发建议:对 BluetoothGatt 的连接和断开请求,都通过发送消息到 Android 的主线程中,让主线程来执行具体的操作。例如创建一个 new Handler(context.getMainLooper());,把消息发送到这个 Handler 中。 [参考帖子]

如果你在开发 BLE 应用的时候,有时候会发现系统的功耗明显增加了,查看电量使用情况,蓝牙功耗占比非常高,好像低功耗是徒有虚名。使用 adb bugreport 获取的了系统信息,分析发现一个名叫 BluetoothRemoteDevicesWakeLock 锁持有时间非常长,导致系统进入不了休眠。分析源代码发现,在连接 BLE 设备的过程中,系统会持有 (Aquire)这个 WakeLock,直到连接上或者主动断开连接(调用 disconnect())才会释放。如果BLE设备不在范围内,这个超时时间大约为30s,而这时你可能又要尝试重新连接,这个 WakeLock 有被重新持有,这样系统就永远不能休眠了。

开发建议:对BLE设备连接,连接过程要尽量短,如果连接不上,不要盲目进行重连,否这你的电池会很快被消耗掉。这个情况,实际上对传统蓝牙设备连接也是一样。 [参考帖子]

Android 作为中心设备,最多只能同时连接 6 个 BLE 外围设备(可能不同的设备这个数字不一样),超过 6 个,就会连接不上了。现在 BLE 设备越来越多,其实并不够用,所以在开发的过程中,需要特别的谨慎使用。

开发建议:按照需要连接设备,如果设备使用完了,应该马上释放连接(调用BluetoothGatt.close()),腾出系统资源给其他可能的设备连接。 [参考帖子]

发起蓝牙Gatt连接 BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback),这里有一个参数autoConnect,如果为 true 的话,系统就会发起一个后台连接,等到系统发现了一个设备,就会自动连上,通常这个过程是非常慢的。为 false 的话,就会直接连接,通常会比较快。同样,BluetoothGatt.connect()只能发起一个后台连接,不是直接连接。所以这个地方需要小心。

public boolean connect() {
    try {
        mService.clientConnect(mClientIf, mDevice.getAddress(),
                               false, mTransport); // autoConnect is inverse of "isDirect"
        return true;
    } catch (RemoteException e) {
        Log.e(TAG,"",e);
        return false;
    }
}

开发建议:如果你需要快速连接(通常情况下我们都希望这样),在 connectGatt() 的时候,传入 autoConnect=false 的参数。如果需要调用 BluetoothGatt.connect() 连接,可一通过反射的方式,强制 mService.clientConnect() 发起直接连接,也就是传入参数 isDirect=true[参考帖子]


本文只是一些经验之谈,观点也比较琐碎。这里很多问题都看起来是蓝牙协议栈不完善导致的,或许在后面 Android 升级中会修复这些问题,我这里说的可能不适用了。

Read more

Android 上的低功耗蓝牙实践

这是我在 Droidcon Beijing 2016 和 GDG Devfest 2016 上做的分享,以下是正文: Slide 01 我今天分享的主题是 Android 上低功耗蓝牙的实践。这个主题比较小众。我在过去的一年多的时间里,主要是在做低功耗蓝牙相关的开发。接触过程中发现,BLE 的开发和通常的 Android APP 的开发有点不一样,这里需要访问硬件资源,而且涉及到一些协议相关的内容,而且这方面的资料也比较少。今天我从 Android 开发者的角度,来分享一下低功耗蓝牙开发实践。 Slide 02 今天分享的内容,主要包含如下几个部分:首先对蓝牙和低功耗蓝牙做一个简单的介绍;然后介绍 Android 上对低功耗蓝牙的支持;再介绍一下在 Android 平台上可以开发哪些低功耗蓝牙应用;然后是,开发过程中,可以帮助我们调试的工具;最后,总结一下所谓的 “最佳实践”,低功耗蓝牙开发的一些小经验。 Slide

By Race604

React Native 触摸事件处理详解

触控是移动设备的核心功能,也移动应用交互的基础,Android 和 iOS 各自都有完善的触摸事件处理机制。React Native(以下简称 RN)提供了一套统一的处理方式,能够方便的处理界面中组件的触摸事件、用户手势等。本文尝试介绍 RN 中触摸事件处理。 1. RN 基本触摸组件 RN 的组件除了 Text,其他组件默认是不支持点击事件,也不能响应基本触摸事件,所以 RN 中提供了几个直接处理响应事件的组件,基本上能够满大部分的点击处理需求TouchableHighlight, TouchableNativeFeedback, TouchableOpacity 和 TouchableWithoutFeedback。因为这几个组件的功能和使用方法基本类似,只是 Touch 的反馈效果不一样,所以一般我们用 Touchable** 代替。Touchable** 有如下几个回调方法: * onPressIn:点击开始; * onPressOut:点击结束或者离开; * onPress:单击事件回调; * onLongPress:长按事件回调。 它们的基本使用方法如下,

By Race604

React Native 中 ScrollView 性能探究

1 基本使用 ScrollView 是 React Native(后面简称:RN) 中最常见的组件之一。理解 ScrollView 的原理,有利于写出高性能的 RN 应用。 ScrollView 的基本使用也非常简单,如下: <ScrollView> <Child1 /> <Child2 /> ... </ScrollView> 它和 View 组件一样,可以包含一个或者多个子组件。对子组件的布局可以是垂直或者水平的,通过属性 horizontal=true/false 来控制。甚至还默认支持“下拉”刷新操作。另外还有一个特别赞的特性,超出屏幕的 View 会自动被移除,从而节省资源和提高绘制效率。我们来看如下一个例子: class

By Race604

30 天入门 Android 开发, Google 与你一起圆梦

经常会有朋友让我推荐 Android 开发入门的教程或者视频,我一直是推荐看官方的教程。大部分人或者觉得比较迷茫,或者觉得坚持不下去。这次推荐这个《30 天入门 Android 开发》是 Google 亲自发起的免费教学,以学习小组方式,大家可以一起学习和交流。一个好的开始,是成功的一半。让 Google 工程师带领你一起进入多彩的 Android 开发大门。点击这里 报名。 Android 设备已经随处可见,你想尝试一下在 Android 设备上的开发和创新吗?快来跟随 Google 的步伐,一起学习 Android 入门课吧! Google Study Jams 活动介绍 Study Jams 是一个学习 Google 在线课程的活动。该活动由学员自主发起课程学习小组,带领小组成员入门 Android 开发,最终将

By Race604