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

如何防止关闭Android应用时Service/Activity被系统杀死

问题分析与解决方案

首先得明确一个核心事实:从Android 8.0(API 26)开始,系统对后台Service的运行做了严格限制——普通后台Service在应用进入后台后最多只能存活几分钟,之后就会被系统强制回收,这就是你日志里看到ActivityManager: Killing的根本原因,哪怕用了START_STICKY和BroadcastReceiver重启,也绕不开这个系统级限制。

你的代码里的关键问题

  1. Service启动方式不符合高版本规范:Android 8.0+不允许后台启动普通Service,必须使用前台Service或者兼容的后台任务调度方案。
  2. TimerTask可靠性极低:Timer依赖单一线程,一旦Service被系统回收,定时任务直接中断;而且在低内存场景下,Timer很容易被系统优先终止。
  3. 手动实例化Service是错误操作:你代码里mMonitoringService = new MonitoringService(getCtx());完全不符合Android组件规范——Service是系统托管的组件,必须由系统实例化,不能手动new。
  4. 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

火山引擎 最新活动