在之前的文章中,我讨论了BLE数据的读写操作 以及如何实现一个用于执行这些命令的队列。在本文中,我们将讨论的主题是 绑定。
绑定
有些设备需要进行"绑定"才能正常工作。从技术上讲,绑定意味着生成、交换和存储加密密钥,以使通信更加安全。当进行绑定过程时,Android可能会要求用户 同意 、输入PIN码 或 密码短语。在后续的连接中,Android知道设备已经绑定,只会进行一些"底层"存储密钥的交换。绑定的重要优势在于连接是加密的,因此更加安全。
绑定(bonding)的内容在Google的文档中几乎没有详细记录,因此不清楚应用程序应该如何处理。其中一个你可能注意到的是 createBond
方法。在iOS中根本没有这样的方法,操作系统会自动处理所有绑定过程!那么为什么需要调用 createBond()
呢?我觉得这很奇怪,因为这意味着你事先就知道哪些设备需要进行绑定,哪些不需要。蓝牙并不是设计成这样的,外围设备通常会明确说明它们需要进行绑定。因此,我开始深入研究并进行了一些实验。花了一些时间,但最终发现其实很简单。
以下是处理绑定的原则:
-
让 Android 处理绑定 当设备指示需要绑定或尝试读取或写入加密的特征时,Android会自动为您执行绑定操作。在大多数情况下,您无需自己调用createBond()方法。
-
在绑定过程进行中,请不要进行任何操作 如果在绑定过程进行中执行发现服务、读写等操作,可能会导致错误并且连接可能会中断。请让Android自行处理。
-
在绑定完成后,请继续执行您之前的操作队列 一旦绑定完成,您需要继续进行之前的操作。
-
如果您知道自己在做什么,并且确实有必要 ,您可以调用
createBond()
来自行启动与设备的绑定过程。但这应该是个例外情况。
是什么触发了绑定过程?
有三种常见的触发绑定过程的方式:
- 在与外设 建立连接 时,外设可以发出信号,表示在进行任何其他操作之前需要进行绑定。
- 一个特征值可以被加密以进行读取或写入操作 。当你尝试读取或写入时,它会触发绑定操作。如果绑定成功,读取或写入操作也会成功。如果绑定失败,读取或写入操作也会失败,并且会出现
INSUFFICIENT_AUTHENTICATION
错误。这与iOS上的错误相同。 - 你可以通过调用
createBond()
方法来 触发绑定过程。如果你的设备需要这个过程,那么它可能不兼容iOS,因为iOS没有相应的调用方法。但在蓝牙标准中是可能的!
让我们详细讨论这几种情况:
连接时进行绑定
如果外围设备在连接建立后需要直接进行配对,当调用 onConnectionStateChange
时,配对状态将为 BOND_BONDING
。这意味着配对正在进行中,你不应该做任何操作,比如调用 discoverServices()
,直到配对完成!如果你这样做,可能会出现意外断开连接或服务发现失败的情况。因此,你需要在 onConnectionStateChanged
中专门处理这种情况,像这样:
scss
// Take action depending on the bond state
if(bondstate == BOND_NONE || bondstate == BOND_BONDED) {
// Connected to device, now proceed to discover it's services
...
} else if (bondstate == BOND_BONDING) {
// Bonding process has already started let it complete
Log.i(TAG, "waiting for bonding to complete");
}
为了跟踪配对过程的进展,你需要在调用 connectGatt
之前为 ACTION_BOND_STATE_CHANGED
注册一个 BroadcastReceiver
,并在配对过程中检查进度。这个回调将在配对状态发生变化时被多次调用。
java
context.registerReceiver(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED));
private final BroadcastReceiver bondStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Ignore updates for other devices
if (bluetoothGatt == null || !device.getAddress().equals(bluetoothGatt.getDevice().getAddress()))
return;
// Check if action is valid
if(action == null) return;
// Take action depending on new bond state
if (action.equals(ACTION_BOND_STATE_CHANGED)) {
final int bondState = intent.getIntExtra(EXTRA_BOND_STATE, ERROR);
final int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);
switch (bondState) {
case BOND_BONDING:
// Bonding started
...
break;
case BOND_BONDED:
// Bonding succeeded
...
break;
case BOND_NONE:
// Oh oh
...
break;
}
}
}
};
一旦配对成功,你可以开始发现服务的操作,因为这之前还没有发生。你可以通过检查是否已经存在服务来进行双重确认:
typescript
case BOND_BONDED:
// Bonding succeeded
Log.d(TAG, "bonded");
// Check if there are services
if(bluetoothGatt.getServices().isEmpty()) {
// No services discovered yet
bleHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, String.format("discovering services of '%s'", getName()));
boolean result = bluetoothGatt.discoverServices();
if (!result) {
Log.e(TAG, "discoverServices failed to start");
}
}
});
}
当读写加密特征值时进行绑定
当读写操作触发绑定时,操作将首先失败,并且设备将返回 GATT_INSUFFICIENT_AUTHENTICATION
的错误。在 Android 6 和 7 上,你实际上会在 onCharacteristicRead
或 onCharacteristicWrite
中收到此错误,但是绑定过程已经在 Android 内部启动。从 Android 8 开始,你将不会看到此错误,并且 Android 将在绑定完成后自动重试操作。然而,在 Android 6 和 7 上,你需要自己捕获错误并在配对完成后重试操作。因此,你需要在绑定完成后捕获错误并重试操作。
所以当你收到错误时,你不需要做任何操作,也不要立即完成指令。
arduino
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
// Perform some checks on the status field
if (status != GATT_SUCCESS) {
if (status == GATT_INSUFFICIENT_AUTHENTICATION ) {
// Characteristic encrypted and needs bonding,
// So retry operation after bonding completes
// This only happens on Android 5/6/7
Log.w(TAG, "read needs bonding, bonding in progress");
return;
} else {
Log.e(TAG, String.format(Locale.ENGLISH,"ERROR: Read failed for characteristic: %s, status %d", characteristic.getUuid(), status));
completedCommand();
return;
}
}
...
一旦绑定完成,你需要检查队列是否仍然处于"繁忙"状态,这意味着某个命令尚未完成。然后你可以重试该操作。
scss
case BOND_BONDED:
// Bonding succeeded
Log.d(TAG, "bonded");
// Check if there are services
...
// If bonding was triggered by a read/write, we must retry it
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
if (commandQueueBusy && !manuallyBonding) {
bleHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "retrying command after bonding");
retryCommand();
}
}, 50);
}
}
主动调用 createBond
虽然我之前告诉过你不要自己调用 createBond
方法,但你有可能会调用。然而,请确认是否真的需要这样做。在iOS上没有 createBond
的等效方法,所以如果调用 createBond
是唯一的配对方式,那么你的设备可能不兼容iOS。iOS的硬件设计指南明确表示不支持此功能。我尝试过几十个蓝牙低功耗设备,只有在特殊情况下才需要自己调用 createBond
方法。
如果你主动调用了 createBond
方法,仍然不能同时进行其他操作,并且需要注册广播来监听配对过程。在这个意义上,它与其他情况下的使用方式相同。请注意,如果你在已经配对的设备上调用 createBond
方法,会收到错误提示,因此在调用之前请进行检查。
还有一个原因你可能仍然想自己调用 createBond
方法,那就是为了更方便地进行重新连接。如果你想通过MAC地址获取一个BluetoothDevice对象,只有当你的设备被缓存或配对时才能成功!因此,你可能希望利用这个事实进行配对,即使它并不是必需的。这样一来,你就不需要再次扫描设备...可能会派上用场!
移除绑定
作为用户,你可以在蓝牙设置菜单中看到已配对设备的列表。在那里,你可以移除设备,从而解除配对关系。
完全处理解除配对需要一些时间。
奇怪的是,没有官方提供的API来解除绑定。虽然有一个隐藏的方法 removeBond
,但你必须使用反射来调用它。以下是如何实现的:
java
try {
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
result = (boolean) method.invoke(device, (Object[]) null);
if (result) {
Log.i(TAG, "Successfully removed bond");
}
return result;
} catch (Exception e) {
Log.e(TAG, "ERROR: could not remove bond");
e.printStackTrace();
return false;
}
失去绑定
除了移除绑定之外,你还可能会失去绑定!大多数设备只支持与一个手机进行配对。正常的使用情况如下:
- 手机A与设备X进行配对
- 手机B与设备X进行配对
- 手机A重新连接到设备X,此时配对关系丢失了
因此,当手机A再次连接时,初始时会显示配对状态为 BOND_BONDED ,但在广播接收器中会立即接收到新的状态 BOND_NONE。通过比较先前的配对状态和新的状态,您可以确定是否刚刚失去了一个配对关系。
java
case BOND_NONE:
if(previousBondState == BOND_BONDING) {
// Bonding failed
...
} else {
// Bond lost
...
}
disconnect();
break;
当您失去一个配对关系时,必须断开连接,否则会发生奇怪的事情,通信将无法正常工作。当您重新连接时,Android会尝试重新建立配对关系。同样适用于配对失败的情况。
有一个小问题/错误您需要知道。如果您失去一个绑定关系,似乎需要 1秒钟 才能完全更新蓝牙堆栈的内部管理。因此,如果您失去一个绑定关系,立即断开连接并重新连接,Android会告诉您设备仍然处于配对状态,但它不会完全工作...所以在断开连接和重新连接之前请等待1秒钟。
总结
所以,这就结束了关于 Android BLE开发 的系列文章!我希望这些信息对你有用,能让你更愉快地使用BLE。我发现,更好地理解Android上的BLE之后,你可以使其工作得相当好。加油!