在 Android 中检测 BLE 设备名称更改

Detecting BLE Device name change in Android

我们有一个正在使用的 BLE 设备,它通过设备名称输出数据。该设备运行正常,可以看到使用 nRF Connect 等应用程序正确更改名称。然而,我们在自己的 Android 应用程序中遇到了困难。我们可以很好地检测到这些设备,但它们几乎永远不会超过给定的原始名称。

我开始使用的代码有一个在 onResume() 中启动的循环,该循环使用 BluetoothLeScanner 和 startScan() 函数进行扫描。

public void BLEScan(final boolean enable){

    final int SCAN_PERIOD = 12000;
    final BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

    if (enable) {
        Log.d(TAG, "Starting Scan");
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(false);
                invalidateOptionsMenu();
            }
        }, SCAN_PERIOD);
        
        bluetoothLeScanner.flushPendingScanResults(mLeScanCallback);
        bluetoothLeScanner.startScan(mLeScanCallback);
    } else {
        Log.d(TAG, "Stopping Scan");
        bluetoothLeScanner.flushPendingScanResults(mLeScanCallback);
        bluetoothLeScanner.stopScan(mLeScanCallback);
        
        mHandler.removeCallbacksAndMessages(null);

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(true);
            }
        }, SCAN_PERIOD);
    }
}

并且在 mLeScanCallback 中检测到基于设备进行过滤。基本上,如果它之前没有看到设备,它会将其添加到信标列表中。但是,如果它之前看到过它,它会使用 Beacon 的 seen() 函数和 Beacon 的名称更新值,因为这就是信息的来源。在这两种情况下,它都会更新其适配器以填充新信息。

private ScanCallback mLeScanCallback =
    new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            final ScanResult res2 = result;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    BluetoothDevice device = res2.getDevice();
                    String address = device.getAddress();
                    
                    if (device.getName() != null){
                        if (device.getName().contains("ABT:")){
                            if (!mLeBeacons.containsKey(address)){
                                BleBeacon beacon = new BleBeacon(device.getName(), address);
                                bleList.add(beacon);
                                adapter.notifyDataSetChanged();

                                mLeBeacons.put(device.getAddress(), beacon);
                            } else {
                                for (int x = 0; x < bleList.size(); x++){
                                    if (device.getAddress().equals(bleList.get(x).getMAC())){
                                        bleList.get(x).seen(device.getName());
                                        adapter.notifyDataSetChanged();
                                    }
                                }
                            }
                        }
                    }
                }
            });
        }
    };

但是,即使在更新名称之后,mLeScanCallback 也只会 return 原来的名称。

在这里搜索一番后,我不断发现的是使用函数 fetchUuidsWithSdp() 和 ACTION_FOUND、ACTION_UUID、ACTION_DISCOVERY_FINISHED 和 [=35 等意图=] 以正确查看名称更改。因此,我将 fetchUuidsWithSdp() 添加到 mLeScanCallback。然而,虽然这会触发 Intents,但名称仍未更新。我尝试在实际意图中调用 fetchUuidsWithSdp() ,但这也没有帮助。奇怪的是,如果我关闭 phone 的屏幕或离 BLE 设备足够远,ACTION_NAME_CHANGED 偶尔会触发。但是,onPause() 所做的只是调用 super.onPause()BLEScan(false)。而且,由于这些是我已经在我的循环中做的事情,我不确定如何在我的代码处于清醒状态时将其引入我的代码中。

经过多方查找,我发现要使用fetchUuidsWithSdq()函数,需要使用BluetoothAdapter的startDiscovery()函数。所以,我改变了 BLEScan 来使用它,完全切断了 mLeScanCallback。

public void BLEScan(final boolean enable){
    final int SCAN_PERIOD = 12000;
    if (enable) {
        Log.d(TAG, "Starting Scan");
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(false);
                invalidateOptionsMenu();
            }
        }, SCAN_PERIOD);

        if (mBluetoothAdapter.isDiscovering()){
            mBluetoothAdapter.cancelDiscovery();
        }
        mBluetoothAdapter.startDiscovery();
    } else {
        Log.d(TAG, "Stopping Scan");
        mBluetoothAdapter.cancelDiscovery();

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                BLEScan(true);
            }
        }, SCAN_PERIOD);
    }
}

然而,尽管 Intent 在我的 Intent 过滤器中,但之前会触发,但现在不会。在做了更多搜索之后,我发现有人说 startDiscovery() 不适用于 Le 设备。所以,我搜索了你应该使用的东西......这让我一路回到 startScan with BluetoothLeScanner。那时,我意识到我绕了一个圈,需要帮助。

我检查了整个过程,因为在某些地方,我遗漏了一些东西。我只是不知道它在哪里。我应该使用 startScan() 吗?我没有正确使用 startDiscovery() 吗?还是我应该完全使用其他东西? ACTION_NAME_CHANGED 偶尔触发的事实让我想回到那个,但我如何让它在设备处于唤醒状态时始终工作?

我认为您可能只是在 android 上遇到缓存问题。请在此处查看此答案以获得可能的解决方案:

虽然我没有发现我在原始代码中做错了什么(尽管听起来 Android 可能只是在这方面被破坏),但我确实找到了解决方法。 nRF 的 NFC 工具箱具有可用的源代码,并使用添加的过滤器进行批量扫描,以及它自己的来自北欧图书馆的 Le 扫描仪。这是我的扫描功能现在的样子:

    public void BLEScan(final boolean enable){

        final BluetoothLeScannerCompat bluetoothLeScanner = BluetoothLeScannerCompat.getScanner();
        final ScanSettings settings = new ScanSettings.Builder()
                .setLegacy(false)
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setReportDelay(1000).setUseHardwareBatchingIfSupported(false).build();
        final List<ScanFilter> filters = new ArrayList<>();
        ParcelUuid Uuid = new ParcelUuid(UUID.fromString(uuidString));
        filters.add(new ScanFilter.Builder().setServiceUuid(Uuid).build());

        if (enable) {
            Log.d(TAG, "Starting Scan");
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    BLEScan(false);
                    invalidateOptionsMenu();
                }
            }, SCAN_PERIOD);

            bluetoothLeScanner.startScan(filters, settings, mLeScanCallback);
        } else {
            BLEService.refreshGatt();
            Log.d(TAG, "Stopping Scan");
            bluetoothLeScanner.stopScan(mLeScanCallback);

            mHandler.removeCallbacksAndMessages(null);

            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    BLEScan(true);
                }
            }, SCAN_PERIOD);
        }
    }

我的回调是这样的:

    private no.nordicsemi.android.support.v18.scanner.ScanCallback mLeScanCallback =
            new no.nordicsemi.android.support.v18.scanner.ScanCallback() {
                @Override
                public void onBatchScanResults(@NonNull final List<no.nordicsemi.android.support.v18.scanner.ScanResult> results) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            for (final no.nordicsemi.android.support.v18.scanner.ScanResult result : results){
                                BluetoothDevice device = result.getDevice();

                                if (device != null){
                                    String address = device.getAddress();
                                    String name = result.getScanRecord() != null ? result.getScanRecord().getDeviceName() : null;

                                    if (!mLeBeacons.containsKey(address)) {
                                        BleBeacon beacon = new BleBeacon(device.getName(), address);
                                        bleList.add(beacon);
                                        adapter.notifyDataSetChanged();

                                        mLeBeacons.put(device.getAddress(), beacon);
                                    } else {
                                        for (int x = 0; x < bleList.size(); x++){
                                            if (device.getAddress().equals(bleList.get(x).getMAC())){
                                                bleList.get(x).seen(device.getName());
                                                adapter.notifyDataSetChanged();
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    });
                }

这似乎可以解决问题。不过,您需要将以下内容添加到您的依赖项中。

implementation 'no.nordicsemi.android.support.v18:scanner:1.4.2'

在那之后,我的名字 ACTION_NAME_CHANGE 意图正确触发并且数据正在更新。

我不确定这是从结果中检索名称的不同方式还是批量扫描。但是,如果连 Nordic 都不使用标准 Android BLE 库,我猜这是最好的方法。