欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 蓝牙4.0 BLE问题总结

程序员文章站 2024-03-24 22:04:28
...

最近做蓝牙4.0相关项目的时候遇到了很多的问题,所以在此记录并总结一下。

问题总结说明

1、首先使用蓝牙必须先打开设备的蓝牙,android为我们提供了两种打开方式,强制打开和非强制打开,如下:

/**
 * 强制打开蓝牙
 */
fun openBluetooth(): Boolean = mBluetoothAdapter!!.enable()

强制打开:这种打开方式在绝大多数手机上是可以直接打开用户的蓝牙的,不会给用户弹框提示;但是有少部分手机还是会弹出提示框,要用户允许后才会打开蓝牙。这种方式太过于流氓,所以我们一般都是使用的非强制打开方式。

注意:使用上面的方法打开的蓝牙是异步的,它会立即给你返回,但是从 off 到 on 的过程需要一个时间,所以只能通过系统broadcast发出的intent里的state判断蓝牙是否已经打开。

/**
 * 非强制打开蓝牙
 */
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, 101)

非强制打开:这种方式会弹框提示用户,用户点击允许后才会打开蓝牙。在实际使用中我们一般采用这种方式。

注意:在使用非强制打开也是一样的,我们首先需要通过onActivityResult判断用户是否点击了允许,如果点击了允许,那么就通过系统broadcast发出的intent里的state判断蓝牙是否已经打开。

2、在Android M中为了能扫描到BLE设备必须要开启定位权限,同时要保证用户的GPS是打开状态,开启了定位权限但是关闭了GPS也同样的扫描不到设备。以下为检测GPS是否打开的方法:

 /**
  * 判断GPS是否打开
  */
fun isOpenGPS(): Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val locationManager = SmarttaGo.instance.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        // 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快)
        val gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
        // 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位)
        val network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
        return gps || network
    }
    return true
}

如果检测到GPS没有打开,那么必须引导用户打开,这样才能扫描到设备。

3、扫描尽量不要放在主线程进行,可以放入子线程里。不然有些机型会出现 do too many work in main thread。

4、Android L换了一套扫描设备的API:BluetoothLeScanner.startScan(ScanCallback),而Android L以下使用的是BluetoothAdapter.startLeScan(LeScanCallback)进行设备的扫描。

5、扫描是一个很耗电的操作,所以扫描时间不应该太长,太长会导致手机发热电量急剧下降。

6、不要在扫描回调中做太多的事情,特别是周围的BLE设备多的时候,不然引起报错。

7、蓝牙的回调都是在线程中的,所以你不能直接在蓝牙的回调里面进行UI操作,你必须切换到主线程中才能进行UI操作。

8、connet、disconnet和connectGatt最好都在UI线程中操作,不然会有的手机可能会引起意想不到的出错。

9、读写Characteristic、Descriptor等几乎所有BLE操作结果都为异步返回,若不等待上一次操作结果返回就执行下一次操作,很可能导致操作失败或者操作无效。onDescriptorWrite()返回的线程与写入线程为同一个线程,别的操作一般在不同的线程回调。

10、任何出错,超时,用完就马上调用Gatt.disconnect(), Gatt.close()释放资源。不然可能导致,下次无法连接。

11、部分在手机,多次断开–扫描–断开–扫描,会导致扫描不到设备,此时只需要在断开连接后,过2秒左右再进行下一次扫描,就能扫描到设备了。出现这个问题可能是因为上次扫描占用的资源还没有完全释放,从而导致下一次无法扫描到设备,所以当过2秒让资源完全的释放后就可以正常扫描了。

12、有的手机发现不了service,可能是因为你复用了之前的gatt来重连,重连后就导致了发现不了service,这种情况下,只要断开连接就close gatt,下次连接时打开全新的gatt,这样就可以发现service了。

13、一个主设备可以同时连接多个从设备(一般为6个,超过就连接不上了),一个从设备只能被一个主设备连接,一旦从设备连接上主设备,就停止广播,断开连接则继续广播。在任何时刻都只能最多一个设备在尝试建立连接。如果同时对多个蓝牙设备建立Gatt连接请求,前面的设备连接失败了,则后面的设备的请求会被永远阻塞住,不会有任何连接回调。所以如果要对多个设备发起连接请求,最好做好同步管理。

14、有时候蓝牙协议栈出现异常可能收不到回调,所以我们要对每个操作做超时检查,否则后面的所有操作都会被阻塞。超时后最好closeGatt,重新建立新的连接。

15、在写入数据时,需要做同步管理,必须一次写完后再进行下一次写入,不然会导致数据的丢失。

16、设备的gatt在不用时要及时关闭,系统支持的连接句柄数是有限的,当达到上限后就无法再建立新的连接了。

17、如果发现连接上了,service也discover到了,但是始终不能触发onCharacteristicChanged,一定要查找如下2个重要原因:

(1) 一定要gatt.setCharacteristicNotification(characteristic, enable)。
(2) 如果设置了(1)还是没有触发,那么对此Characteristic的descriptor做descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUEe就应该可以了。

18、BLE的特征一次读写最大长度20字节。

19、Android会对连接过的BLE设备的Services进行缓存,若设备升级后Services等有改动,则程序会出现通讯失败。此时就需要刷新缓存,但是刷新缓存的方法并没有开放,这里只能使用反射来调用BluetoothGatt类中的refresh()方法。如下:

fun refreshBluetoothCach(): Boolean {
    if (mBluetoothGatt != null) {
        try {
            val localMethod = mBluetoothGatt!!.javaClass.getMethod("refresh")
            if (localMethod != null) {
                return localMethod.invoke(mBluetoothGatt) as Boolean
            }
        } catch (localException: Exception) {
            Logger.e("refreshServices()", "An exception occured while refreshing device")
        }
    }
    return false
}