应用被强制关闭后Broadcast Receiver无法调用Service问题
兄弟,我看你在小米Redmi Note4上遇到了强制关闭应用后Service没法重启的问题,这事儿我太熟了——一方面是MIUI的后台管控太严,另一方面是你当前的代码有几个关键问题没处理到,咱们一步步来解决:
先把根儿上的问题捋清楚:
- 你用的是动态注册BroadcastReceiver,这种Receiver完全依赖应用进程存活,一旦应用被强制杀死,动态注册的Receiver就跟着失效了,根本接收不到广播,自然没法重启Service。
- 小米MIUI有自己的后台管控机制,默认会限制非白名单应用的后台行为,哪怕代码写对了,没开权限也白搭。
- Android 8.0(API 26)之后,系统对后台启动Service做了严格限制,直接用
startService()会抛出异常,必须用前台服务的方式。 - 当应用被强制关闭时,
Service.onDestroy()大概率不会被调用,你在里面发的自定义广播根本发不出去,这个重启逻辑走不通。
咱们先改代码,再配合系统设置,双管齐下:
1. 把BroadcastReceiver改成静态注册
动态注册靠不住,必须在AndroidManifest.xml里静态注册Receiver,这样即使应用进程被杀死,系统也能唤醒它(前提是权限够)。
修改AndroidManifest.xml,添加静态注册的Receiver和必要权限:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.turzo.servicetest"> <!-- 前台服务权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- 开机自启权限(可选,需要开机后自动重启Service就加) --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- 网络状态权限(如果需要监听网络变化) --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 注册Service --> <service android:name=".SensorService" android:enabled="true" android:exported="false" /> <!-- 静态注册重启Receiver --> <receiver android:name=".SensorRestarterBroadcastReceiver" android:enabled="true" android:exported="true"> <!-- 监听自定义重启广播 --> <intent-filter> <action android:name="com.turzo.servicetest.RestartSensor" /> <!-- 可选:监听开机完成事件 --> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> </manifest>
2. 修改BroadcastReceiver适配Android O+
在Receiver里,Android O及以上版本不能直接调用startService(),必须用startForegroundService(),同时要处理版本兼容:
package com.turzo.servicetest; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Build; import android.util.Log; public class SensorRestarterBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "RestartReceiver"; @Override public void onReceive(Context context, Intent intent) { Log.i(TAG, "收到重启Service的广播"); Intent serviceIntent = new Intent(context, SensorService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android O及以上,启动前台服务 context.startForegroundService(serviceIntent); } else { // 低版本直接启动 context.startService(serviceIntent); } } }
3. 把Service改成前台服务
为了避免被系统(尤其是MIUI)轻易杀死,把Service改成前台服务,在onStartCommand()里弹出一个低优先级的前台通知(尽量不打扰用户):
package com.turzo.servicetest; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; import android.content.Intent; import android.os.Build; import android.os.IBinder; import android.util.Log; import androidx.annotation.Nullable; import java.util.Timer; import java.util.TimerTask; public class SensorService extends Service { private static final String TAG = "ServiceTest"; private static final int NOTIFICATION_ID = 1001; private static final String CHANNEL_ID = "SensorServiceChannel"; private Timer timer; private TimerTask timerTask; private int counter = 0; // 注意:Service只能有无参构造,系统创建Service时会调用这个,自定义带参构造会导致异常! public SensorService() { super(); Log.i(TAG, "Service初始化"); } @Override public void onCreate() { super.onCreate(); // 创建通知渠道(Android O+必需) createNotificationChannel(); // 启动前台服务 startForeground(NOTIFICATION_ID, getNotification()); } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); Log.i(TAG, "Service启动"); startTimer(); // 返回START_STICKY:如果Service被系统杀死,系统会尝试重启它(MIUI可能不严格遵守) return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "Service销毁"); stoptimertask(); // 发送自定义广播,尝试重启自己 Intent broadcastIntent = new Intent("com.turzo.servicetest.RestartSensor"); sendBroadcast(broadcastIntent); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } // 创建通知渠道 private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "传感器服务", NotificationManager.IMPORTANCE_LOW // 低优先级,不弹出通知 ); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) { manager.createNotificationChannel(channel); } } } // 创建前台通知 private Notification getNotification() { Notification.Builder builder; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder = new Notification.Builder(this, CHANNEL_ID); } else { builder = new Notification.Builder(this); } return builder.setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("传感器服务运行中") .setContentText("正在后台收集数据") .setPriority(Notification.PRIORITY_LOW) .build(); } // 定时器相关代码 private void startTimer() { timer = new Timer(); initializeTimerTask(); timer.schedule(timerTask, 1000, 1000); } private void initializeTimerTask() { timerTask = new TimerTask() { @Override public void run() { Log.i(TAG, "定时器运行中:" + (counter++)); } }; } private void stoptimertask() { if (timer != null) { timer.cancel(); timer = null; } } }
4. 简化MainActivity,移除动态注册的Receiver
因为已经静态注册了Receiver,MainActivity里的动态注册可以去掉,避免重复注册:
package com.turzo.servicetest; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "ServiceTest"; private Intent mServiceIntent; private Context ctx; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ctx = this; setContentView(R.layout.activity_main); mServiceIntent = new Intent(ctx, SensorService.class); if (!isMyServiceRunning(SensorService.class)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(mServiceIntent); } else { startService(mServiceIntent); } } } private boolean isMyServiceRunning(Class<?> serviceClass) { ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (serviceClass.getName().equals(service.service.getClassName())) { Log.i(TAG, "Service正在运行"); return true; } } Log.i(TAG, "Service未运行"); return false; } @Override protected void onDestroy() { // 这里不要调用stopService,否则用户退出Activity就会停掉Service(如果你希望Service一直运行的话) // stopService(mServiceIntent); Log.i(TAG, "MainActivity销毁"); super.onDestroy(); } }
5. 小米设备必须手动开启的权限
这一步是重中之重!MIUI默认会限制应用的后台行为,你必须手动开启:
- 打开安全中心 → 应用管理 → 找到你的应用「ServiceTest」
- 进入权限管理 → 开启「自启动」权限
- 进入后台设置 → 选择「无限制」,禁止系统自动清理该应用的后台进程
- (可选)如果还是不行,把应用加入省电优化的白名单,避免被省电策略杀死
即使做了以上所有操作,MIUI的某些版本可能还是会在内存紧张时杀死后台服务,这是系统层面的限制,没法完全绕过,但前台服务+自启动权限已经是能做到的最优方案了。另外,不要过度依赖Service.onDestroy()发送广播,因为应用被强制关闭时这个方法可能不会执行,更好的方式是用系统广播(比如开机完成)或者WorkManager做周期性检查(Android Jetpack组件,更适合后台任务)。
内容的提问来源于stack exchange,提问作者XpressGeek

