一直都负责项目的蓝牙模块,期间踩过很多坑,说出来跟大家分享一下。

1. 从简单的开始,首先是权限的问题,在Android6.0以上,部分机型需要开启定位权限,部分机型需要同时开启GPS。所以使用蓝牙之前,你可以动态申请定位权限,或者直接将targetSdkVersion设置为23以下。

2. 蓝牙刚开启的时候,建议间隔1s后再进行搜索,有些机型初始化很慢,会搜索不到设备。

3. 始终无法搜索设备,可能是上一次连接残留的蓝牙缓存导致的,重启蓝牙试一试。

4. 搜索方法需要区分Android版本。21以下调用.startLeScan(LeScanCallback),21及其以上调用:

ScanSettings mScanSettings = new ScanSettings.Builder()
                            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                            .setReportDelay(0)
                            .build();
                    List<ScanFilter> mFilters = new ArrayList<>();//搜索蓝牙过滤UUID
                    ScanFilter scanFilter = new ScanFilter.Builder()
                            .setServiceUuid(ParcelUuid.fromString("xxxxxxxxxx"))
                            .build();
                    mFilters.add(scanFilter);
                    if (scanner == null) {
                        scanner = getBluetoothAdapter().getBluetoothLeScanner();
                    }
                    scanner.startScan(mFilters, mScanSettings, ScanCallback);

5. 搜索结束后,最好间隔1s后在连接。部分机型可能会在搜索后刷新蓝牙缓存,导致连接失败。

6. 连接方法也需要做相应的判断处理:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                bluetoothGatt = device.connectGatt(mContext, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE);
            } else {
                bluetoothGatt = device.connectGatt(mContext, false, bluetoothGattCallback);
            }

7. 连接失败,或者连接断开后,必须及时关闭bluetoothGatt,具体操作如下:

public void closeGatt(){
    if (bluetoothGatt != null) {
            refreshGattCache(bluetoothGatt);
            bluetoothGatt.disconnect();
            bluetoothGatt.close();
            bluetoothGatt = null;
    }
}
public static boolean refreshGattCache(BluetoothGatt gatt) {
        boolean result = false;
        try {
            if (gatt != null) {
                Method refresh = BluetoothGatt.class.getMethod("refresh");
                if (refresh != null) {
                    refresh.setAccessible(true);
                    result = (boolean) refresh.invoke(gatt, new Object[0]);
                }
            }
        } catch (Exception e) {
        }
        return result;
    }

8. 在onConnectionStateChange检测到133错误,需要关闭gatt。如果返回BluetoothProfile.STATE_CONNECTED,间隔1s后再调用gatt.discoverServices(),只有在onServicesDiscovered返回BluetoothGatt.GATT_SUCCESS才能说明设备连接成功,其他状态需要关闭gatt,以免下次搜索连接不上设备。

9. 执行notify()后,onCharacteristicChanged方法才能接收到设备返回的数据

//具体的UUID需要参考你们自己的蓝牙协议
try {
            BluetoothGattService BGService = bluetoothGatt
                    .getService(UUID.fromString(serviceUUID));
            if (BGService == null) {
                return;
            }
            BluetoothGattCharacteristic characteristic = BGService
                    .getCharacteristic(UUID.fromString(character));
            if (characteristic == null) {
                return;
            }
            int properties = characteristic.getProperties();
            if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) {
                return;
            }
            bluetoothGatt.setCharacteristicNotification(characteristicUUID, true);
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID);
            if (descriptor != null) {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                bluetoothGatt.writeDescriptor(descriptor);
            }
        } catch (NullPointerException e) {
            LogUtils.e("getService NullPointerException");
        }

10. 关于连接的稳定性,如果是BLE4.0可能会与手机的4G信号相互干扰导致中断,或者是APP长时间未操作蓝牙服务被关闭,还有就是手机蓝牙的连接参数跟设备蓝牙的连接参数不一致导致稳定性变差。连接参数这个无法在客户端修改,只能跟硬件设备协调,但也无法保证所有机型能够百分百适配。APP长时间未操作可采用进程保活的方式,现在比较正式的是加入电池白名单,但部分机型没有此类权限(锤子、360手机等),而且不一定管用;要么就是加入无声音乐,APP很耗电,也违背了Android开发原则,但效果不错。

11. 有时候你明明关闭了gatt,但最后死活搜索不到该设备。原因也很简单,系统并未执行成功,还残留着该gatt的引用。目前本人除了重启手机蓝牙,想不到有更好的方法。在网上看到有个大神写过一篇博客,里面有详细介绍,链接参见下面。


但实际使用中会引发一些问题,比如里面通过反射关闭蓝牙和开启蓝牙,实际使用发现关闭蓝牙后可能会导致手机无法搜索到任何设备,反复调用开启蓝牙才恢复正常(猜测是系统未能对开启的操作执行成功)。听说Android9将取消反射的方法,也不知道是真是假。

12. 一段时间内,不要过于频繁的搜索设备。一方面搜索过程中,不要反复调用搜索的方法,加标识进行判断;另一方面,当次搜索的时长尽可能长一点,比如10s。

当前想到的就只有这些了,后续想到会继续补充。有想法的小伙伴也可以在评论区留下你的建议。