You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android BLE前台服务扫描异常:息屏后中断或频次降低

解决Android BLE前台服务持续扫描异常问题

问题背景

你开发了一款用于扫描BLE设备的简易前台服务,预期实现全天候持续扫描。自研BLE设备每0.5秒发送一帧数据,测试两台设备后发现以下异常:

  • 亮屏状态:Android 9设备每10秒仅接收6-7帧(预期20帧);Android 6设备每10秒接收15-17帧,且扫描存在间隔性启停(扫描10秒后停止5-7秒)。
  • 息屏状态:扫描情况进一步恶化,Android 9设备息屏1分钟后完全停止扫描;Android 6设备息屏后每10秒仅能接收6帧数据。

你的服务核心代码如下:

public class DeviceScan extends Service {
    BluetoothLeScanner btScanner;
    public static final String CHANNEL_ID = "ForegroundServiceChannel";
    List<ScanFilter> filterList = new ArrayList<>();
    {
        filterList.add(new ScanFilter.Builder().setDeviceName("MYBEACON").build());
    }
    private ScanCallback leScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.e("Adress: ",result.getDevice().getAddress());
            Log.e("RSSI: ", " rssi: " + result.getRssi());
        }
    };
    public void startScanning(){
        btScanner = mBluetoothAdapter.getBluetoothLeScanner();
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                btScanner.startScan(filterList, new ScanSettings.Builder()
                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                        .setReportDelay(0) //when I use different value than 0, stop scanning
                        .build(),leScanCallback);
            }
        });
    }
    public void stopScanning() {
        btScanner = mBluetoothAdapter.getBluetoothLeScanner();
        AsyncTask.execute(new Runnable() {
            @Override
            public void run() {
                btScanner.stopScan(leScanCallback);
            }
        });
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        createNotificationChannel();
        Intent notificationIntent = new Intent(this, AlbumActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Foreground Service")
                .setContentText("Scanning")
                .setPriority(Notification.PRIORITY_MAX)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1, notification);
        startScanning();
        return START_STICKY;
    }
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel serviceChannel = new NotificationChannel(
                    CHANNEL_ID, "Foreground Service Channel", NotificationManager.IMPORTANCE_DEFAULT
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(serviceChannel);
        }
    }
}

针对性解决方案

结合Android不同版本的BLE扫描限制和你的代码情况,以下是可以解决扫描异常的具体措施:

1. 适配不同Android版本的扫描参数与模式

  • Android 9+的节流应对:Android 8.0起系统对BLE后台扫描有严格限制,即使前台服务也会被系统主动节流。你当前使用的SCAN_MODE_LOW_LATENCY虽然延迟低,但系统可能会限制扫描时长。可以尝试分场景切换扫描模式:亮屏时用LOW_LATENCY,息屏时切换为SCAN_MODE_BALANCED,同时避免长时间连续扫描,可采用“扫描10秒+暂停1秒”的循环(保证整体扫描占比足够高)。
  • Android 6的间隔启停问题:Android 6的扫描间隔性停止大概率是系统的扫描窗口限制导致的。建议先移除ScanFilter测试——系统层面的过滤可能会丢帧或触发扫描停顿,若移除后扫描恢复稳定,可以考虑在回调里自己做设备名称过滤,而不是依赖系统的ScanFilter
  • 关于setReportDelay(0):设置非0值导致扫描停止,可能是因为回调处理超时。确保onScanResult里的逻辑足够轻量(你当前只是打Log没问题,后续加业务逻辑时要注意异步处理)。

2. 处理电源优化与权限限制

  • Android 9+息屏停止扫描:Android 9要求后台扫描必须申请ACCESS_BACKGROUND_LOCATION权限,且需要用户手动授予。同时,必须在应用设置里关闭电池优化(把应用设为“不优化”),否则系统会在息屏后冻结扫描线程。
  • Android 6的权限与优化:虽然Android 6不需要后台位置权限,但BLE扫描依赖ACCESS_FINE_LOCATION权限,必须确保用户已经授予。同样要关闭应用的电池优化,避免系统休眠时暂停服务。

3. 提升前台服务的稳定性与优先级

  • 提高通知渠道优先级:你当前的通知渠道是IMPORTANCE_DEFAULT,可以改为IMPORTANCE_HIGH,这样系统会更重视这个前台服务,不容易被回收:
    NotificationChannel serviceChannel = new NotificationChannel(
            CHANNEL_ID, "Foreground Service Channel", NotificationManager.IMPORTANCE_HIGH
    );
    
  • 补充服务重启逻辑START_STICKY会在服务被杀死后重启,但重启过程中会有扫描中断。可以在onDestroy方法里添加重启逻辑,或者用WorkManager(兼容全版本)定时监控扫描状态,一旦发现长时间没有收到BLE数据,就重启扫描。

4. 实现扫描心跳与自动重启机制

系统可能会在后台偷偷停止扫描(即使前台服务存活),可以添加一个心跳检测:

  • Handler定时(比如每10秒)统计onScanResult的接收帧数,如果连续2个周期没有收到数据,就先调用stopScanning()再重新startScanning()
  • 注意:重启扫描时必须先停止再启动,避免重复注册ScanCallback导致异常。

5. 替换AsyncTask为更可靠的线程方式

AsyncTask已经被废弃,且线程管理存在不确定性。建议改用HandlerThread来执行扫描操作,确保线程稳定:

private HandlerThread scanThread;
private Handler scanHandler;

@Override
public void onCreate() {
    super.onCreate();
    scanThread = new HandlerThread("BLE_Scan_Thread");
    scanThread.start();
    scanHandler = new Handler(scanThread.getLooper());
}

public void startScanning(){
    btScanner = mBluetoothAdapter.getBluetoothLeScanner();
    scanHandler.post(new Runnable() {
        @Override
        public void run() {
            btScanner.startScan(filterList, new ScanSettings.Builder()
                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                    .setReportDelay(0)
                    .build(),leScanCallback);
        }
    });
}

@Override
public void onDestroy() {
    super.onDestroy();
    stopScanning();
    scanThread.quitSafely();
}

6. 完善权限声明与动态申请

确保Manifest里声明了所有必要的权限:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 9+后台扫描需要 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Android 12+需要的BLE权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

并且在代码中动态申请这些权限,特别是位置权限(包括后台权限)。

内容的提问来源于stack exchange,提问作者KyluAce

火山引擎 最新活动