Android Ble蓝牙开发总结
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-featureandroid: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() {@Overridepublic 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() {/*** 断开或连接 状态发生变化时调用* */@Overridepublic 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;}}/*** 发现设备(真正建立连接)* */@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);//直到这里才是真正建立了可通信的连接isConnecting = false;Log.e(TAG, "onServicesDiscovered()---建立连接");//获取初始化服务和特征值initServiceAndChara();//订阅通知mHandler.post(new Runnable() {@Overridepublic void run() {setCharacteristicNotification(mBluetoothGatt.getService(indicate_UUID_service).getCharacteristic(indicate_UUID_chara), true);}});}/*** 读操作的回调* */@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(MainActivity.this, "当前读取值:" + HexUtil.encodeHexStr(characteristic.getValue()), Toast.LENGTH_SHORT).show();}});Log.e(TAG, "onCharacteristicRead()" + HexUtil.encodeHexStr(characteristic.getValue()));}/*** 写操作的回调* */@Overridepublic 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)); }/*** 接收到硬件返回的数据* */@Overridepublic 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;}
结论
项目还在进行,以后继续填坑,结束!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
