Android低功耗蓝牙(BLE)随笔(二)
Android中的实现
1. 扫描广播包
- 首先获取 BluetoothManager 和 BluetoothAdapter
private BluetoothAdapter mBluetoothAdapter; BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter= bluetoothManager.getAdapter();
- 开始扫描
/** * 开始扫描,扫描结果在 BluetoothAdapter.LeScanCallback 中返回 * @param deviceName 扫描的设备名 */ public void startScan(String deviceName) { if (mBluetoothAdapter == null) { // error return; } BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); this.mScanning = true; List filters = new ArrayList<>(); ScanFilter filter = new ScanFilter.Builder() .setDeviceName(deviceName) .build(); filters.add(filter); scanner.startScan(filters, new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(), mScanCallback); }
这里的扫描设置了过滤的参数,只返回设备名为传入参数的广播。如果要扫描特定的设备,也可以根据设备的Mac地址过滤。或者也可以用不过滤方式扫描,过滤逻辑可以在扫描结果中处理。
private ScanCallback mScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { // 广播的信息,可以在result中获取 Log.e(TAG, "onScanResult: name: " + result.getDevice().getName() + ", address: " + result.getDevice().getAddress() + ", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord()); } };
开启蓝牙扫描会增加手机功耗,Android官方也不建议一直进行扫描,因此无论有没有扫描到目标设备,都应该设置一个超时时间,避免长时间扫描。停止扫描指令如下:
public void stopScan() { BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); scanner.stopScan(mScanCallback); }
2. 建立蓝牙连接
如果发现需要进行蓝牙连接的设备,就可以发起建立蓝牙连接请求:
/** * 建立蓝牙连接 * @param address 设备的Mac地址 * @result 连接请求是否成功 */ public boolean connect(Context context, String address) { if (mBluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); error(); return false; } Log.i(TAG, "connecting "); // Previously connected device. Try to reconnect. if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { Log.i(TAG, "尝试使用已有的GATT连接"); if (mBluetoothGatt.connect()) { return true; } else { return false; } } final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { Log.i(TAG, "Device not found. Unable to connect."); return false; } // 连接的核心代码 mBluetoothGatt = device.connectGatt(context, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); return true; }
连接是否成功可以在回调函数中获取:
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { mConnectionState = STATE_CONNECTED; Log.i(TAG, "Connected to GATT server."); // 连接成功 ... } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { mConnectionState = STATE_DISCONNECTED; // 断开连接 ... } else { // 其他错误情况 } } };
onConnectionStateChange方法在连接状态改变后被调用,status是之前状态,newState是改变后的状态。类BluetoothGattCallback 除了得到连接状态的变化,还可以得到其他信息。稍后会逐步介绍。
3. 发现服务
如果蓝牙连接成功,就可以通过GATT协议进行通讯。前面介绍过,蓝牙的信息是通过特征值(characteristics)和服务(services)的传输。因此首先得获取设备的服务:
public void discoverServices() { mBluetoothGatt.discoverServices(); }
发现服务的结果在BluetoothGattCallback的另一个方法中返回:
private BleDeviceService mBleDeviceService ; private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { //获取服务成功 //可用gatt.getServices()获取Service,并用BleDeviceService缓存起来,供访问使用。 mBleDeviceService = new BleDeviceService(gatt.getServices()); } else { //获取服务失败 } } };
其中BleDeviceService 用List缓存了相关的Service,并提供了用UUID查询的方法:
public class BleDeviceService { private final List bluetoothGattServices; public BleDeviceService(List bluetoothGattServices) { this.bluetoothGattServices = bluetoothGattServices; } /** * 获取全部 BluetoothGattService */ public List getBluetoothGattServices() { return bluetoothGattServices; } /** * 获取特定 BluetoothGattService * @param serviceUuid UUID service的标识 * @return BluetoothGattService 查找不到返回null */ public BluetoothGattService getService(@NonNull final UUID serviceUuid){ for (BluetoothGattService bluetoothGattService : bluetoothGattServices) { if (bluetoothGattService.getUuid().equals(serviceUuid)){ return bluetoothGattService; } } return null; } /** * 获取特定特征值 * @param characteristicUuid 特征值UUID * @return BluetoothGattCharacteristic 特征值,查找不到返回null */ public BluetoothGattCharacteristic getCharacteristic(@NonNull UUID serviceUuid,@NonNull UUID characteristicUuid) { BluetoothGattService bluetoothGattService = getService(serviceUuid); if (bluetoothGattService != null){ BluetoothGattCharacteristic characteristic = bluetoothGattService.getCharacteristic(characteristicUuid); if (characteristic != null){ return characteristic; } } return null; }}
4. 读写特征值
有了Service的信息,就可以进行读写特征值了,读特征值是获取设备的信息,写特征值是,发送信息给设备。
读特征值:
/** * 读特征值 * * @param serviceUUID 服务 UUID * @param characteristicUUID 特征值 UUID */ public void readCharacteristic(String serviceUUID, String characteristicUUID) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } boolean isError = false; if (mBleDeviceService != null) { BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID), UUID.fromString(characteristicUUID)); if (characteristic != null) { int permission = characteristic.getProperties(); if ((permission & BleConstants.AT_BLE_PERMISSION_CHAR_READ) == BleConstants.AT_BLE_PERMISSION_CHAR_READ) { Log.i(TAG, "reading characteristic"); mBluetoothGatt.readCharacteristic(characteristic); } else { Log.i(TAG, "read permission denied"); isError = true; } } else { Log.i(TAG, "characteristic is null"); isError = true; } } else { Log.i(TAG, "mBleDeviceService is null"); isError = true; } if (isError) { // 处理错误 } }
写特征值:
private void write(String serviceUUID, String characteristicUUID, byte[] data) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { //错误 return; } if (mBleDeviceService != null) { BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID), UUID.fromString(characteristicUUID)); if (characteristic != null) { int permission = (byte) characteristic.getProperties(); if ((permission & BleConstants.AT_BLE_PERMISSION_CHAR_WRITE) == BleConstants.AT_BLE_PERMISSION_CHAR_WRITE || (permission & BleConstants.AT_BLE_PERMISSION_CHAR_SIGNED_WRITE) == BleConstants.AT_BLE_PERMISSION_CHAR_SIGNED_WRITE || (permission & BleConstants.AT_BLE_PERMISSION_CHAR_WRITE_WITHOUT_RESPONSE) == BleConstants.AT_BLE_PERMISSION_CHAR_WRITE_WITHOUT_RESPONSE || (permission & BleConstants.AT_BLE_PERMISSION_CHAR_RELIABLE_WRITE) == BleConstants.AT_BLE_PERMISSION_CHAR_RELIABLE_WRITE) { //可以设置特征值的类型,默认为有应答。 //characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); characteristic.setValue(data); mBluetoothGatt.writeCharacteristic(characteristic); Log.i(TAG, "writing characteristic done"); } else { Log.i(TAG, "writing permission denied"); //error } } else { Log.i(TAG, "null characteristic"); //error } } }
这里注释的这一行(characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);)需要说明一下,如果不设置,默认的写特征值类型是有应答的。有无应答体现在底层通信上,不会影响到写特征值的回调函数的调用。目前发现区别是:对某些蓝牙芯片,无应答的通讯速率会略快,而且对调大的最大MTU(Maximum Transmission Unit,传输单元),也会减少出错的几率。
读写是否成功的结果同样也是在回调函数中获得:
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG, "read characteristic success"); // 通过characteristic.getValue()获取信息 ... } else { Log.i(TAG, "read characteristic fail " + status); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG, "characteristic write success "); ... } else { Log.i(TAG, "characteristic write fail " + status); ... } } };
5. 使能和接收通知
读特征值的方式只能是手机去读取设备的信息,而不能设备主动发送信息过来。如果想要实现设备主动发送信息给手机,必须手机端先使能一个特征值,之后设备就可以通过这个特征值发送信息。
使能的方法:
/** * 使能通知 * * @param serviceUUID 服务UUID * @param characteristicUUID 特征值UUID * @param notificationFlag 是否开启 */ public void toggleNotification(String serviceUUID, String characteristicUUID, boolean notificationFlag) { if ( mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); // error return; } if (mBleDeviceService != null) { BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID), UUID.fromString(characteristicUUID)); if (characteristic != null) { int permission = (byte) characteristic.getProperties(); if ((permission & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { UUID CCC = UUID.fromString(BleConstants.CONFIG_DESCRIPTOR); mBluetoothGatt.setCharacteristicNotification(characteristic, notificationFlag); //Enabled locally BluetoothGattDescriptor config = characteristic.getDescriptor(CCC); config.setValue(notificationFlag ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(config); //Enabled remotely } else { Log.i(TAG, "characteristic has no notification property"); // error } } else { Log.i(TAG, "null characteristic"); // error } } }
获取使能是否成功的结果以及接收设备发送来的信息的回调方法:
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); Log.i(TAG, "onCharacteristicChanged: " + Arrays.toString(characteristic.getValue()) + " " + characteristic.getUuid().toString()); // 可以用characteristic.getValue()读取信息。 } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG, "Notification/indication enabled successfully"); ... } else { Log.i(TAG, "Notification/indication enabled failed"); // error } } };
代码中用的是Notification,还有另一个方法是关于Indication的(将toggleNotification方法内的BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE替换为BluetoothGattDescriptor.ENABLE_INDICATION_VALUE),二者的区别也是前者类似TCP后者类似UDP。
5. 断开连接
最后,就是通讯结束后断开连接的方法:
public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.i(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.disconnect(); }
更多相关文章
- Android(安卓)蓝牙配对、连接和通信
- Android学习笔记——蓝牙入门
- Android调用MediaScanner进行扫描
- android开发笔记之2012版辅助开发工具包(ADT)新功能特性介绍及安装
- 2012版辅助开发工具包(ADT)新功能特性介绍及安装使用
- 启动qt_Qt编程实例:基于Android的BLE通信软件
- 条码扫描二维码扫描——ZXing android 源码简化
- Android(安卓)Wear手表蓝牙连接Android(安卓)Studio调试/开发的
- android Zxing二维码扫描 竖屏切换问题的解决