Android蓝牙BLE基本用法
16lz
2021-01-26
Android蓝牙BLE基本用法
- Android应用权限
- 蓝牙相关对象获取
- 检查设备是否支持BLE
- 开启设备的蓝牙功能
- 使设备的蓝牙可被发现
- 开启BLE服务端
- 新建一个GATT服务
- 新建一个GATT特征值
- 新建一个特征值描述(可选)
- 特征值加入特征值描述(可选)
- 服务加入特征值
- 开启GATT服务端
- GATT服务端加入刚才创建的GATT Service
- 开始发送BLE广播
- BLE客户端
- 扫描设备和服务
- 扫描回调接口
- 客户端连接
- 读操作
- 写操作
Android应用权限
蓝牙相关对象获取
private BluetoothAdapter bluetoothAdapter; private BluetoothManager bluetoothManager; @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // bluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // bluetoothManager BLE需要这个类 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE); } }
检查设备是否支持BLE
public void checkBLESupport() { if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, "不支持BLE", Toast.LENGTH_LONG).show(); Log.d("kaikai", "不支持BLE"); } else { Toast.makeText(this, "支持BLE", Toast.LENGTH_LONG).show(); Log.d("kaikai", "支持BLE"); } }
开启设备的蓝牙功能
public void onEnableBlueTooth() { if (!bluetoothAdapter.isEnabled()) { Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, 0); } else { Toast.makeText(this, "蓝牙已开启", Toast.LENGTH_LONG).show(); } }
使设备的蓝牙可被发现
public void onDiscoverable() { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); startActivity(discoverableIntent); }
开启BLE服务端
新建一个GATT服务
BluetoothGattService service = new BluetoothGattService(YOUR_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
新建一个GATT特征值
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(YOUR_CHARACTERISTIC_UUID,// 注意,如果要使特征值可写,PROPERTY需设置PROPERTY_WRITE(服务端需要返回响应)或PROPERTY_WRITE_NO_RESPONSE(服务端无需返回响应),BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_WRITE,BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
注意,BluetoothGattCharacteristic构造函数的第二个参数,如果要使特征值可被客户端进行反写,需要有BluetoothGattCharacteristic.PROPERTY_WRITE或PROPERTY_WRITE_NO_RESPONSE标志位,其中,PROPERTY_WRITE标志位要求服务端在接收写请求时,返回响应,而PROPERTY_WRITE_NO_RESPONSE则不需要响应,但数据可能会被截断。
新建一个特征值描述(可选)
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(YOUR_DESCRIPTER_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
特征值加入特征值描述(可选)
characteristic.addDescriptor(descriptor);
服务加入特征值
service.addCharacteristic(characteristic);
开启GATT服务端
// 使用一个类成员保存BluetoothGattServer引用 private BluetoothGattServer bluetoothGattServer; // onCreate方法中或某个按钮触发的回调函数中 bluetoothGattServer = bluetoothManager.openGattServer(this, new BluetoothGattServerCallback() { @Override public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { // 连接状态改变 if (newState == BluetoothProfile.STATE_CONNECTED) { Log.d("kaikai", "BLE服务端:BLE Connected!"); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d("kaikai", "BLE服务端:BLE Disconnected!"); } } @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { Log.d("kaikai", "BLE服务端:接收到特征值读请求!" + " requestId=" + requestId + " offset=" + offset); BluetoothGattCharacteristic gattCharacteristic = bluetoothGattServer.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID); if (characteristic == gattCharacteristic) { // 证明characteristic与通过gattServer得到的是同一个对象 Log.d("kaikai", "BLE服务端:same characteristic!"); } byte[] value = characteristic.getValue(); if (value == null) { value = "init responseData".getBytes(); characteristic.setValue(value); } // offset != 0,如果读的偏移不为0,证明是分段读,返回特征值的对应便宜的截断数据就行(每次返回的长度都至末尾) if (offset != 0) { int newLen = value.length - offset; byte[] retVal = new byte[value.length - offset]; System.arraycopy(value, offset, retVal, 0, newLen); value = retVal; } // 请求读特征 if (TIME_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) { bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); } } @Override public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { // super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); Log.d("kaikai", "BLE Server端 onCharacteristicWriteRequest" + " requestId:" + requestId + " offset:" + offset + " prepareWrite:" + preparedWrite + " responseNeeded:" + responseNeeded + " value:" + new String(value)); // 不是分段写 if (!preparedWrite) { characteristic.setValue(value); } // 分段写 else { if (offset == 0) { bleDataByteArray = new ByteArrayOutputStream(); } try { bleDataByteArray.write(value); } catch (IOException e) { throw new RuntimeException(e); } writingObj = characteristic; } // 尝试用更改后的数据作为响应,发现客户端的回调的状态值为133,不为0 byte[] valueWrong = new byte[value.length]; System.arraycopy(value, 0, valueWrong, 0, value.length); valueWrong[0]++; if (responseNeeded) { // 注意,如果写特征值需要响应(特征值的属性是PROPERTY_WRITE不是PROPERTY_WRITE_NO_RESPONSE),必需发送value作为响应数据 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); // 此处为使用错误的数据做响应// bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, valueWrong); } } @Override public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { super.onExecuteWrite(device, requestId, execute); // 当分段写时,才会回调此方法 Log.d("kaikai", "onExecuteWrite called! requestId=" + requestId + " execute=" + execute); if (execute) { byte[] data = bleDataByteArray.toByteArray(); String dataStr = new String(data); Log.d("kaikai", "onExecuteWrite 拼接数据:" + dataStr);// BluetoothGattCharacteristic characteristic1 = bluetoothGattServer.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID);// characteristic1.setValue(data); if (writingObj != null) { if (writingObj instanceof BluetoothGattCharacteristic) { ((BluetoothGattCharacteristic) writingObj).setValue(data); } else if (writingObj instanceof BluetoothGattDescriptor) { ((BluetoothGattDescriptor) writingObj).setValue(data); } else { throw new RuntimeException("writingObj类型不明"); } } else { throw new RuntimeException("writingObj为空"); } // 注意,当写数据过长时,会自动分片,多次调用完onCharacteristicWriteRequest后,便会调用此方法,要在此方法中发送响应,execute参数指示是否执行成功,可按照此参数发送响应的状态 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null); } else { Log.d("kaikai", "BLE SERVER: onExecuteWrite发送失败响应"); // 发送一个失败响应 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null); } } @Override public void onServiceAdded(int status, BluetoothGattService service) { super.onServiceAdded(status, service); Log.d("kaikai", "onServiceAdded"); } @Override public void onMtuChanged(BluetoothDevice device, int mtu) { super.onMtuChanged(device, mtu); Log.d("kaikai", "BLE SERVER:onMTUChanged! mtu=" + mtu);// bluetoothGattServer.sendResponse(device,0,0,0,null); } @Override public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { super.onDescriptorReadRequest(device, requestId, offset, descriptor); Log.d("kaikai", "BLE服务端:onDescriptorReadRequest requestId=" + requestId + " offset=" + offset); byte[] value = descriptor.getValue(); if (value == null) { value = "descriptor1 value".getBytes(); } // 与characteristic的处理一样 if (offset > 0) { int newLen = value.length - offset; byte[] newValue = new byte[newLen]; System.arraycopy(value, offset, newValue, 0, newLen); value = newValue; } bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); } @Override public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); Log.d("kaikai", "BLE服务端:onDescriptorWriteRequest requestId=" + requestId + " preparedWrite=" + preparedWrite + " responseNeeded=" + responseNeeded + " value=" + new String(value)); if (!preparedWrite) { descriptor.setValue(value); } else { if (offset == 0) { bleDataByteArray = new ByteArrayOutputStream(); } try { bleDataByteArray.write(value); } catch (IOException e) { throw new RuntimeException(e); } writingObj = descriptor; } if (responseNeeded) { bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value); } } });
有几点需要注意:
- Characteristic和Descriptor的读和写,在交互数据过长(超出MTU)的情况下,都会产生分段(会回调多次相关的方法,并传入对应的offset值)。写操作主要判断prepareWrite参数,如果为true,则是分段写,并且在最后会回调onExecuteWrite方法。读操作主要用offset判断,只需返回Characteristic或Descriptor的value的在offset处的截断即可,详看代码解释。
- 可用一个临时成员保存正在分段写的Characteristic或Descriptor引用,上述代码使用了一个Object writingObj。
- 写操作的responseNeeded取决于Characteristic的构造函数的第二个参数。在responseNeeded==true时,服务端才需返回响应,并且返回的数据需要和传入的参数value相同。
GATT服务端加入刚才创建的GATT Service
bluetoothGattServer.addService(service);
开始发送BLE广播
/** * 通知服务开启(发广告) */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void startAdvertising() { BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); if (advertiser == null) { Log.d("kaikai", "advertiser为空"); return; } AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED) .setConnectable(true) .setTimeout(0) .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) .build(); AdvertiseData data = new AdvertiseData.Builder().setIncludeDeviceName(true) .setIncludeTxPowerLevel(false) .addServiceUuid(new ParcelUuid(TIME_SERVICE_UUID)) .build(); advertiser.startAdvertising(settings, data, new AdvertiseCallback() { @Override public void onStartSuccess(AdvertiseSettings settingsInEffect) { Log.d("kaikai", "BLE Advertise started!"); } @Override public void onStartFailure(int errorCode) { Log.d("kaikai", "BLE Advertise fail!errorCode=" + errorCode); } }); }
此处有几点需要注意:
- 如果设备名称过长,可能会导致广播失败
- 如果没有开启应用的定位权限,也可能会导致广播失败,请检查应用的相关权限有没开启
AdvertiseCallback回调接口用于判断是否成功开启BLE广播
至此,BLE服务端成功开启。
BLE客户端
扫描设备和服务
public void onBLEScan(View view) { bluetoothAdapter.getBluetoothLeScanner().startScan(scanCallback); }
扫描回调接口
scanCallback = new ScanCallback() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); BluetoothDevice device = result.getDevice(); Log.d("kaikai", "BLE发现设备:" + device.getName() + " " + device.getAddress()); deviceAdapter.add("设备名:" + device.getName() + "地址:" + device.getAddress());// 这里我添加了自己特定的处理逻辑,大家按回自己的逻辑进行处理 if (device.getName() != null && device.getName().equalsIgnoreCase("kk")) { // 搜索到设备名为 “kk”,即可停止搜索了 Log.d("kaikai", "match!"); bluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback); // 这里可接着写连接BluetoothGATT的逻辑,下面会介绍 } } @Override public void onBatchScanResults(List results) { super.onBatchScanResults(results); Log.d("kaikai", "BatchScanResult" + results.toString()); } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); Log.d("kaikai", "BLE Scan failed!"); } };
客户端连接
当搜索到你想要的设备对象(BluetoothDevice)时,就可进行连接了,使用BluetoothGATT对象进行
// 连接 blueToothGatt = device.connectGatt(MainActivity.this, false, new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (newState == BluetoothProfile.STATE_CONNECTED) { Log.d("kaikai", "BLE客户端:BLE连接成功"); blueToothGatt.discoverServices(); if (blueToothGatt == gatt) { Log.d("kaikai", "BLE客户端:same gatt object!"); } Log.d("kaikai", "BLE客户端:开始搜索外围服务"); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.d("kaikai", "BLE客户端:断开BLE连接"); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); if (status == BluetoothGatt.GATT_SUCCESS) { Log.d("kaikai", "BLE客户端:成功搜索到服务"); List services = gatt.getServices(); BluetoothGattService service = gatt.getService(TIME_SERVICE_UUID); if (service == null) { Log.d("kaikai", "service为空"); return; } // 对应的特征 BluetoothGattCharacteristic characteristic = service.getCharacteristic(TIME_CHARACTERISTIC_UUID); gatt.setCharacteristicNotification(characteristic, true); boolean b; // 更改特性描述 // 读一下特性// b = gatt.readCharacteristic(characteristic);// Log.d("kaikai", "b:" + b); // 如果调用了readCharacteristic,则睡眠数秒后再调用readDescriptor也会返回false,应该是同一线程内不能与服务端交互多次// try {// Thread.sleep(5000);// } catch (InterruptedException e) {// e.printStackTrace();// } // 注意,前面如果调用了readCharacteristic,这里就不能直接进行descriptor读取,会返回false。如果没有调用则可 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(TIME_DESCRIPTER_UUID); b = gatt.readDescriptor(descriptor); Log.d("kaikai", "descriptor b:" + b); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { Log.d("kaikai", "BLE客户端:成功读取到特性"); byte[] value = characteristic.getValue(); try { String val = new String(value, "utf8"); Log.d("kaikai", val); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 注意,descriptor的读取须要在完成characteristic读取后执行// BluetoothGattDescriptor descriptor = characteristic.getDescriptor(TIME_DESCRIPTER_UUID);// boolean b = gatt.readDescriptor(descriptor);// Log.d("kaikai", "b2:" + b); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); Log.d("kaikai", "BLE客户端:onCharacteristicWrite called!status=" + status); if (status == BluetoothGatt.GATT_SUCCESS) { Log.d("kaikai", "BLE客户端:写特征值成功"); byte[] value = characteristic.getValue(); Log.d("kaikai", "value:" + new String(value)); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); Log.d("kaikai", "BLE客户端:onCharacteristicChanged"); byte[] newValue = characteristic.getValue(); try { Log.d("kaikai", new String(newValue, "utf8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status); Log.d("kaikai", "BLE客户端:onDescriptorRead status=" + status); Log.d("kaikai", "BLE客户端:成功读取descriptor:" + new String(descriptor.getValue())); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); Log.d("kaikai", "BLE客户端:onDescriptorWrite status=" + status); } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status); Log.d("kaikai", "BLE客户端: onMTUChanged. mtu=" + mtu + " status=" + status); if (status != BluetoothGatt.GATT_SUCCESS) { Log.d("kaikai", "BLE客户端: onMTUChanged status不为0"); } } });
客户端不需要处理分段问题。
onCharacteristicChanged方法可监听服务端推送的特征值改变请求
读操作
/** * BLE客户端读取特征值1 * * @param view */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) public void onBLEClientReadCharacteristic1(View view) { BluetoothGattCharacteristic characteristic = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID); blueToothGatt.readCharacteristic(characteristic); } /** * BLE客户端读取descriptor * * @param view */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) public void onClientReadDescriptor(View view) { BluetoothGattDescriptor descriptor = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID).getDescriptor(TIME_DESCRIPTER_UUID); boolean readDescriptorBool = blueToothGatt.readDescriptor(descriptor); Log.d("kaikai", "readDescriptorBool:" + readDescriptorBool); }
读取成功后,会回调BluetoothGattCallback的相关方法
写操作
/** * BLE客户端写特征值1 * * @param view */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) public void onBLEClientWriteCharacteristic1(View view) throws UnsupportedEncodingException { BluetoothGattCharacteristic characteristic = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID); byte[] characteristicData = characteristic.getValue(); if (characteristicData != null) { Log.d("kaikai", "BLE客户端写特征值前的特征值为:" + new String(characteristicData, "utf8")); } else { Log.d("kaikai", "characteristic value为空"); } characteristic.setValue("client write charrrrrrrrrrrrrr 9999999994444".getBytes()); blueToothGatt.writeCharacteristic(characteristic); }
/** * BLE客户端读写descriptor * * @param view */ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) public void onClientWriteDescriptor(View view) { byte[] dataToWrite = "1111111111222222222233333333334444444444".getBytes(); BluetoothGattDescriptor descriptor = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID) .getDescriptor(TIME_DESCRIPTER_UUID); descriptor.setValue(dataToWrite); boolean result = blueToothGatt.writeDescriptor(descriptor); Log.d("kaikai", "descriptorWriteBool:" + result); }
写操作成功后,会回调BluetoothGattCallback的相关方法
更多相关文章
- 基于web的android图像处理示例(Win7+Apache+PHP+Matlab+Android)
- 『转』Android(安卓)推送方式
- Android艺术探索学习笔记:第2章 IPC机制
- Android(安卓)之 多线程和Socket套接字的使用介绍
- Android(安卓)IPC机制之 Android的各种IPC方式
- Android远程服务例程
- 桌面云的三种模式 VDI IDV VOI (笔记)
- Nginx系列教程(二)| 一文带你读懂Nginx的正向与反向代理
- RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装