如何防止关闭Android应用时Service/Activity被系统杀死
问题分析与解决方案
首先得明确一个核心事实:从Android 8.0(API 26)开始,系统对后台Service的运行做了严格限制——普通后台Service在应用进入后台后最多只能存活几分钟,之后就会被系统强制回收,这就是你日志里看到ActivityManager: Killing的根本原因,哪怕用了START_STICKY和BroadcastReceiver重启,也绕不开这个系统级限制。
你的代码里的关键问题
- Service启动方式不符合高版本规范:Android 8.0+不允许后台启动普通Service,必须使用前台Service或者兼容的后台任务调度方案。
- TimerTask可靠性极低:Timer依赖单一线程,一旦Service被系统回收,定时任务直接中断;而且在低内存场景下,Timer很容易被系统优先终止。
- 手动实例化Service是错误操作:你代码里
mMonitoringService = new MonitoringService(getCtx());完全不符合Android组件规范——Service是系统托管的组件,必须由系统实例化,不能手动new。 - BroadcastReceiver重启逻辑在高版本失效:Android 8.0+对隐式广播和后台启动Service都有限制,导致你的广播重启逻辑无法长期生效。
具体解决方案
1. 将Service改为前台Service
前台Service会显示一个持续的通知,系统会判定它是用户主动关注的服务,不会轻易回收。修改你的MonitoringService代码:
public class MonitoringService extends Service { private static final int NOTIFICATION_ID = 1001; private long lastCheck = System.currentTimeMillis() - 60000; // 注意:Service必须保留无参构造函数,不能自定义带参数的构造! public MonitoringService() { super(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); // 创建前台通知,必须调用startForeground才能让Service进入前台状态 createForegroundNotification(); // 后续替换TimerTask为更可靠的方案,先保留你的逻辑过渡 startMyTimer(this); return START_STICKY; } private void createForegroundNotification() { // Android 8.0+需要先创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "monitoring_channel", "更新监控", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.createNotificationChannel(channel); } Notification notification = new NotificationCompat.Builder(this, "monitoring_channel") .setContentTitle("更新监控中") .setContentText("正在后台检查更新") .setSmallIcon(R.drawable.ic_notification) // 替换成你的应用通知图标 .build(); // 启动前台服务 startForeground(NOTIFICATION_ID, notification); } // ... 你的startMyTimer、callPHPfunction等方法保持不变,仅移除自定义构造函数 }
2. 修正Service启动逻辑
按钮点击监听器里手动实例化Service的操作必须删掉,直接用Intent由系统启动:
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (btn.getText().equals("Start Monitoring")) { Intent serviceIntent = new Intent(getCtx(), MonitoringService.class); if (!isMyServiceRunning(MonitoringService.class)) { // Android 8.0+必须用startForegroundService启动前台服务 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } } btn.setText("Stop Monitoring"); } else { Intent serviceIntent = new Intent(getCtx(), MonitoringService.class); stopService(serviceIntent); btn.setText("Start Monitoring"); } } }); // 检查Service是否运行的方法保留,但不要用手动实例化的对象 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())) { return true; } } return false; }
3. 替换TimerTask为可靠的定时方案
TimerTask在后台场景下稳定性极差,建议用AlarmManager(适合精确定时)或WorkManager(适合跨版本兼容的后台任务):
用AlarmManager实现每60秒检查一次
// 在Service的onStartCommand里初始化AlarmManager private void setupAlarmManager() { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(this, UpdateCheckReceiver.class); // 注意FLAG_IMMUTABLE适配Android 12+ PendingIntent pendingIntent = PendingIntent.getBroadcast( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); long triggerAtMillis = System.currentTimeMillis() + 60000; // 高版本用setExactAndAllowWhileIdle保证闹钟能触发 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } else { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, 60000, pendingIntent); } } // 新建BroadcastReceiver处理闹钟触发 public class UpdateCheckReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (isNetworkAvailable(context)) { callPHPfunction(context); // 重新设置下一次闹钟 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent nextIntent = new Intent(context, UpdateCheckReceiver.class); PendingIntent nextPendingIntent = PendingIntent.getBroadcast( context, 0, nextIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); long nextTrigger = System.currentTimeMillis() + 60000; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextTrigger, nextPendingIntent); } else { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, nextTrigger, 60000, nextPendingIntent); } } } }
4. 处理厂商电池优化
很多国产厂商(小米、华为、OPPO等)有独立的后台管控机制,哪怕用了前台Service,也可能被电池优化杀掉。你需要:
- 在应用内引导用户关闭该应用的电池优化:
Intent intent = new Intent(); String packageName = getPackageName(); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (!pm.isIgnoringBatteryOptimizations(packageName)) { intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + packageName)); startActivity(intent); }
- 引导用户将应用加入厂商的「后台白名单」或「自启动管理」列表(不同厂商路径不同,需针对性提示)。
5. 修正onTaskRemoved的重启逻辑
直接在onTaskRemoved里启动前台Service,无需通过BroadcastReceiver中转:
@Override public void onTaskRemoved(Intent rootIntent) { super.onTaskRemoved(rootIntent); Log.i("EXIT", "onTaskRemoved!"); Intent restartIntent = new Intent(this, MonitoringService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(restartIntent); } else { startService(restartIntent); } stopMyTimertask(); }
总结
Android的后台管控越来越严格,普通后台Service已经无法实现长期运行的需求,必须结合前台Service+可靠定时方案,同时处理厂商的特殊限制,才能让你的更新监控服务稳定运行。
内容的提问来源于stack exchange,提问作者The_Rkp




