这篇博客主要讲解AndroidBLE蓝牙4.0的基本概念,以及基础用法。
BLE 即 Bluetooth Low Energy,蓝牙低功耗技术,是蓝牙4.0引入的新技术,在安卓4.3(API 18)以上为BLE的核心功能提供平台支持和API。与传统的蓝牙相比,BLE更显著的特点是低功耗,所以现在越来越多的智能设备使用了BLE,比如满大街的智能手环,还有体重秤、血压计、心电计等很多BLE设备都使用了BLE与终端设备进行通信。
关键概念和术语
- Generic Attribute Profile(GATT):GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。
- Service:service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。
- Characteristic:一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。
- Descriptor:Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。
他们之间的关系是一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。
这里我打个断点,在调试模式下给你们看看里面具体的结构,先大致的感受一下,不感兴趣的直接往下拉吧。
这是连接了一个BLE设备后获取的,可以看出,我这个BLE设备里有5个Service,第一个Service里又有5个Characteristic。
这幅图是把第一个Service里的Characteristic展开后的结构,可以看到,这个Characteristic没有Descriptor。通过这两幅图可以看出每个Service和Characteristic都有他自己的UUID。
与BLE设备相互通信的大致流程
- 扫描并与指定的BLE设备进行连接。
- 连接成功就能拿到设备的GATT、Service、Characteristic、Descriptor这几个关键的东西(其实能拿到很多东西),并且每个Service、Characteristic都有自己唯一的UUID。
- 开启数据通道,这里的一条数据通道就相当于一个Characteristic,而具体开启哪一个或哪几个,需要根据BLE设备的工程师给的协议文档,如果工程师给的文档很坑的话,就要自己调试,判断等。
- 手机就可以向BLE设备发送数据或接受BLE向手机发送的数据。
- 一般BLE设备向手机发送的数据都16进制的数据报,类似于IP数据报,需要自己解析,具体的解析规则BLE设备的工程师都会给一份协议文档的,看着解析就行了,到这里就完成了与BLE设备的通信了。
下面就开始具体的一步一步实现了。
一、扫描BLE设备并对指定设备进行连接
首先别忘了在AndroidManifest.xml中声明蓝牙权限。
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
一般在程序开始的时候先要判断手机是否支持BLE,不支持的话赶快换手机吧。
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.i(TAG, "支持BLE");
} else {
Log.i(TAG, "不支持BLE");
}
手机支持BLE的话,就可以继续往下走了。
接着我们需要用到蓝牙适配器,然后判断蓝牙是否开启,没开启就会提示开启。
//通过系统服务获取蓝牙管理者
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
//获取蓝牙适配器
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
System.out.println("蓝牙没有开启");
//请求开启蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 1);
}
提示开蓝牙
蓝牙开启了就可以开始搜索设备了。
这里的mBluetoothAdapter就是我们前面拿到的那个,然后调用startLeScan()方法搜索设备(只能搜索到BLE设备,蓝牙2.0的不行),并且需要传给它一个LeScanCallback回调,那么我们就实现一个传给它。
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索BLE设备
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
//当搜索到一个设备,这里就会回调,注意这里回调到的是子线程。
@Override
public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
//在这里可以把搜索到的设备保存起来0
//device.getName();获取蓝牙设备名字
//device.getAddress();获取蓝牙设备mac地址。
//这里的rssi即信号强度,即手机与设备之间的信号强度。
//注意,这里不是搜索到1个设备后就只回调一次这个设备,可能过个几秒又搜索到了这个设备,还会回调的
//所以,这里可以实时刷新设备的信号强度rssi,但是保存的时候就只保存一次就行了。
if (!bluetoothDeviceList.contains(device)) {//保存设备
bluetoothDeviceList.add(device);
Log.i(TAG, device.getName() + "");
Log.i(TAG, device.getAddress() + "");
Log.i(TAG, "信号:" + rssi);
}
}
};
搜索到了BLE设备,并且也保存了。就可以通过调用device.connectGatt()来进行连接,这个方法有三个参数,第一个context不用说了,第二个是boolean类型的,表示是否自动连接,第三个参数又是一个回调,这个回调比较重要,后续很多操作都跟这个回到有关,这里为了方便看我就直接匿名实现了。在onConnectionStateChange这个回调中,我们会收到3个参数,gatt,就是上面的调试图中的那个gatt,这里我们可以保存下来也可以不保存,因为下面其他的回调方法中都会有这个gatt,都是同一个,会经常用到。status表示相应的连接或断开操作是否完成,而不是指连接状态,newStatus表示的是设备的新状态,这里我们可以通过newState来判断是否连接成功。如果连接成功再进行后续的操作。当然也可以通过newState判断设备是否断开。
device.connectGatt(MainActivity.this, false, new BluetoothGattCallback() {
//连接状态改变时回调
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "连接成功");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "连接已断开");
}
}
//发现服务的回调
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
}
//写操作的回调
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
}
//数据返回的回调
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
}
//写入描述符后的回调
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
});
二、扫描服务
扫描服务一般在连接成功后就开始扫描,调用gatt.discoverServices()方法就可以了。
//连接状态改变时回调
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "连接成功");
gatt.discoverServices(); //扫描服务
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "连接已断开");
}
}
当扫描到服务后或者发现了服务,就会触发onServicesDiscovered这个回调,收到2个参数,gatt还是那个,status也还是表示相应的连接或断开操作是否完成。
//发现服务的回调
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "发现服务成功");
}
}
三、开启数据通道(Characteristic)
有了gatt,也成功发现了服务(Service),接下来就是开启数据通道,因为只有通道开启了,才能进行通信。
开启数据通道还是在发现服务的这个回调里。这里要看工程师给的文档怎么样了。我这就先用文档中有UUID进行讲解。
可以看到,有一个Services的UUID,一个可写的通道(Characteristic),和4个Notify就是收数据的通道(Characteristic)。虽然UUID没写全,但是我们可以根据这个信息,在调试模式里就能找到,如下图。
可以看到这个设备里有4个Service,但是看文挡,我们只需要UUID是ba11f08c-5f14开头的这个Service,然后这里面又有5个Charactristic,刚好是文档中的,我们全都记录下来,定义成常量。还有每一个Charactristic下都有一个Descriptor,这个是描述符也有对应的UUID,记录下来,开数据通道的时候也需要用到。
//发现服务的回调
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onServicesDiscovered: 成功");
}
/下面是通过UUID拿到对应的service和characteristic,并把characteristic声明成全局的
BluetoothGattService service = gatt.getService(UUID.fromString(UUID_SERVICE));
characteristicWrite = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_WRITE));
characteristic01 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY1));
characteristic02 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY2));
characteristic03 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY3));
characteristic04 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY4));
setNotification(gatt, characteristic01, true); //先开启1号通道
}
//成功写入描述符回调,这里就实现按顺序数据通道,setNotification是自己写的方法。
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
Log.i(TAG, "onDescriptorWrite: 回调");
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
String uuid = characteristic.getUuid().toString();
if (uuid.equals(UUID_CHARACTER_NOTIFY1)) {
setNotification(gatt, characteristic02, true);
} else if (uuid.equals(UUID_CHARACTER_NOTIFY2)) {
setNotification(gatt, characteristic03, true);
} else if (uuid.equals(UUID_CHARACTER_NOTIFY3)) {
setNotification(gatt, characteristic04, true);
} else if (uuid.equals(UUID_CHARACTER_NOTIFY4)) {
//给设备发数据,这里是因为我的设备连接的时候需要连接确认。这里的AA5504B10000B5就是指令
characteristicWrite.setValue(getHexBytes("AA5504B10000B5"));
gatt.writeCharacteristic(characteristicWrite);
}
}
private void setNotification(BluetoothGatt mGatt, BluetoothGattCharacteristic mCharacteristic, boolean mEnable) {
if (mCharacteristic == null) {
return;
}
//设置为Notifiy,并写入描述符
mGatt.setCharacteristicNotification(mCharacteristic, mEnable);
BluetoothGattDescriptor descriptor = mCharacteristic.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mGatt.writeDescriptor(descriptor);
}
四、接收数据
当设备有数据发送给手机的话,就会触发onCharacteristicChanged这个回调,这个回调能被触发,那手机与BLE设备的通信基本就算完成了。通过characteristic.getValue()来获取设备发来的数据,是字节数组,
一次回调最多能发来20个字节,所以要是数据很长的话,就会触发多次回调,就要把每次回调回来的数据保存下来,最后再解析,解析的话根据协议文档来就行了,因为不同的设备解析的规则是不一样的。
//数据返回的回调
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "onCharacteristicChanged: 回调");
byte[] bytes = characteristic.getValue(); //获取设备发来的数据
Log.i(TAG, "value=" + byte2hex(bytes)); //byte2hex()是把字节数组转换成16进制的字符串,方便看
}
一般发来的就是这样的数据,根据协议文档解析
五、发送数据
给设备发送数据,就是发送指令,文档上会写可以给设备发送指令,也就是功能,然后还会说明指令格式等等,指令最终也还是字节数组。
如下代码,就是一条指令
private byte[] getAllDataOrder() {
Log.i(TAG, "获取全部数据指令");
byte[] data = new byte[7];
data[0] = (byte) 0x93;
data[1] = (byte) 0x8e;
data[2] = (byte) 0x04;
data[3] = (byte) 0x00;
data[4] = (byte) 0x08;
data[5] = (byte) 0x05;
data[6] = (byte) 0x11;
return data;
}
然后调用下面这2个方法写入指令,设备成功收到的话会触发onCharacteristicChanged回调,并收到数据。这里的characteristicWrite是我们前面拿到的那个。
characteristicWrite.setValue(getAllDataOrder());
gatt.writeCharacteristic(characteristicWrite);
以上就是本人对安卓BLE开发一些初步理解,如有错误的地方,还望指正。