You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 04:02:56