Android - 连续扫描几次都找不到BLE设备

问题描述 投票:0回答:2

我们创建了一个 Android 应用程序,可使用 BLE(低功耗蓝牙)扫描特定制造商设备。实际上一切都很好,但我们有一个问题。有时,经过几次扫描后,平板电脑将无法在随后的所有扫描中找到任何设备。唯一的解决方法是关闭蓝牙然后再次打开,然后平板电脑可以再次找到设备。该环境大约有 30 个 BLE 设备,其中 20 个是我们想要查找和过滤的设备。客户端可以重现它,但不幸的是我们不能,因此很难调试。 扫描只能在前台进行,不需要在后台扫描。用户可以在扫描结束后立即重新启动扫描。我已经知道 30 秒/5 次扫描的限制,这处理得很好。

目标平板电脑运行Android 8.1,项目设置为:

compileSdkVersion = 28 buildToolsVersion = '30.0.2' minSdkVersion = 17 targetSdkVersion = 28

BLE 扫描设置:

ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) .setReportDelay(0L) .build(); new ScanFilter.Builder().setManufacturerData(MANUFACTURE_ID, new byte[0]).build());

扫描工作时,日志如下所示:

I/BleScanService: Start Scanning D/Fragment: refreshUIElements true D/BluetoothAdapter: isLeEnabled(): ON D/BluetoothLeScanner: onScannerRegistered() - status=0 scannerId=7 mScannerId=0 I/Fragment$ScannerBroadcastReceiver: listing device BLE: deviceMacAddress:EC:A5:80:12:D4:1A, nwpBleMacAddress:XXXXXXXXXXEE, name:Device I/Fragment$ScannerBroadcastReceiver: listing device BLE: deviceMacAddress:CE:A8:80:60:C9:A8, nwpBleMacAddress:XXXXXXXXXX08, name:Device D/BluetoothAdapter: isLeEnabled(): ON I/BleScanService: Stopped Scanning

当扫描停止工作时,我得到这样的日志(有趣的是 isLeEnabled(): ON 日志条目不再出现):

07-28 14:02:48:310 I/BleScanService(2) : Start Scanning 07-28 14:02:48:316 I/BleScanService(2) : Resume Scanning in 0 ms 07-28 14:02:48:324 D/Fragment(2) : refreshUIElements true 07-28 14:02:54:357 I/BleScanService(2) : Stopped Scanning

这是实现所有魔力的 ScanService 类:

public class BleScanService { private final Handler scanHandler; private final Context context; private final Settings settings; private BluetoothAdapter bluetoothAdapter; private BluetoothLeScanner bluetoothLeScanner; private final Set<String> discoveredDevices; private final List<Long> scanTimestamps = new ArrayList<>(10); private boolean scanning; @Inject public BleScanService(Context context, Settings settings) { this.context = context; this.settings = settings; this.scanHandler = new Handler(); } public BluetoothDevice getBluetoothDevice(String deviceMacAddress) { return bluetoothAdapter.getRemoteDevice(deviceMacAddress); } public boolean isScanning() { return scanning; } public void startScanning() { if (scanning) { Timber.i("Ignore start scanning request because scanning is already active"); return; } try { scanning = true; discoveredDevices.clear(); initService(); long nextScannerAvailability = getNextScannerAvailability(); Timber.i("Resume Scanning in %s ms", nextScannerAvailability); scanHandler.postDelayed(this::scan, nextScannerAvailability); } catch (Exception e) { Timber.e(e); stopScanning(); } } private void scan() { bluetoothLeScanner.startScan(BleScanSettings.getManufacturerScanFilter(), BleScanSettings.getScanSettings(), leScanCallback); scanHandler.postDelayed(() -> { stopScanning(); Timber.i("Devices shown %s", String.join(", ", discoveredDevices)); }, 8000); } private void initService() { if (bluetoothAdapter == null) { BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); if (bluetoothManager != null) { bluetoothAdapter = bluetoothManager.getAdapter(); } if (bluetoothLeScanner == null) { bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); } } } private long getNextScannerAvailability() { final long currentTimeMillis = System.currentTimeMillis(); scanTimestamps.removeIf(t -> currentTimeMillis - t > 30000); if (scanTimestamps.size() < 4) { return 0; } long oldestActiveTimestamp = scanTimestamps.get(scanTimestamps.size() - 4); return 30000 - (currentTimeMillis - oldestActiveTimestamp) + 500; } public void stopScanning() { if (bluetoothAdapter == null) { return; } try { bluetoothLeScanner.stopScan(leScanCallback); } catch (Exception e) { Timber.w(e, "Exception occurred while attempting to stop BLE scanning."); } if (scanning) { scanTimestamps.add(System.currentTimeMillis()); } scanHandler.removeCallbacksAndMessages(null); scanning = false; Timber.i("Stopped Scanning"); } protected void broadcastDetectedDevice(Intent intent) { context.sendBroadcast(intent); } private final ScanCallback leScanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); if (!scanning) { Timber.i("ignore scanning result because the scanning is paused"); return; } if (result.getScanRecord() == null) { Timber.i("Skip unsupported device %s", result.getDevice().getName()); return; } BluetoothDevice device = result.getDevice(); String deviceMacAddress = device.getAddress(); if (isAlreadyDiscovered(deviceMacAddress)) { return; } byte[] manufacturerSpecificData = result.getScanRecord().getManufacturerSpecificData(BleScanSettings.MANUFACTURE_ID); final Optional<Intent> msdIntent = BleDetectedDeviceIntentHelper .getIntentFromManufacturerSpecificData(deviceMacAddress, manufacturerSpecificData); if (msdIntent.isPresent()) { handleManufacturerDataScan(deviceMacAddress, msdIntent.get()); } } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); Timber.i("SCAN FAILED"); stopScanning(); }

使用和请求的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.CAMERA" />

有什么想法吗?谢谢您的帮助!

java android bluetooth-lowenergy android-bluetooth
2个回答
1
投票

8秒延迟后自动停止并重新开始扫描可能会解决问题。

此外,从 Android 12+ 开始,应用程序需要

BLUETOOTH_SCAN

权限才能扫描蓝牙设备。

将此行添加到您的应用程序清单中:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

这里是请求权限的示例代码:

public void requestBluetoothPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (this.checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.BLUETOOTH_SCAN)) { // display permission rationale in snackbar or dialog message } else { this.requestPermissions(new String[]{ Manifest.permission.BLUETOOTH_SCAN }, MyActivity.REQUEST_BLUETOOTH_PERMISSIONS); } } } }



0
投票
Starting from Android 7

,对于蓝牙 LE,

Android limits
设备扫描到
5 times every 30 seconds
的数量。如果超过此限制,它将自动停止,并且您将不再接收任何数据。
    

© www.soinside.com 2019 - 2024. All rights reserved.