最近项目中需要使用到BLE通信,用来实现车机端和另外一个设备的信息交互(称为从设备)。在做了一番了解之后发现,Android对于BLE的封装已经很到位了,使得BLE的功能相对于整个Android蓝牙来说就是一个Profile,使用起来已经很方便。
一、BLE基本概念:
1.概述
BLE全称为Bluetooth Low Energy,低功耗蓝牙技术,用以解决传统蓝牙(BR)功耗过高的问题。其中定义了两个角色,一个是主设备(Master),如手机、车机等处理能力较强的设备;一个是从设备(Slave),通常是智能手环、心跳仪等外围设备。一个主设备可以同时连接多个从设备,而一个从设备也可以同时连接多个主设备。
2.广播
在主设备跟从设备建立连接之前,从设备需要每隔一段时间就发送一条广播信号,实际上是在37(2402MHz) /38(2426MHz) /39(2480MHz)这三个信道上发送相同的广播,广播间隔(Advertising interval )在20ms ~ 10.28s之间。一条广播信息中包含设备地址(device address)、设备名、数据类型、自定义数据字段等,长度不能超过31个字节。 当主设备打开扫描功能之后,会在三个信道上分时开启接收窗口,一旦在某个信道上的接收到广播数据,那么就可以扫描到从设备了。
3.连接
由于广播携带的数据有限,如果两个设备间需要交互数据,那么就需要建立连接。所谓建立连接,其实就是两个设备之间达成收发数据的物理信道一致、时间原点一致、基于时间原点的收发时间间隔一致。 当主设备端收到从设备的广播之后,就可以对从设备发起连接请求。从设备在每次广播之后,会开启一段时间的信息接收窗口,这个时候从设备收到连接请求,就会给主设备反馈连接成功的信号。后面两个设备就会定时发送数据和接收数据,完成数据的交互。 当然,以上是大致讲述了底层连接的过程,而上层协议栈中,实际上是建立了GATT协议的连接。
4.GATT协议
GATT协议中定义两个角色,一个是Service,一个是Characteristic,每个Service和Characteristic都有特定的UUID,类似于 0000ff00-0000-1000-8000-00805f9b34fb 这样的字符串。每个Service代表提供某种服务的能力,比如跟心率有关的Service;Characteristic代表的是一个键值对,Service就是通过这个一个个的键值对达到传输数据的目的。如下图所示:
当主设备和从设备连接上GATT协议之后,就可以询问从设备可以提供哪些服务,在得到从设备的反馈报文后,就可以通过双方协商好的UUID获取到Service服务,然后再从Service中根据UUID获取到可读的Characteristic和可写的Characteristic,操作Characteristic就可以实现具体的数据通信。
二、Android中的BLE
1.扫描设备
使用BluetoothLeScanner对象来执行扫描的动作:
private BluetoothLeScanner bluetoothLeScanner;
bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(scanCallback);
由于扫描是一个异步的过程,所以这里需要传入一个回调接口,我们在回调接口去获取扫描的结果:
public ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
ScanRecord record = result.getScanRecord();
BluetoothDevice device = result.getDevice();
String deviceName = record.getDeviceName();
Log.d(TAG, "record name:" + deviceName);
Log.d(TAG, "ServiceUuids:" + record.getServiceUuids());
if (TextUtils.isEmpty(deviceName)) {
return;
}
if (deviceName.startsWith("XX")) {
byte[] bytes = record.getBytes();
for (int i = 0; i < bytes.length; i++) {
Log.d(TAG, "[" + i + "]:" + bytes[i]);
}
bluetoothLeScanner.stopScan(scanCallback);
}
}
};
2.连接设备
在找到了“XX”名称的设备后,我们就可以发起GATT协议的连接了:
device.connectGatt(context, false, gattCallback);
第二个参数,如果是false代表仅发起本次连接,如果连接不上则会反馈连接失败;如果是true则表示只要这个远程的设备可用,那么底层协议栈就会自动去连接,并且第一次连接不上,也会继续去连接。 第三个参数,是一个关于GATT协议相关的回调接口,主要有GATT连接状态的回调、发现Service服务的回调、特征值(Characteristic)发生改变的回调、最大传输单元(MTU)改变的回调、物理层发送模式(PHY)改变回调等,如下:
BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
super.onPhyRead(gatt, txPhy, rxPhy, status);
Log.d(TAG, "onPhyUpdate txPhy:" + txPhy + "; rxPhy:" + rxPhy);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
Log.d(TAG, "onMtuChanged mtu:" + mtu + "; status:" + status);
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.d(TAG, "onConnectionStateChange newState:" + newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d(TAG, "STATE_CONNECTED");
bluetoothGatt = gatt;
bluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(TAG, "STATE_DISCONNECTED");
}
}
}
3.获取服务和特征(Characteristic)
在GATT协议连接成功之后,就可以去发现从设备端提供了哪些Service服务,如上代码。 这是一个异步的过程,待从设备反馈了自己提供的服务之后,Android框架层会通过BluetoothGattCallback回调通知,如下:
BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
List<BluetoothGattService> services = gatt.getServices();
for (BluetoothGattService service : services) {
Log.d(TAG, "UUID:" + service.getUuid().toString());
}
mGattService = gatt.getService(UUID.fromString("0000ff00-0000-1000-8000-00805f9b34fb"));
if (mGattService == null) {
Log.w(TAG, "GattService is null!");
} else {
Log.i(TAG, "connect GattService");
if(writeCharacteristic == null){
writeCharacteristic = mGattService
.getCharacteristic(UUID.fromString("0000ff02-0000-1000-8000-00805f9b34fb"));
}
if(readCharacteristic == null){
readCharacteristic = mGattService
.getCharacteristic(UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb"));
bluetoothGatt.setCharacteristicNotification(readCharacteristic, true);
List<BluetoothGattDescriptor> descriptors = readCharacteristic.getDescriptors();
for (BluetoothGattDescriptor descriptor : descriptors) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
}
}
}
经过上述代码中的四个步骤,两个设备间已经可以发送和接收数据了。
4.通过特征(Characteristic)发送数据
把需要发送的数据设置到writeCharacteristic,然后再调用BluetoothGatt的写入方法,即可完成数据的发送:
writeCharacteristic.setValue(datas);
bluetoothGatt.writeCharacteristic(writeCharacteristic);
5.读取数据
当从设备有数据发送到主设备之后,Android系统会回调BluetoothGattCallback的onCharacteristicChanged方法通知:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
UUID uuid = characteristic.getUuid();
byte[] receiveData = characteristic.getValue();
for (byte b : receiveData) {
Log.d(TAG, "receiveData:" + Integer.toHexString(b));
}
}
三、注意事项
1.自动连接属性
connectGatt方法的自动连接参数设置为true之后,连接建立了,这个时候如果是断开连接,如下:
bluetoothGatt.disconnect();
虽然在Android层面的BluetoothGattCallback接口会立刻反馈一个STATE_DISCONNECTED信号值,但是在数据链路层却还是处于连接的状态,连接并没有断开。
2.开启定位功能
现在Android最新的版本,需要开启定位才能使用BLE功能。 判断定位功能是否开启:
private boolean isLocationEnable(Context context) {
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
boolean networkProvider = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
boolean gpsProvider = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (networkProvider || gpsProvider) {
return true;
}
return false;
}
开启定位功能的方法:
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
try {
Field field = UserHandle.class.getDeclaredField("SYSTEM");
field.setAccessible(true);
UserHandle userHandle = (UserHandle) field.get(UserHandle.class);
Method method = LocationManager.class.getDeclaredMethod(
"setLocationEnabledForUser",
boolean.class,
UserHandle.class);
method.invoke(locationManager, true, userHandle);
} catch (Exception e) {
}
3.最大传输单元(MTU)的设置
Android默认的最大传输单元(MTU)是23个字节,除去报文头占用的3个字节,实际最大只能传递20个字节。当两个设备之间传递的数据长度超过20字节的时候,数据就会被截断,导致通信异常。 只有在GATT协议连接成功之后,才可以设置MTU值,最大MTU=512,如下:
bluetoothGatt.requestMtu(128);
4.从设备广播间隔影响连接
当Android协议栈(Host)给蓝牙芯片Chip发送一个连接的指令,芯片在收到之后,会在一定的时间内去接收从设备的广播,在收到广播之后才会发送连接请求给从设备;如果从设备的广播间隔设置不合理,就会导致芯片无法在限定的时间内收到广播,导致无法发送连接请求。BLE连接过程如下图:
|