美文网首页
Android低功耗蓝牙踩坑填坑记录

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

作者: Winter_F | 来源:发表于2018-12-26 17:11 被阅读62次

    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)!!
    }
    

    相关文章

      网友评论

          本文标题:Android低功耗蓝牙踩坑填坑记录

          本文链接:https://www.haomeiwen.com/subject/sqaflqtx.html