蓝牙BLE是在Android4.3系统及以上引入的,Android BLE 使用的蓝牙协议是 GATT 协议。
BLE坑
Q:为什么扫描不到手机蓝牙?(排查了好久……)
A:因为手机和电脑普通方式广播的不是ble蓝牙,而手环是ble蓝牙。因此手机只能当ble的主机用。
Q:扫描不到设备
先确定下列几项是否满足:
1、蓝牙是否打开
2、蓝牙相关权限是否授权(6.0以上位置权限)
3、7.0以上手机很多需要手动打开GPS
Q:有时候刚开始扫描还正常,过段时间扫描不到设备?
原因: 出现这个问题的很多是Android7.0以上手机,为什么呢?
因为Google为了防止Android7中的BLE扫描滥用,从而做了一些限制,即不要在30s内对蓝牙扫描 重复开启-关闭超过5次。
建议:
设置扫描周期>6s, 用户点击扫描后不要重复进行扫描,可以做一个是否正在扫描的标志位,如果 正在扫描就不做重复扫描动作了。
Q:为什么有些手机退到后台扫描不到设备?
Android8.0以上退到后台息屏后,为了保证省电等原因,如果不设置ScanFilters的话是默认扫不到设备的,所以解决办法就是设置如下:
mScannerSetting = new ScanSettings.Builder()
//退到后台时设置扫描模式为低功耗
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
mFilters.add(new ScanFilter.Builder()
//过滤扫描蓝牙设备的主服务
.setServiceUuid(ParcelUuid.fromString(“0000ffff-0000-1000-8000-00805f9bfffb”))
.build());
mScanner.startScan(mFilters, mScannerSetting, mScannerCallback);
Q:为什么同时多个设备连接时经常连接不成功?
可能原因:
在使用 BluetoothDevice.connectGatt() 或者 BluetoothGatt.connect() 等建立BluetoothGatt 连接的时候,在任何时刻都只能最多一个设备在尝试建立连接。如果同时对多个蓝牙设备发起建立 Gatt 连接请求。如果前面的设备连接失败了,后面的设备请求会被永远阻塞住,不会有任何连接回调。
建议:
如果要对多个设备发起连接请求,最好建立一个请求队列,前一个设备请求建立连接,后面请求在队列中等待上调用BluetoothGatt.disconnect()/close()来释放建立连接请求,然后处理下一个设备连接请求。
Q:为什么有时候连接成功了,但是发现不了服务及特征值,进而影响数据的接收和发送。
连接成功后,会进行BluetoothGatt.discoverServices()去发现服务,进而设置特征值等,因为该方法是在主线程中执行的,所以为了连接过程的可靠性,建议不要在该过程中,在主线程中不要处理太多的操作(尤其是频 繁绘制操作)。
Q:为什么连接成功后,过不一会又断开了?
这个问题其实并不主要是客户端的问题,所以不要一味的在代码中找问题了,建议与硬件沟通,让其进行优化,如可以调整设备的连接参数(ConnectionInterval(连接间隔)、SlaveLatency(从设备延迟或者从设备时 延)、SupervisionTimeout(超时时间或者监控超时)),这三个参数是低功耗蓝牙中十分重要的连接参数,一起决定了BLE的功耗,一般硬件设备会在APP连接成功时主动去更新一下这三个参数,以保证不同手机的差异性得到一致,但是APP端是没办法控制这三个参数的。
Q:最大连接数问题
标准答案是7个,为什么是7个,这个我们可以从如下图所示的底层蓝牙源码中找到依据。
https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/include/bt_target.h#1428
stackoverflow问答社区
Q:连接api的使用问题
发起蓝牙Gatt连接 BluetoothDevice.connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback),这里有一个参数autoConnect,如果为 true的话,系统就会发起一个后台连接,等到系统发现了一个设备,就会自动连上,通常这个过程是非常慢的。为false 的话,就会直接连接,通常会比较快。同样,BluetoothGatt.connect()只能发起一个后台连接,不是直接连接,所以连接时设置autoConnect参数设置为false,如果想实现重连功能的话,自己去手动实现吧,实在不想手动写,那就用Android-BLE库吧,你想要的基本都有。
Q:为什么连接总是报133、19之类的非0异常
原因:可能由于首次连接蓝牙后没有释放掉gatt资源导致的蓝牙协议栈异常,从而出现133或257 19等值不为0:由于协议栈,连接建立失败。
建议:在onConnectionStateChange()回调中判断,若state非0(连接断开),调用gatt.close(),手动释放掉gatt相关资源。
Q:为什么连接成功了,发送数据总是失败?
原因:
1、首先确定主服务是否正确,再看设置的读、写特征值是否正确
2、因为BLE发现服务和设置特征、通知等是需要耗时的,所以你并不能连接成功后立马发送数据,可以等到在onDescriptorWrite()回调时,或者手动延迟一段时间再去做发送操作。
Q:为什么要分包发送?
BLE4.0蓝牙发送数据,单次最大传输20个byte,如果是一般的协议命令,如:开关灯、前进左右等等,是不需要
分包的,如果是需要发送如:图片、BIN文档、音乐等大数据量的文件,则必须进行分包发送,BLE库中已经提
供了发送大数据包的接口,需要的小伙伴可以去下载DEMO查看用法。
Service和Characteristic
Service是服务,Characteristic是特征值。蓝牙里面有多个Service,一个Service里面又包括多个Characteristic,具体的关系可以看图
图中画的比较少,实际上一个蓝牙协议里面包含的Service和Characteristic是比较多的 ,这时候你可能会问,这么多的同名属性用什么来区分呢?答案就是UUID,每个Service或者Characteristic都有一个 128 bit 的UUID来标识。Service可以理解为一个功能集合,而Characteristic比较重要,蓝牙设备正是通过Characteristic来进行设备间的交互的(如读、写、订阅等操作)。
开发
声明蓝牙BLE权限
<!--声明蓝牙权限-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Android6.0系统以上开启蓝牙还需要定位权限,定位权限属于危险权限,需要动态申请,申请定位的代码省略。
android.Manifest.permission.ACCESS_FINE_LOCATION
/**
* BLE 蓝牙连接
*/
public class BLEBlueToothUtil {
private static final String TAG ="BLEBlueToothUtil" ;
private FragmentActivity mActivity;
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mBluetoothGatt; //非常重要,读、写、订阅等操作都需要用到这个对象
private List<BluetoothDevice> mDeviceList = new ArrayList<>();
private List<Integer> mRssiList = new ArrayList<>();
//服务和特征值
private UUID write_UUID_service; //写入bluetoothGattService的UUID
private UUID write_UUID_chara; //写入characteristic 特征值的UUID
private UUID read_UUID_service; //读取bluetoothGattService的UUID
private UUID read_UUID_chara; //读取characteristic 特征值的UUID
private UUID notify_UUID_service; //订阅bluetoothGattService的UUID
private UUID notify_UUID_chara; //订阅characteristic 特征值的UUID
private UUID indicate_UUID_service;
private UUID indicate_UUID_chara;
private String hex="7B46363941373237323532443741397D";
private boolean mIsScanning = false;
private boolean mIsConnecting = false;
private BLEBlueToothUtil(){
}
public static BLEBlueToothUtil getInstance(){
return BLEBlueToothUtilHolder.mInstance;
}
private static class BLEBlueToothUtilHolder{
private static final BLEBlueToothUtil mInstance = new BLEBlueToothUtil();
}
BluetoothAdapter.LeScanCallback mScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
//device是设备对象,rssi扫描到的设备强度,scanRecord是扫面记录
// 扫描过的设备仍然会被再次扫描到,因此要加入设备列表之前可以判断一下,如果已经加入过了就不必再次添加了。
public void onLeScan(BluetoothDevice device,