项目要用到蓝牙ble进行通信,要求初次使用配置简单,后续使用无感知,稳定接收蓝牙服务方发送的数据,本来以为相对简单,真正调试才发现坑很多,网上找的几个文章都各自有不完善的地方,在此记录下
1.蓝牙BLE的简介
1.蓝牙ble介绍
蓝牙BLE是在Android4.3系统及以上引入的,但是仅作为中央设备,直到5.0以后才可以既作为中央设备又可以作为周边设备。也就是5.0系统以后,可以手机控制手机了,不过绝大多数的场景手机还是作为中央设备去控制其他的周边设备。Android BLE 使用的蓝牙协议是 GATT 协议。关于这个GATT协议,我就不详细给大家介绍了,放上个链接,感兴趣的可以看一下http://blog.chinaunix.net/uid-21411227-id-5750680.html
2.Service和Characteristic
Service是服务,Characteristic是特征值。蓝牙里面有多个Service,一个Service里面又包括多个Characteristic,具体的关系可以看图
图中画的比较少,实际上一个蓝牙协议里面包含的Service和Characteristic是比较多的 ,这时候你可能会问,这么多的同名属性用什么来区分呢?答案就是UUID,每个Service或者Characteristic都有一个 128 bit 的UUID来标识。Service可以理解为一个功能集合,而Characteristic比较重要,蓝牙设备正是通过Characteristic来进行设备间的交互的(如读、写、订阅等操作)。
3.server与client
低功耗蓝牙的设备可以分成两类:一类是中央设备,用于找到并与外围设备进行交互,在常见的场景中,一般它具有丰富的功能和复杂的用户界面,比如手机。中央设备在BLE中一般扮演的角色是client。 低功耗蓝牙应用的另一类是外围设备,向中央设备提供信息和服务。在常见的场景中,它一般是搭载在各设备上的蓝牙芯片,扮演server角色。 类似于互联网的HTTP协议一样,属性协议其本质上也是也是一种无状态协议,它不仅在连接时没有状态,在连接和连接之间也没有状态。因此,在每次信息发生变化时,都需要其中一方主动发送信息。
4.交换MTU请求
在低功耗蓝牙连接中,属性协议默认的MTU长度为23字节。按照1个字节的类型操作码(六种基本操作:请求、响应、命令、指示、确认、通知)以及最少2个字节操作句柄(16BitsUUID)算,数据传输字节最多不超过20字节。在两个设备连接初期,谁也不知道对方底细,因此数据交换严格按照默认MTU来,即MTU为23字节。 如果设备想要发送更大的数据包,那么它就要协商一个更长的MTU。只有客户端可以发起这种请求。客户端的请求包含客户端接收的MTU长度;服务器请求则包含服务器接受的MTU长度。对于同时是是客户端服务器的设备而言,二者提供的接收MTU长度中较小的那个即是连接将会使用的MTU长度,目前最长支持设置517个字节,超过会自动断开连接。
2.蓝牙ble连接流程
1.业务流程
2.连接流程
3.蓝牙设备搜索
////////////////////////////////// 搜索设备 /////////////////////////////////////////////////
private void searchBtDevice() {
if(bleManager == null){
Log.d(TAG, "searchBtDevice()-->bleManager == null");
return;
}
if (bleManager.isDiscovery()) { //当前正在搜索设备...
bleManager.stopDiscoveryDevice();
}
if(lvDevicesAdapter != null){
lvDevicesAdapter.clear(); //清空列表
}
//开始搜索
bleManager.startDiscoveryDevice(onDeviceSearchListener,15000);
}
//扫描结果回调
private OnDeviceSearchListener onDeviceSearchListener = new OnDeviceSearchListener() {
@Override
public void onDeviceFound(BLEDevice bleDevice) {
}
@Override
public void onDiscoveryOutTime() {
}
};
4.蓝牙连接
bleManager.connectBleDevice(mContext,curBluetoothDevice,10000,onBleConnectListener);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
mBluetoothGatt=bluetoothDevice.connectGatt(context,false,bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE,BluetoothDevice.PHY_LE_1M_MASK);
}else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
mBluetoothGatt=bluetoothDevice.connectGatt(context,false,bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE);
}else{
mBluetoothGatt = bluetoothDevice.connectGatt(context,true,bluetoothGattCallback);
}
mBluetoothGatt.connect();
5.连接设置
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
BluetoothDevice bluetoothDevice = gatt.getDevice();
Log.d(TAG,"连接的设备:" + bluetoothDevice.getName() + " " + bluetoothDevice.getAddress());
isConnectIng = false;
//移除连接超时
mHandler.removeCallbacks(connectOutTimeRunnable);
if(newState == BluetoothGatt.STATE_CONNECTED){
Log.w(TAG,"连接成功");
//连接成功去发现服务
gatt.requestMtu(517);
//设置发现服务超时时间
mHandler.postDelayed(serviceDiscoverOutTimeRunnable,MAX_CONNECT_TIME);
if(onBleConnectListener != null){
onBleConnectListener.onConnectSuccess(gatt,bluetoothDevice,status); //连接成功回调
}
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
///设置mtu值,即bluetoothGatt.requestMtu()时触发,提示该操作是否成功
if(status == BluetoothGatt.GATT_SUCCESS){ //设置MTU成功
//MTU默认取的是23,当收到 onMtuChanged 后,会根据传递的值修改MTU,注意由于传输用掉3字节,因此传递的值需要减3。
//mtu - 3
Log.w(TAG,"设置MTU成功,新的MTU值:" + (mtu-3) + ",status" + status);
gatt.discoverServices();
if(onBleConnectListener != null){
onBleConnectListener.onMTUSetSuccess("设置后新的MTU值 = " + (mtu-3) + " status = " + status,mtu - 3); //MTU设置成功
}
}else if(status == BluetoothGatt.GATT_FAILURE){ //设置MTU失败
Log.e(TAG,"设置MTU值失败:" + (mtu-3) + ",status" + status);
if(onBleConnectListener != null){
onBleConnectListener.onMTUSetFailure("设置MTU值失败:" + (mtu-3) + " status:" + status); //MTU设置失败
}
}
List<BluetoothGattService> services=gatt.getServices();
String servUuid=services.get(services.size()-1).getUuid().toString();
String readUuid=services.get(services.size()-1).getCharacteristics().get(0).getUuid().toString();
String writeUuid=services.get(services.size()-1).getCharacteristics().get(1).getUuid().toString();
//移除发现服务超时
mHandler.removeCallbacks(serviceDiscoverOutTimeRunnable);
Log.d(TAG,"移除发现服务超时");
Log.d(TAG,"发现服务");
//if(setupService(gatt,serviceUUID,readUUID,writeUUID)){
//配置服务信息
if(setupService(gatt,servUuid,readUuid,writeUuid)){
if(onBleConnectListener != null){
onBleConnectListener.onServiceDiscoverySucceed(gatt,gatt.getDevice(),status); //成功发现服务回调
}
}else{
if(onBleConnectListener != null){
onBleConnectListener.onServiceDiscoveryFailed(gatt,gatt.getDevice(),"获取服务特征异常"); //发现服务失败回调
}
}
3.踩坑记录
1.连接失败
1.权限问题
2.蓝牙连接兼容问题
bleManager.connectBleDevice(mContext,curBluetoothDevice,10000,onBleConnectListener);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
mBluetoothGatt=bluetoothDevice.connectGatt(context,false,bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE,BluetoothDevice.PHY_LE_1M_MASK);
}else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
mBluetoothGatt=bluetoothDevice.connectGatt(context,false,bluetoothGattCallback,BluetoothDevice.TRANSPORT_LE);
}else{
mBluetoothGatt = bluetoothDevice.connectGatt(context,true,bluetoothGattCallback);
}
2.字节接收只有40个字节
默认只能接收40个字节,多余40个字节的会截断,不会报错,如果修改需要onConnectionStateChange后设置gatt.requestMtu(517)
注意:如果设置MTu,设置生效后(onMtuChanged)再发现服务gatt.discoverServices(),否则会不生效
3.蓝牙发现搜索
如果蓝牙已连接,再次搜索可能会搜索不到,如果需要切换页面并获取蓝牙连接状态时需要注意下,根据情况断开再连接或者传递 device监测是否已连接