Android BLE开发入门(4) —— 绑定

在之前的文章中,我讨论了BLE数据的读写操作 以及如何实现一个用于执行这些命令的队列。在本文中,我们将讨论的主题是 绑定

原文: medium.com/@martijn.va...

绑定

有些设备需要进行"绑定"才能正常工作。从技术上讲,绑定意味着生成、交换和存储加密密钥,以使通信更加安全。当进行绑定过程时,Android可能会要求用户 同意输入PIN码密码短语。在后续的连接中,Android知道设备已经绑定,只会进行一些"底层"存储密钥的交换。绑定的重要优势在于连接是加密的,因此更加安全。

绑定(bonding)的内容在Google的文档中几乎没有详细记录,因此不清楚应用程序应该如何处理。其中一个你可能注意到的是 createBond 方法。在iOS中根本没有这样的方法,操作系统会自动处理所有绑定过程!那么为什么需要调用 createBond() 呢?我觉得这很奇怪,因为这意味着你事先就知道哪些设备需要进行绑定,哪些不需要。蓝牙并不是设计成这样的,外围设备通常会明确说明它们需要进行绑定。因此,我开始深入研究并进行了一些实验。花了一些时间,但最终发现其实很简单。

以下是处理绑定的原则:

  • 让 Android 处理绑定 当设备指示需要绑定或尝试读取或写入加密的特征时,Android会自动为您执行绑定操作。在大多数情况下,您无需自己调用createBond()方法。

  • 在绑定过程进行中,请不要进行任何操作 如果在绑定过程进行中执行发现服务、读写等操作,可能会导致错误并且连接可能会中断。请让Android自行处理。

  • 在绑定完成后,请继续执行您之前的操作队列 一旦绑定完成,您需要继续进行之前的操作。

  • 如果您知道自己在做什么,并且确实有必要 ,您可以调用 createBond() 来自行启动与设备的绑定过程。但这应该是个例外情况。

是什么触发了绑定过程?

有三种常见的触发绑定过程的方式:

  1. 在与外设 建立连接 时,外设可以发出信号,表示在进行任何其他操作之前需要进行绑定。
  2. 一个特征值可以被加密以进行读取或写入操作 。当你尝试读取或写入时,它会触发绑定操作。如果绑定成功,读取或写入操作也会成功。如果绑定失败,读取或写入操作也会失败,并且会出现 INSUFFICIENT_AUTHENTICATION 错误。这与iOS上的错误相同。
  3. 你可以通过调用 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 上,你实际上会在 onCharacteristicReadonCharacteristicWrite 中收到此错误,但是绑定过程已经在 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之后,你可以使其工作得相当好。加油!

相关推荐
CheungChunChiu11 分钟前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜1 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0071 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp
江上清风山间明月1 小时前
flutter bottomSheet 控件详解
android·flutter·底部导航·bottomsheet
Wanliang Li1 小时前
Linux电源管理——CPU Hotplug 流程
linux·嵌入式硬件·嵌入式·armv8·电源管理·cpuhotplug
憧憬一下3 小时前
PCI/PCIe设备INTx中断机制和MSI中断机制
arm开发·嵌入式硬件·嵌入式·linux驱动开发·pci/pcie
Crossoads3 小时前
【汇编语言】外中断(一)—— 外中断的魔法:PC机键盘如何触发计算机响应
android·开发语言·数据库·深度学习·机器学习·计算机外设·汇编语言
sunphp开发者4 小时前
黑客攻击网站,篡改首页问题排查修复
android·js
我又来搬代码了4 小时前
【Android Studio】创建新项目遇到的一些问题
android·ide·android studio
ggs_and_ddu8 小时前
Android--java实现手机亮度控制
android·java·智能手机