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<ScanFilter> 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<BluetoothGattService> bluetoothGattServices;
public BleDeviceService(List<BluetoothGattService> bluetoothGattServices) {
this.bluetoothGattServices = bluetoothGattServices;
}
/**
* 获取全部 BluetoothGattService
*/
public List<BluetoothGattService> 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();
}