Android 13更新后BLE扫描结果频繁丢失,待机桶值20是否为诱因?
后台BLE扫描在Android 13下的扫描丢失问题
首先我尝试了后台BLE扫描(周期性执行)。该应用需要大量后台运行,这是客户的需求。
实现方式
- 使用Alarm Manager每小时唤醒应用。
- 收到应用广播接收器的回调后,调度下一次闹钟并启动带expedited选项的WorkManager。
- 启动40秒BLE扫描 -> 将BLE设备数据发送至服务器。
相关代码
Manifest.xml
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> <uses-feature android:name="android.hardware.location.gps" android:required="true" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" /> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
scanCallbackImpl.java
public class ScanCallbackImpl extends ScanCallback { private final PublishSubject<ScanResult> onScanResult$; private final Context context; private static final String SCAN_TEST = "SCAN_TEST"; public ScanCallbackImpl(Context context) { super(); this.context = context; onScanResult$ = PublishSubject.create(); } @Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); Log.d(SCAN_TEST, "onScanResult"); onScanResult$.onNext(result); } @Override public void onBatchScanResults(List<ScanResult> results) { super.onBatchScanResults(results); Log.d(SCAN_TEST, "onBatchScanResult"); } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); Log.d(SCAN_TEST, "onScanFailed"); switch (errorCode) { case ScanCallback.SCAN_FAILED_ALREADY_STARTED: onScanResult$.onError(new AlreadyStartedException(context)); break; case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: onScanResult$.onError(new ApplicationRegistrationFailedException(context)); break; case ScanCallback.SCAN_FAILED_INTERNAL_ERROR: onScanResult$.onError(new InternalErrorException(context)); break; case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED: onScanResult$.onError(new FeatureUnsupportedException(context)); break; } } public PublishSubject<ScanResult> getOnScanResult$() { return onScanResult$; } }
ScanService.java
private void scanClose() { if(onScanResult != null && !onScanResult.isDisposed()){ onScanResult.dispose(); } if(startScan != null && !startScan.isDisposed()){ startScan.dispose(); } } public Observable<ScanDeviceVo> startScan(String deviceName, String macAddress, long timeoutSec, ScanMode scanMode) { scanClose(); Observable<ScanResult> observable = Observable.create(emitter -> { if(!checkBleScanSupport()){ emitter.onError(new BleScanUnsupportedException(context)); return; } if(!checkEnable(emitter)){ return; } if(!isAlwaysLocation()){ emitter.onError(new LocationPermissionException(context)); return; } BluetoothLeScanner bleScanner = bluetoothAdapter.getBluetoothLeScanner(); scanCallback = new ScanCallbackImpl(context); scanCallback.getOnScanResult$() .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .doOnSubscribe(disposable -> onScanResult = disposable) .subscribe(emitter::onNext, emitter::onError); isScanning = true; bleScanner.startScan(setScanFilterList(deviceName, macAddress), setScanSetting(scanMode), scanCallback); }); return observable .observeOn(Schedulers.io()) .take(timeoutSec, TimeUnit.SECONDS) .timeout(timeoutSec, TimeUnit.SECONDS) .onErrorResumeNext(throwable -> throwable instanceof TimeoutException ? Observable.error(new NotFoundException(context)) : Observable.error(throwable)) .distinct(scanResult -> scanResult.getScanRecord().getBytes()) .map(scanResult -> ScanDeviceVo.builder() .name(scanResult.getDevice().getName()) .macAddr(scanResult.getDevice().getAddress()) .packet(scanResult.getScanRecord().getBytes()) .rssi(scanResult.getRssi()).build()) .doOnSubscribe(disposable -> startScan = disposable) .doOnTerminate(this::stopScan); } private ScanSettings setScanSetting(ScanMode scanMode){ ScanSettings.Builder builder = new ScanSettings.Builder(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { builder = builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE); } return builder.setScanMode(scanMode.rawValue()) .setReportDelay(0) .build(); } private List<ScanFilter> setScanFilterList(String deviceName, String macAddress) { List<ScanFilter> scanFilterList = new Vector<>(); ScanFilter.Builder builder = new ScanFilter.Builder(); if(!deviceName.equals("")){ builder = builder.setDeviceName(deviceName); } if(!macAddress.equals("")){ builder = builder.setDeviceAddress(macAddress); } scanFilterList.add(builder.build()); return scanFilterList; }
ScanWorker.java
.... .... ScanService scanService = new ScanService(context); scanService.startScan(deviceName, syncedMac, 40, ScanMode.BALANCED) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) .firstOrError() .onErrorResumeNext(throwable -> { scanService.stopScan(); return throwable instanceof NoSuchElementException ? Single.error(new NotFoundException(context)) : Single.error(throwable); }) .map(scanDeviceVo -> new AdvData(scanDeviceVo.getPacket())) .subscribe(advData -> { ..send with retrofit.. }); .... ....
问题现象与疑问
该方案在Android 12及更早版本(甚至深度Doze模式)运行正常,但更新至Android 13后,BLE扫描结果频繁丢失——闹钟可正常工作,带expedited选项的WorkManager也能正常运行,但ScanCallback的onScanResult很少被调用。
了解到Android 13的电池优化变更:如果应用接收过多广播接收器,会被加入受限应用待机桶。执行命令 adb shell am get-standby-bucket PACKAGE_NAME,返回值为20。
请问待机桶值20是否与BLE扫描结果频繁丢失相关?
内容的提问来源于stack exchange,提问作者daemon
相关产品推荐
相关产品推荐

