在 Android 开发中,后台服务被系统杀死是一个让开发者颇为头疼的问题。很多时候,我们希望应用的后台服务能够持续稳定地运行,比如音乐播放应用需要在后台持续播放音乐,即时通讯应用需要在后台保持消息的接收。接下来,咱们就详细聊聊解决 Android 后台服务被系统杀死的那些保活策略。

一、前台服务保活

1. 原理

前台服务是一种被认为是用户主动知晓且希望持续运行的服务,系统不会轻易将其杀死。它会在系统的通知栏显示一个通知,告知用户该服务正在运行。

2. 示例(Java 技术栈)

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;

public class MyForegroundService extends Service {

    private static final int FOREGROUND_SERVICE_ID = 1;
    private static final String CHANNEL_ID = "ForegroundServiceChannel";

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        createNotificationChannel();
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
               .setContentTitle("前台服务正在运行")
               .setContentText("这是一个前台服务示例")
               .setSmallIcon(R.drawable.ic_notification)
               .setContentIntent(pendingIntent)
               .build();

        startForeground(FOREGROUND_SERVICE_ID, notification);

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    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);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }
}

注释

  • FOREGROUND_SERVICE_ID:前台服务的唯一标识。
  • CHANNEL_ID:通知渠道的 ID,在 Android 8.0 及以上版本需要创建通知渠道。
  • onStartCommand 方法中,我们创建了一个通知,并通过 startForeground 方法将服务设置为前台服务。
  • createNotificationChannel 方法用于创建通知渠道,确保在高版本系统中通知能正常显示。

3. 应用场景

适用于那些需要持续运行,且需要让用户明确知晓的服务,如音乐播放、文件下载等。

4. 技术优缺点

优点:简单有效,系统不会轻易杀死前台服务。 缺点:会在通知栏显示通知,可能会影响用户体验。

5. 注意事项

  • 在 Android 8.0 及以上版本,必须创建通知渠道,否则通知无法显示。
  • 确保通知的内容准确、简洁,避免给用户造成困扰。

二、JobScheduler 保活

1. 原理

JobScheduler 是 Android 5.0(API 级别 21)引入的一个系统服务,用于在满足一定条件时执行任务。我们可以使用它来定期检查服务是否在运行,如果不在运行则重新启动。

2. 示例(Java 技术栈)

import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.RequiresApi;

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 检查服务是否在运行,如果不在运行则启动服务
        startService(new Intent(this, MyForegroundService.class));
        jobFinished(params, false);
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    public static void scheduleJob(Context context) {
        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        ComponentName componentName = new ComponentName(context, MyJobService.class);
        JobInfo jobInfo = new JobInfo.Builder(1, componentName)
               .setPeriodic(15 * 60 * 1000) // 每 15 分钟执行一次
               .setPersisted(true)
               .build();
        jobScheduler.schedule(jobInfo);
    }
}

注释

  • onStartJob 方法中,我们检查服务是否在运行,如果不在运行则启动服务。
  • scheduleJob 方法用于设置 JobScheduler 的执行周期,这里设置为每 15 分钟执行一次。

3. 应用场景

适用于那些需要定期执行任务,或者在特定条件下执行任务的场景,如数据同步、定时清理等。

4. 技术优缺点

优点:可以根据条件灵活调度任务,节省电量。 缺点:执行周期有一定限制,不能设置过短。

5. 注意事项

  • 执行周期不能设置过短,Android 系统有最小执行周期限制,以避免过度消耗电量。
  • 在 Android 7.0 及以上版本,JobScheduler 的执行可能会受到系统休眠模式的影响。

三、广播接收器保活

1. 原理

通过注册广播接收器,监听系统发出的各种广播事件,如屏幕点亮、网络连接变化等。当接收到这些广播时,检查服务是否在运行,如果不在运行则重新启动。

2. 示例(Java 技术栈)

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_SCREEN_ON.equals(action) || Intent.ACTION_SCREEN_OFF.equals(action)) {
            // 检查服务是否在运行,如果不在运行则启动服务
            startService(new Intent(context, MyForegroundService.class));
        }
    }
}

注释

  • onReceive 方法中,我们监听了屏幕点亮和熄灭的广播事件,当接收到这些广播时,启动服务。

3. 应用场景

适用于那些需要在特定系统事件发生时执行任务的场景,如在屏幕点亮时更新 UI、在网络连接变化时同步数据等。

4. 技术优缺点

优点:可以及时响应系统事件,保证服务的持续运行。 缺点:如果广播接收器过多,会增加系统的负载。

5. 注意事项

  • 动态注册的广播接收器在 Activity 销毁时需要注销,避免内存泄漏。
  • 避免在广播接收器中执行耗时操作,否则可能会导致 ANR(Application Not Responding)错误。

四、双进程守护保活

1. 原理

创建两个相互守护的服务进程,当一个进程被杀死时,另一个进程负责将其重新启动。

2. 示例(Java 技术栈)

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;

import com.example.myservice.IMyAidlInterface;

public class Process1Service extends Service {

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                IMyAidlInterface binder = IMyAidlInterface.Stub.asInterface(service);
                binder.ping();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 当另一个进程的服务断开连接时,重新启动该服务
            startService(new Intent(Process1Service.this, Process2Service.class));
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        bindService(new Intent(this, Process2Service.class), connection, BIND_AUTO_CREATE);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

public class Process2Service extends Service {

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                IMyAidlInterface binder = IMyAidlInterface.Stub.asInterface(service);
                binder.ping();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 当另一个进程的服务断开连接时,重新启动该服务
            startService(new Intent(Process2Service.this, Process1Service.class));
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        bindService(new Intent(this, Process1Service.class), connection, BIND_AUTO_CREATE);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

注释

  • Process1ServiceProcess2Service 相互绑定,当一个服务断开连接时,另一个服务会重新启动它。
  • IMyAidlInterface 是一个 AIDL 接口,用于进程间通信。

3. 应用场景

适用于对服务的稳定性要求较高的场景,如即时通讯、安全防护等。

4. 技术优缺点

优点:能在一定程度上保证服务的持续运行,提高服务的稳定性。 缺点:实现复杂,会增加系统的资源消耗。

5. 注意事项

  • 确保两个服务运行在不同的进程中,否则无法实现双进程守护。
  • 在 Android 8.0 及以上版本,启动服务的方式有所改变,需要使用 startForegroundService 方法。

文章总结

解决 Android 后台服务被系统杀死的问题,需要根据具体的应用场景选择合适的保活策略。前台服务保活简单有效,但会影响用户体验;JobScheduler 适用于定期执行任务,但有执行周期限制;广播接收器可以及时响应系统事件,但可能会增加系统负载;双进程守护能提高服务的稳定性,但实现复杂。在实际开发中,我们可以结合多种保活策略,以达到最佳的保活效果。同时,也要注意避免过度保活,以免影响系统性能和用户体验。