Android Ble蓝牙开发总结
前言
本文总结了ble的搜索,连接,读写操作。以及在开发过程中可能遇到的坑。
首先我们需要知道,什么是ble。
蓝牙发展至今经历了8个版本的更新。1.1、1.2、2.0、2.1、3.0、4.0、4.1、4.2。那么在1.x~3.0之间的我们称之为传统蓝牙,4.x开始的蓝牙我们称之为低功耗蓝牙也就是蓝牙ble。
蓝牙BLE相对于传统蓝牙的优点:最大化的待机时间、快速连接和低峰值的发送/接收功耗。应用区别:BLE低功耗蓝牙一般多用在蓝牙数据模块,拥有极低的运行和待机功耗,使用一粒纽扣电池可连续工作数年之久;BT经典蓝牙模块多用在蓝牙音频模块,音频需要大码流的数据传输更适合使用。
1、蓝牙BLE的发送和接受任务会以最快的速度完成,完成之后蓝牙BLE会暂停发射无线(但是还是会接受),等待下一次连接再激活;而传统蓝牙是持续保持连接。
2、广播信道(为保证网络不互相干扰而划分)仅有3个,而传统蓝牙是32个。
3、蓝牙低能耗技术“完成”一次连接(即扫描其它设备、建立链路、发送数据、认证和适当地结束)只需3ms。而标准蓝牙技术完成相同的连接周期需要数百毫秒。
4、蓝牙低能耗技术使用非常短的数据包,标准蓝牙技术使用的数据包长度较长。
ble的相关概念
Generic Attribute Profile (GATT)
通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。
Profile可以理解为一种规范,一个标准的通信协议,其存在于手机中,蓝牙组织规定了一些标准的profile:HID OVER GATT ,防丢器等,每个profile中包含了多个service。
Attribute Protocol (ATT)
GATT是基于ATT Protocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。
Service
可以理解为一个服务,这里要区分的是BluetoothServer,一个是服务,一个是服务器端。在BLE从机中有多个服务,电量信息,系统服务信息等,每一个service中包含了多个characteristic特征值,每一个具体的characteristic特征值才是BLE通信的主题。 Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart rate measurement”的Characteristic。
Characteristic
Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。通过操作Characteristic可以实现Ble的数据传输。
Descriptor
对Characteristic的描述,例如范围、计量单位等。
UUID(统一标识码)
service和characteristic均需要这个唯一的UUID进行标识。UUID可以双方自定义,例如客户端访问服务器端的写characteristic,那么客户端就需要有服务器端定义的写characteristic UUID
这三部分都用UUID作为唯一标识符。UUID为这种格式:0000ffe1-0000-1000-8000-00805f9b34fb。比如有3个Service,那么就有三个不同的UUID与Service对应。这些UUID都写在硬件里,我们通过BLE提供的API可以读取到,同时也可以自定义Application层的UUID。
他们关系可以总结如下:一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。
Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。
代码实现
1.权限设置
使用蓝牙必须先获取到相应的权限,因为需要使用BLE,所以Manifest中需要加入以下权限及说明,在6.0上面需要使用BLE蓝牙,我们还需要加上LOCATION权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
2.检查设备蓝牙状态
判断当前设备是否支持蓝牙,并且是否开启蓝牙,如果支持且没有打开则需要开启蓝牙,要是不支持蓝牙,那就不用继续看了,直接说做不了就行了。
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0);
}
3.搜索蓝牙设备
mBluetoothAdapter.startLeScan(scanCallback);
BluetoothAdapter.LeScanCallback scanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
Log.e(TAG, "run: scanning..." + device.getName());
if (device.getName() != null && !device.getName().equals("null") && !mDatas.contains(device)) {
//展示当前搜索到的蓝牙设备
}
}
};
4.获取蓝牙设备特征值
private void initServiceAndChara() {
List<BluetoothGattService> bluetoothGattServices = mBluetoothGatt.getServices();
for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {
List<BluetoothGattCharacteristic> characteristics = bluetoothGattService.getCharacteristics();
for (BluetoothGattCharacteristic characteristic : characteristics) {
int charaProp = characteristic.getProperties();
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
read_UUID_chara = characteristic.getUuid();
read_UUID_service = bluetoothGattService.getUuid();
Log.e(TAG, "read_chara=" + read_UUID_chara + "----read_service=" + read_UUID_service);
}
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
write_UUID_chara = characteristic.getUuid();
write_UUID_service = bluetoothGattService.getUuid();
Log.e(TAG, "write_chara=" + write_UUID_chara + "----write_service=" + write_UUID_service);
}
// if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
// write_UUID_chara = characteristic.getUuid();
// write_UUID_service = bluetoothGattService.getUuid();
// Log.e(TAG, "write_chara=" + write_UUID_chara + "----write_service=" + write_UUID_service);
//
// }
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
notify_UUID_chara = characteristic.getUuid();
notify_UUID_service = bluetoothGattService.getUuid();
Log.e(TAG, "notify_chara=" + notify_UUID_chara + "----notify_service=" + notify_UUID_service);
}
if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
indicate_UUID_chara = characteristic.getUuid();
indicate_UUID_service = bluetoothGattService.getUuid();
Log.e(TAG, "indicate_chara=" + indicate_UUID_chara + "----indicate_service=" + indicate_UUID_service);
}
}
}
}
5.连接蓝牙设备
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this,
true, gattCallback, TRANSPORT_LE);
} else {
mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this,
true, gattCallback);
}
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
/**
* 断开或连接 状态发生变化时调用
* */
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.e(TAG, "onConnectionStateChange()");
if (status == BluetoothGatt.GATT_SUCCESS) {
//连接成功
if (newState == BluetoothGatt.STATE_CONNECTED) {
Log.e(TAG, "连接成功");
isScaning = false;
//发现服务
gatt.discoverServices();
}
} else {
//连接失败
Log.e(TAG, "失败==" + status);
mBluetoothGatt.close();
isConnecting = false;
}
}
/**
* 发现设备(真正建立连接)
* */
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
//直到这里才是真正建立了可通信的连接
isConnecting = false;
Log.e(TAG, "onServicesDiscovered()---建立连接");
//获取初始化服务和特征值
initServiceAndChara();
//订阅通知
mHandler.post(new Runnable() {
@Override
public void run() {
setCharacteristicNotification(mBluetoothGatt
.getService(indicate_UUID_service).getCharacteristic(indicate_UUID_chara), true);
}
});
}
/**
* 读操作的回调
* */
@Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "当前读取值:" + HexUtil.encodeHexStr(characteristic.getValue()), Toast.LENGTH_SHORT).show();
}
});
Log.e(TAG, "onCharacteristicRead()" + HexUtil.encodeHexStr(characteristic.getValue()));
}
/**
* 写操作的回调
* */
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
final byte[] value = characteristic.getValue();
Log.e(TAG, "onCharacteristicWrite() status=" + status + ",value=" + bytesToHex(value));
}
/**
* 接收到硬件返回的数据
* */
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
final byte[] data = characteristic.getValue();
Log.e(TAG, "onCharacteristicChanged()" + bytesToHex(data));
}
};
6.蓝牙读写数据
private void writeData() {
BluetoothGattService service = mBluetoothGatt.getService(write_UUID_service);
BluetoothGattCharacteristic charaWrite = service.getCharacteristic(write_UUID_chara);
String content = etWriteContent.getText().toString();
if (TextUtils.isEmpty(content)) {
return;
}
byte[] body = content.getBytes();
if (body.length > 20) {//数据大于个字节 分批次写入
Log.e(TAG, "writeData: length=" + body.length);
int num = 0;
if (body.length % 20!= 0) {
num = body.length / 20+ 1;
} else {
num = body.length / 20;
}
Log.e(TAG, "writeData: 需要" + num + "次");
for (int i = 0; i < num; i++) {
byte[] tempArr;
if (i == num - 1) {
tempArr = new byte[body.length - i * 20];
System.arraycopy(body, i * 20, tempArr, 0, body.length - i * 20);
} else {
tempArr = new byte[20];
System.arraycopy(body, i * 20, tempArr, 0,2017);
}
charaWrite.setValue(tempArr);
mBluetoothGatt.writeCharacteristic(charaWrite);
}
} else {
charaWrite.setValue(body);
mBluetoothGatt.writeCharacteristic(charaWrite);
}
}
private void readData() {
BluetoothGattCharacteristic characteristic = mBluetoothGatt.getService(read_UUID_service)
.getCharacteristic(read_UUID_chara);
mBluetoothGatt.readCharacteristic(characteristic);
}
开发过程中可能遇到的坑
1.onCharacteristicChanged死活不回调
onServicesDiscovered回调后需要对相应的通道进行订阅。
setCharacteristicNotification(mBluetoothGatt
.getService(indicate_UUID_service).getCharacteristic(indicate_UUID_chara), true);
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
return;
}
boolean notification = mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
//这里可以加入判断对指定的UUID值进行订阅
List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
for (BluetoothGattDescriptor descriptor : descriptors) {
if (descriptor != null) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
boolean b = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
} else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
boolean b = descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
}
mBluetoothGatt.writeDescriptor(descriptor);
}
}
}
工具类
//合并byte[ ]
public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
byte[] bt3 = new byte[bt1.length + bt2.length];
System.arraycopy(bt1, 0, bt3, 0, bt1.length);
System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
return bt3;
}
//byte[ ] -->16进制字符串
public static String byte2hex(byte[] buffer) {
String h = "";
for (int i = 0; i < buffer.length; i++) {
String temp = Integer.toHexString(buffer[i] & 0xFF);
if (temp.length() == 1) {
temp = "0" + temp;
}
h = h + temp;
}
return h;
}
// 16进制字符串 -->byte[ ]
public static byte[] hexStringToByte(String hex) {
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
结论
项目还在进行,以后继续填坑,结束!