Android低功耗蓝牙踩坑填坑记录

Android 4.3(API Level 18)开始引入Bluetooth Low Energy(BLE,低功耗蓝牙)的核心功能并提供了相应的 API, 应用程序可以通过这些 API 执行扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。

关于BLE的基础介绍和API使用说明,可以参照其他文章(比如这篇《Android BLE 蓝牙开发入门》就写的不错),本文主要关注和记录开发过程中遇到的坑和解决办法。

关于操作线程

扫描、连接、读写等全部蓝牙操作都应该在同一个线程中执行,否则在不同手机上可能会出现各种奇奇怪怪的错误。如果业务操作不频繁,没有要求同时操作多个蓝牙设备的话,可以简单一点处理,把操作都放在UI线程中执行,但更优的方法是创建一个新线程,所有操作都在这个线程中执行。

关于蓝牙状态

扫描蓝牙设备前需要做几步常规操作:

  1. 检查设备是否支持蓝牙
val manager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val adapter = manager.adapter
if(adapter == null){
    toast("设备不支持蓝牙")
}
  1. 6.0以上系统,需要请求 ACCESS_COARSE_LOCATION 权限才能扫描附近的蓝牙设备
ActivityCompat.requestPermissions(
    this,
    arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
    REQUEST_PERMISSION_LOCATION
)
  1. 6.0以上系统,需要打开定位服务才能扫描到蓝牙设备,部分手机还只有开启高精度定位才能扫描,可以用文字提醒用户去开启
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivityForResult(intent, REQUEST_LOCATION)
  1. 打开蓝牙开关
val manager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val adapter = manager.adapter
if(!adapter.enable()){
    val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
    startActivityForResult(intent, REQUEST_BLUETOOTH)
}

关于扫描方法

系统提供了两个扫描蓝牙设备的,分别是BluetoothAdapter和BluetoothLeScanner,其中BluetoothAdapter又提供了两个搜索蓝牙设备的方法startDiscovery、startLeScan,它们的区别如下:

方法 API 搜索范围 效率 其他特点
BluetoothAdapter.startDiscovery 18 经典蓝牙和低功耗蓝牙 搜索低功耗蓝牙的效率很低
BluetoothAdapter.startLeScan 18 低功耗蓝牙
BluetoothLeScanner 21 低功耗蓝牙 接口更灵活

对于BLE设备,如果API支持,最好使用BluetoothLeScanner,否则,应该使用效率更高的startLeScan方法。

val bluetoothManager = mContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val adapter = bluetoothManager.adapter
// 方法一
adapter.startDiscovery()
// 方法二
adapter.startLeScan(mLeScanCallback)
// 方法三
adapter.bluetoothLeScanner.startScan(scanCallback);

关于资源消耗

蓝牙搜索非常消耗电量,在搜索到目标设备时,应该马上停止搜索操作,可以通过设置一个定时器来定时停止扫描操作。

val overtime = 10000; //10秒超时
Flowable.timer(overtime, TimeUnit.MILLISECONDS, Schedulers.newThread())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({
        stopScan()
    }, Throwable::printStackTrace)

关于连接设备

连接设备时,系统会返回一个操作对象Gatt,这个Gatt有几个比较坑的点:

  1. 连接设备时要设置一个BluetoothGattCallback的回调,只有关闭Gatt才能同时关闭回调,如果只是断开了Gatt连接而没有关闭,后续连接上同一个设备时会把全部Callback都回调一次
  2. Gatt提供了重连功能,但部分手机上重连后会发现不到service
  3. 系统支持的连接句柄数是有限的,当达到上限后无法再建立新的连接,所以Gatt在不用时要及时关闭

结合上面三点,最好的使用方式是断开连接后马上关闭Gatt,重新连接时不要使用重连功能,而是创建一个新的Gatt对象。

// 连接设备
mGatt = device.connectGatt(mContext, false, mConnectGattCallbackImpl)

......

// 断开连接
mGatt.disconnect()
mGatt.close()

关于发现服务

部分手机上 discoverServices 的操作很慢,甚至一直不回调 onServicesDiscovered 方法,原因可能是connect interval太大。根据从网上找到的资料,有的手机会主动向设备发起更改connect interval,而有的手机却不会,这样的话connect interval相差就会很大,有的手机是7ms,有的手机是默认的50ms,所以发现service都要8s,甚至20s。
对此,我们有两个解决方法:

  1. 建立连接后,手动修改连接间隔
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    mGatt!!.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
}
  1. 对 discoverServices 方法进行超时检查,一段时间内没有回调 onServicesDiscovered 的话,就重新执行发现服务的操作

实际上,以上两个解决方法是不冲突的,可以同时使用。

关于Notify开关

Gatt提供了开关 Notify 的方法,但对于部分蓝牙设备来说并不足够,我们还必须对Descriptor写入一个数值才能真正完成开关 Notify 的操作,同时要注意,等到onDescriptorWrite被回调才能执行下一个操作。

    private val CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID =
        UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
//设置Char的notify开关
var result = mGatt?.setCharacteristicNotification(char, enable)!!
if (result) {
    val descriptor = char.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID)
    descriptor?.value = if (enable)
        BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
    else
        BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
    result = result && mGatt?.writeDescriptor(descriptor)!!
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容