1.Android 应用进程保活方法介绍
在Android应用程序中,为了保证应用的正常运行和稳定性,有时需要对应用进程进行保活。以下是一些实现进程保活的方法:
1、使用前台服务(Foreground Service):将服务调用startForeground()方法,并传入一个通知对象,将该服务置于前台运行状态。这样可以使得该服务的优先级更高,从而减少被系统杀死的概率。
2、使用JobScheduler:使用setPeriodic()方法可以让应用程序周期性地执行任务,从而避免长时间占用CPU资源,setPersisted(true)方法则表示当设备重启后,该任务仍然需要继续执行。
3、使用AlarmManager:使用这个API可以让应用程序在指定的时间间隔内执行任务。例如,可以设置一个闹钟,每隔一段时间唤醒应用程序并执行一些操作。
4、使用守护进程:启动一个后台守护进程,监控应用程序的状态并在应用程序被杀死时重新启动它,使用守护进程需要申请额外的权限。
5、使用双进程保活:启动两个相互绑定的进程,在其中一个进程被杀死时,另一个进程可以重新启动它。
6、使用WorkManger: 这是目前比较新的保活机制,用于取代JobScheduler。
需要注意的是,为了避免滥用和浪费系统资源,Android系统不断升级后,已经严格限制应用程序使用过多的后台资源和保活机制。
JobScheduler 是系统服务,由系统负责调度第三方应用注册的JobScheduler ,定时完成指定任务。
在应用中创建一个 JobService 服务,JobService 需要 API Level 21以上才可以使用,该服务注册时必须声明 android.permission.BIND_JOB_SERVICE 权限:
<!-- JobScheduler 拉活 -->
<service
android:name=".jobscheduler.KeepAliveJobService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE"></service>
通常使用JobScheduler需要以下几个步骤:
1、获取 JobScheduler 对象:通过Binder机制获取该JobScheduler系统服务;
// 创建 JobScheduler
JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
2、指定 JobScheduler 任务信息 JobInfo:绑定任务 ID,指定任务的运行组件,也就是之前创建并注册的 JobService, 最后要设置该任务在重启后也要执行;
// 第一个参数指定任务 ID
// 第二个参数指定任务在哪个组件中执行
// setPersisted 方法需要 android.permission.RECEIVE_BOOT_COMPLETED 权限
// setPersisted 方法作用是设备重启后 , 依然执行 JobScheduler 定时任务
JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(10,
new ComponentName(context.getPackageName(), KeepAliveJobService.class.getName()))
.setPersisted(true);
3、设置时间信息:7.0 以下的系统可以设置间隔, 7.0 以上的版本需要设置延迟执行,否则无法启动;
// 7.0 以下的版本, 可以每隔 5000 毫秒执行一次任务
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
jobInfoBuilder.setPeriodic(5_000);
}else{
// 7.0 以上的版本 , 设置延迟 5 秒执行
// 该时间不能小于 JobInfo.getMinLatencyMillis 方法获取的最小值
jobInfoBuilder.setMinimumLatency(5_000);
}
4、开启定时任务;
// 开启定时任务
jobScheduler.schedule(jobInfoBuilder.build());
5、7.0 以上的特殊处理,由于在7.0 以上的系统中设置了延迟执行,需要在JobService 的 onStartJob 方法中再次开启一次 JobScheduler 任务执行,也就是重复上述1 ~ 4执行, 这样就实现了周期性执行的目的;
public class KeepAliveJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
Log.i("KeepAliveJobService", "JobService onStartJob 开启");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
// 如果当前设备大于 7.0 , 延迟 5 秒 , 再次执行一次
startJob(this);
}
return false;
}
}
WorkManager是适合用于持久性工作的推荐解决方案,它可处理三种类型的持久性工作:
1、立即执行:必须立即开始且很快就完成的任务,可以加急。
2、长时间运行:运行时间可能较长(有可能超过 10 分钟)的任务。
3、可延期执行:延期开始并且可以定期运行的预定任务。
通常使用WorkManager需要以下几个步骤:
1、将依赖项添加到应用的build.gradle文件中;
2、定义工作:工作使用 Worker 类定义,doWork() 方法在 WorkManager 提供的后台线程上异步运行。如需为 WorkManager 创建一些要运行的工作,则需扩展 Worker 类并替换 doWork() 方法;
public class XxxWorker extends Worker {
publicXxxWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
// Do the work here
xxxxx();
// Indicate whether the work finished successfully with the Result
return Result.success();
}
}
3、创建 WorkRequest:定义工作后,必须使用WorkManager 服务进行调度该工作才能运行;
WorkRequest xxxWorkRequest =
new OneTimeWorkRequest.Builder(XxxWorker.class)
.build();
4.将 WorkRequest 提交给系统:需要使用enqueue()方法将WorkRequest提交到WorkManager;
WorkManager
.getInstance(myContext)
.enqueue(uploadWorkRequest);
在定义工作时要考虑要考虑下面常见的需求:
1、调度一次性工作还是重复性工作;
2、工作约束条件是怎样的,例如要求连接到 Wi-Fi 网络或正在充电;
3、确保至少延迟一定时间再执行工作;
4、设置重试和退避策略;
5、输入数据如何传递给工作等等。
双进程保活的方式就是在运行了一个主进程之外,还运行了一个 “本地前台进程”,并绑定“远程前台进程”, “远程前台进程”与“本地前台进程”实现了相同的功能,代码基本一致,这两个进程都是前台进程,都进行了提权,并且互相绑定,当监听到绑定的另外一个进程突然断开连接,则本进程再次开启前台进程提权,并且重新绑定对方进程,以达到拉活对方进程的目的。
双进程保活的实现步骤如下:
1、定义 AIDL 接口 IMyAidlInterface,每个服务中都需要定义继承 IMyAidlInterface.Stub 的 Binder 类,作为进程间通信的桥梁( 这是个默认的 AIDL 接口 ),监听进程的连接断开;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
2、实现一个判定服务运行工具:
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.text.TextUtils;
import org.w3c.dom.Text;
import java.util.List;
public class ServiceUtils {
/**
* 判定 Service 是否在运行
* @param context
* @return
*/
public static boolean isServiceRunning(Context context, String serviceName){
if(TextUtils.isEmpty(serviceName)) return false;
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// 最多获取 200 个正在运行的 Service
List<ActivityManager.RunningServiceInfo> infos =
activityManager.getRunningServices(200);
// 遍历当前运行的 Service 信息, 如果找到相同名称的服务 , 说明某进程正在运行
for (ActivityManager.RunningServiceInfo info: infos){
if (TextUtils.equals(info.service.getClassName(), serviceName)){
return true;
}
}
return false;
}
}
3、定义一个用于本地与远程连接的类:
class Connection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 服务绑定成功时回调
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 再次启动前台进程
startService();
// 绑定另外一个远程进程
bindService();
}
}
4、定义一个本地前台服务类:
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.core.app.NotificationCompat;
import static androidx.core.app.NotificationCompat.PRIORITY_MIN;
/**
* 本地前台服务
*/
public class LocalForegroundService extends Service {
/**
* 远程调用 Binder 对象
*/
private MyBinder myBinder;
/**
* 连接对象
*/
private Connection connection;
/**
* AIDL 远程调用接口
* 其它进程调与该 RemoteForegroundService 服务进程通信时 , 可以通过 onBind 方法获取该 myBinder 成员
* 通过调用该成员的 basicTypes 方法 , 可以与该进程进行数据传递
*/
class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(
int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {
// 通信内容
}
}
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
// 创建 Binder 对象
myBinder = new MyBinder();
// 启动前台进程
startService();
}
private void startService(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
// startForeground();
// 创建通知通道
NotificationChannel channel = new NotificationChannel("service",
"service", NotificationManager.IMPORTANCE_NONE);
channel.setLightColor(Color.BLUE);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 正式创建
service.createNotificationChannel(channel);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "service");
Notification notification = builder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
// 开启前台进程 , API 26 以上无法关闭通知栏
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
startForeground(10, new Notification());
// API 18 ~ 25 以上的设备 , 启动相同 id 的前台服务 , 并关闭 , 可以关闭通知
startService(new Intent(this, CancelNotificationService.class));
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){
// 将该服务转为前台服务
// 需要设置 ID 和 通知
// 设置 ID 为 0 , 就不显示已通知了 , 但是 oom_adj 值会变成后台进程 11
// 设置 ID 为 1 , 会在通知栏显示该前台服务
// 8.0 以上该用法报错
startForeground(10, new Notification());
}
}
/**
* 绑定 另外一个 服务
* LocalForegroundService 与 RemoteForegroundService 两个服务互相绑定
*/
private void bindService(){
// 绑定另外一个 服务
// LocalForegroundService 与 RemoteForegroundService 两个服务互相绑定
// 创建连接对象
connection = new Connection();
// 创建本地前台进程组件意图
Intent bindIntent = new Intent(this, RemoteForegroundService.class);
// 绑定进程操作
bindService(bindIntent, connection, BIND_AUTO_CREATE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 绑定另外一个服务
bindService();
return super.onStartCommand(intent, flags, startId);
}
}
5、定义一个远程前台服务类:
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.core.app.NotificationCompat;
import static androidx.core.app.NotificationCompat.PRIORITY_MIN;
/**
* 远程前台服务
*/
public class RemoteForegroundService extends Service {
/**
* 远程调用 Binder 对象
*/
private MyBinder myBinder;
/**
* 连接对象
*/
private Connection connection;
/**
* AIDL 远程调用接口
* 其它进程调与该 RemoteForegroundService 服务进程通信时 , 可以通过 onBind 方法获取该 myBinder 成员
* 通过调用该成员的 basicTypes 方法 , 可以与该进程进行数据传递
*/
class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(
int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {
// 通信内容
}
}
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
// 创建 Binder 对象
myBinder = new MyBinder();
// 启动前台进程
startService();
}
private void startService(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
// startForeground();
// 创建通知通道
NotificationChannel channel = new NotificationChannel("service",
"service", NotificationManager.IMPORTANCE_NONE);
channel.setLightColor(Color.BLUE);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// 正式创建
service.createNotificationChannel(channel);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "service");
Notification notification = builder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(PRIORITY_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
// 开启前台进程 , API 26 以上无法关闭通知栏
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
startForeground(10, new Notification());
// API 18 ~ 25 以上的设备 , 启动相同 id 的前台服务 , 并关闭 , 可以关闭通知
startService(new Intent(this, CancelNotificationService.class));
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){
// 将该服务转为前台服务
// 需要设置 ID 和 通知
// 设置 ID 为 0 , 就不显示已通知了 , 但是 oom_adj 值会变成后台进程 11
// 设置 ID 为 1 , 会在通知栏显示该前台服务
// 8.0 以上该用法报错
startForeground(10, new Notification());
}
}
/**
* 绑定 另外一个 服务
* LocalForegroundService 与 RemoteForegroundService 两个服务互相绑定
*/
private void bindService(){
// 绑定 另外一个 服务
// LocalForegroundService 与 RemoteForegroundService 两个服务互相绑定
// 创建连接对象
connection = new Connection();
// 创建本地前台进程组件意图
Intent bindIntent = new Intent(this, LocalForegroundService.class);
// 绑定进程操作
bindService(bindIntent, connection, BIND_AUTO_CREATE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 绑定另外一个服务
bindService();
return super.onStartCommand(intent, flags, startId);
}
}
6、启动两个服务:
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this, LocalForegroundService.class));
startService(new Intent(this, RemoteForegroundService.class));
}
}
这种方案是在 JobService的onStartJob 方法中判定“双进程保活”中的双进程是否挂了 ,如果这两个进程挂了,就重新将挂掉的进程重启。
这里给出一个双进程保活+JobScheduler整合方案中JobScheduler部分的示意代码,而双进程保活部分保持不变。
public class KeepAliveJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
Log.i("KeepAliveJobService", "JobService onStartJob 开启");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
// 如果当前设备大于 7.0 , 延迟 5 秒 , 再次执行一次
startJob(this);
}
// 判定本地前台进程是否正在运行
boolean isLocalServiceRunning =
ServiceUtils.isServiceRunning(this, LocalForegroundService.class.getName());
if (!isLocalServiceRunning){
startService(new Intent(this, LocalForegroundService.class));
}
// 判定远程前台进程是否正在运行
boolean isRemoteServiceRunning =
ServiceUtils.isServiceRunning(this, RemoteForegroundService.class.getName());
if (!isRemoteServiceRunning){
startService(new Intent(this, RemoteForegroundService.class));
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.i("KeepAliveJobService", "JobService onStopJob 关闭");
return false;
}
public static void startJob(Context context){
// 创建 JobScheduler
JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
// 第一个参数指定任务 ID
// 第二个参数指定任务在哪个组件中执行
// setPersisted 方法需要 android.permission.RECEIVE_BOOT_COMPLETED 权限
// setPersisted 方法作用是设备重启后 , 依然执行 JobScheduler 定时任务
JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(10,
new ComponentName(context.getPackageName(), KeepAliveJobService.class.getName()))
.setPersisted(true);
// 7.0 以下的版本, 可以每隔 5000 毫秒执行一次任务
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
jobInfoBuilder.setPeriodic(5_000);
}else{
// 7.0 以上的版本 , 设置延迟 5 秒执行
// 该时间不能小于 JobInfo.getMinLatencyMillis 方法获取的最小值
jobInfoBuilder.setMinimumLatency(5_000);
}
// 开启定时任务
jobScheduler.schedule(jobInfoBuilder.build());
}
}
1、【Android 进程保活】应用进程拉活 ( 双进程守护 + JobScheduler 保活):https://hanshuliang.blog.csdn.net/article/details/115607584
2、【Android 进程保活】应用进程拉活 ( 双进程守护保活 ):https://hanshuliang.blog.csdn.net/article/details/115604667
3、【Android 进程保活】应用进程拉活 ( JobScheduler 拉活):https://hanshuliang.blog.csdn.net/article/details/115584240
4、Android实现进程保活的思路:https://blog.csdn.net/gs12software/article/details/130502312
5、WorkManager 使用入门:https://developer.android.google.cn/develop/background-work/background-tasks/persistent/getting-started