最近刚好项目需要手机与蓝牙模块通信,基于蓝牙4.0,网上资料较少也有些小坑,故作一下总结。
关键术语和概念
蓝牙有传统蓝牙(3.0以下)和低功耗蓝牙(BLE,又称蓝牙4.0)之分,而蓝牙4.0开发需要android4.3版本(API 18)及以上才支持BLE API。相比传统的蓝牙,BLE更显著的特点是低功耗。这一优点使android App可以与具有低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备等。BLE 全称 Bluetooth Low EnergyGeneric Attribute Profile(GATT)—GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。Attribute Protocol(ATT)—GATT在ATT协议基础上建立,也被称为GATT/ATT。ATT对在BLE设备上运行进行了优化,为此,它使用了尽可能少的字节。每个属性通过一个唯一的的统一标识符(UUID)来标识,每个String类型UUID使用128 bit标准格式。属性通过ATT被格式化为characteristics和services。Characteristic 一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。Service service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。
蓝牙4.0的结构
简单的说,就是BLE是基于GATT实现的,BLE分为三个部分Service、Characteristic、Descriptor,每个部分都拥有不同的 UUID来标识。一个BLE设备可以拥有多个Service,一个Service可以包含多个Characteristic, 一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。 通信数据一般存储在Characteristic内,目前一个Characteristic中存储的数据最大为20 byte。 与Characteristic相关的权限字段主要有READ、WRITE、WRITE_NO_RESPONSE、NOTIFY。 Characteristic具有的权限属性可以有一个或者多个。
用一张图来说明蓝牙4.0的组成:
开发流程
1. 获取相关权限
在AndroidManifest.xml声明相关权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2. 获取BluetoothManager和BluetoothAdapter
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bleAdapter = manager.getAdapter();
BluetoothAdapter bleAdapter = BluetoothAdapter.getDefaultAdapter();
3. 检测蓝牙是否打开或可用
public boolean check() {
return (
null != bleAdapter && bleAdapter.isEnabled() && !bleAdapter.isDiscovering());
}
4. 扫描蓝牙并实现回调接口LeScanCallback
public void startScan(
long scanDelay) {
if(!check())
return;
BleScanCallback leScanCallback =
new BleScanCallback();
if (bleAdapter.startLeScan(leScanCallback)) {
timer.schedule(
new TimerTask() {
@Override
public void run() {
stopScan();
}
}, scanDelay);
}
}
private class BleScanCallback implements BluetoothAdapter.LeScanCallback {
@Override
public void onLeScan(BluetoothDevice device,
int rssi,
byte[] scanRecord) {
Log.e(TAG,
"found device name : " + device.getName() +
" address : " + device.getAddress());
}
}
有一个地方可以注意下,在API 21及以上,不推荐使用BluetoothAdapter.startLeScan和stopLeScan。而是新引入了BluetoothLeScanner类和ScanCallback回调接口。由于需要兼容API 19,所以这里不作过多的阐述,有需要可以查看官方文档了解。
5. 连接蓝牙设备并实现连接回调接口
public void connect(BluetoothDevice device) {
if(!check())
return;
if (
null != bleAdapter) {
bleAdapter.stopLeScan(leScanCallback);
}
if (bleGatt.connect()) {
if (TextUtils.equals(device.getAddress(), bleGatt.getDevice().getAddress())) {
return;
}
else {
disconnect();
}
}
bleGatt = device.connectGatt(context,
false, bleGattCallback);
}
private class BleGattCallback extends BluetoothGattCallback {
@Override
public void onConnectionStateChange(BluetoothGatt gatt,
int status,
int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
Intent intent =
new Intent(ActionBle.CONNECTED);
context.sendBroadcast(intent);
gatt.discoverServices();
}
else {
Intent intent =
new Intent(ActionBle.CONNECT_FAIL);
context.sendBroadcast(intent);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
services = gatt.getServices();
characteristics =
new ArrayList<>();
descriptors =
new ArrayList<>();
for (BluetoothGattService service : services) {
Log.e(TAG,
"-- service uuid : " + service.getUuid().toString() +
" --");
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
Log.e(TAG,
"---- characteristic uuid : " + characteristic.getUuid() +
" ----");
characteristics.add(characteristic);
if (characteristic.getUuid().toString().equals(Command.READ_UUID)) {
setCharacteristicNotification(characteristic,
true);
}
for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
Log.e(TAG,
"-------- descriptor uuid : " + characteristic.getUuid() +
" --------");
descriptors.add(descriptor);
}
}
}
Intent intent =
new Intent(ActionBle.DISCOVER_SERVICES_SUCCESS);
context.sendBroadcast(intent);
}
else {
Intent intent =
new Intent(ActionBle.DISCOVER_SERVICES_FAIL);
context.sendBroadcast(intent);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Intent intent;
if (BluetoothGatt.GATT_SUCCESS == status) {
intent =
new Intent(ActionBle.WRITE_DATA_SUCCESS);
}
else {
intent =
new Intent(ActionBle.WRITE_DATA_FAIL);
}
context.sendBroadcast(intent);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.e(TAG,
"onCharacteristicChanged: " + Arrays.toString(characteristic.getValue()));
}
}
private boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (bleGatt ==
null) {
return false;
}
BluetoothGattDescriptor localBluetoothGattDescriptor = characteristic.getDescriptor(UUID.fromString(DESCRIPTORS_UUID));
if (enabled) {
localBluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
}
else {
localBluetoothGattDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
}
bleGatt.writeDescriptor(localBluetoothGattDescriptor);
return bleGatt.setCharacteristicNotification(characteristic, enabled);
}
连接设备的回调函数很关键,有几个点需要注意: 1. 当设备连接状态改变(连接成功或失败)时,会回调onConnectionStateChange接口,需要在连接成功的时候调用gatt.discoverServices();去搜索设备的服务Service集合。 2. 当设备搜索服务状态(成功找到服务或失败)时,会回调onServicesDiscovered接口,这里面很关键,需要处理订阅相关特征Characteristic: bleGatt.setCharacteristicNotification(characteristic, true); 这个需要依据蓝牙模块协议而定,只有在这里订阅了,才能接收到蓝牙模块发送过来的数据。 这里还有个大坑,具体看setCharacteristicNotification。 3. 订阅的特征Characteristic接收到消息,也就是蓝牙模块发送数据过来时,会回调onCharacteristicChanged接口,数据的处理就在该接口处理。
6. 向蓝牙模块发送数据
其实也很简单,根据协议往相关特征写入数据即可。
public boolean writeCharacteristic(String uuid,
byte[] bytes) {
if (
null == characteristics) {
return false;
}
Log.e(TAG,
"writeCharacteristic to " + uuid +
" : " + Arrays.toString(bytes));
if (
null != bleGatt) {
for (BluetoothGattCharacteristic characteristic : characteristics) {
if (TextUtils.equals(uuid, characteristic.getUuid().toString())) {
characteristic.setValue(bytes);
return bleGatt.writeCharacteristic(characteristic);
}
}
}
return false;
}
注意这里会回调上面实现的BluetoothGattCallback.onCharacteristicWrite接口 这里我用的蓝牙模块约定发送和接收的特征uuid如下:
String READ_UUID =
"6e400003-b5a3-f393-e0a9-e50e24dcca9e";
String WRITE_UUID =
"6e400002-b5a3-f393-e0a9-e50e24dcca9e";
具体蓝牙模块看具体的协议而定。
7. 断开连接
public void disconnect() {
if (
null != bleGatt) {
bleGatt.disconnect();
}
Log.e(TAG,
"disconnect");
}
8. 数据的转换
与蓝牙模块的通信一般都是采用16进制,byte[]传输,因此需提供几个格式转换的方法。
public static String
bytes2HexString(
byte[] bytes) {
String ret =
"";
for (
byte item : bytes) {
String hex = Integer.toHexString(item &
0xFF);
if (hex.length() ==
1) {
hex =
'0' + hex;
}
ret += hex.toUpperCase(Locale.CHINA);
}
return ret;
}
public static byte[]
getHexBytes(String message) {
int len = message.length() /
2;
char[] chars = message.toCharArray();
String[] hexStr =
new String[len];
byte[] bytes =
new byte[len];
for (
int i =
0, j =
0; j < len; i +=
2, j++) {
hexStr[j] =
"" + chars[i] + chars[i +
1];
bytes[j] = (
byte) Integer.parseInt(hexStr[j],
16);
}
return bytes;
}
以上就是android 蓝牙4.0的基本操作啦,可以实现基本的通信啦,瑟瑟发抖。