Android 7.0下BLE后台服务仅运行一次问题排查求助
我来帮你拆解这个问题,结合你的代码和Android 7.0(API 24)的系统特性,核心问题大概率出在这几个方面:
1. 服务启动模式的返回值不对
你在onStartCommand里直接返回了super.onStartCommand(...),默认返回值不会让系统在服务被回收后自动重启它。调试模式下系统对应用的限制更少,所以能维持3小时,但打包后系统一旦回收服务,就不会再重启,导致只运行一次。
修复建议:
把返回值改成START_STICKY或者START_REDELIVER_INTENT,根据你的需求选:
// 系统回收后内存充足时自动重启服务,适合周期性任务 return START_STICKY; // 或者需要恢复最后一次任务时用这个 // return START_REDELIVER_INTENT;
2. Android 7.0的Doze模式限制了定时任务
Android 7.0引入的Doze模式会暂停后台应用的Timer、Handler这类定时任务来省电,而调试模式下系统会豁免你的应用,所以调试时能正常运行。打包后应用进入后台,定时任务被系统暂停,服务就停了。
修复建议:
替换Timer为符合省电机制的AlarmManager或者JobScheduler,比如用AlarmManager实现周期性任务:
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, BleScanReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); long interval = 1000 * nSecondi; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 即使在Doze模式下也能触发的精确定时 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + interval, pendingIntent); } else { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, interval, pendingIntent); }
然后写一个广播接收器BleScanReceiver,在onReceive里触发BLE扫描和读取逻辑。
3. 代码混淆导致BLE回调失效
打包APK时如果开启了混淆,你的GattClientCallback可能被混淆重命名,系统无法正确调用回调方法,导致读取流程中断,服务看起来像是停止运行了。
修复建议:
在proguard-rules.pro里添加BLE相关类的混淆豁免:
// 保留你的Gatt回调类和方法 -keep class com.yourpackage.BlePowerService$GattClientCallback { *; } // 保留系统BLE回调类 -keep class android.bluetooth.BluetoothGattCallback { *; } -keepattributes *Annotation*,EnclosingMethod,Signature
记得把com.yourpackage换成你实际的包名。
4. 权限检查缺失
调试时Android Studio可能自动给了位置权限,但打包安装后用户可能没授予ACCESS_COARSE_LOCATION(Android 7.0扫描BLE必须的权限),导致扫描失败,服务找不到设备就停了。
修复建议:
在onStartCommand开头加权限检查:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION); return START_STICKY; }
同时确保AndroidManifest.xml里声明了必要权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
5. BLE连接和读取逻辑的阻塞问题
你代码里while(isReading)用Thread.sleep(1)轮询的方式会阻塞线程,很容易触发ANR,导致系统直接杀死服务。而且mGatt实例没有正确管理,连接丢失后没关闭旧实例,可能导致资源泄漏,后续无法重新连接。
修复建议:
- 用回调驱动读取逻辑,在
GattClientCallback的onCharacteristicRead回调里触发下一个特征的读取,避免阻塞线程:
// 启动读取队列 private void startReadingQueue() { if (ReadQueue != null && !ReadQueue.isEmpty()) { readNextChar(0); } } private void readNextChar(int index) { if (index >= ReadQueue.size()) { // 读取完成,标记结束 continuaLetturaForza = false; continuaLetturaTemperatura = false; return; } BluetoothGattCharacteristic chara = ReadQueue.get(index); if (mGatt != null && mGatt.readCharacteristic(chara)) { // 等待onCharacteristicRead回调再读下一个 } else { readNextChar(index + 1); } } // 在GattClientCallback里添加: @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); // 处理读取到的数据... // 读下一个 readNextChar(ReadQueue.indexOf(characteristic) + 1); }
- 断开连接时记得调用
mGatt.close()释放资源,并把mGatt置为null。
额外优化:改用前台服务
Android 7.0及以上后台服务更容易被回收,把服务设为前台服务能大幅降低被杀死的概率:
// 在onCreate或onStartCommand里启动前台服务 Notification notification = new NotificationCompat.Builder(this, "BLE_CHANNEL_ID") .setContentTitle("BLE数据采集") .setContentText("正在后台同步设备数据") .setSmallIcon(R.drawable.ic_ble_notification) .build(); startForeground(1, notification);
Android 9.0及以上需要在Manifest里加前台服务权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
内容的提问来源于stack exchange,提问作者bircastri

