【软件干货】Android应用进程如何保活?

摘要:使用前台服务(Foreground Service):将服务调用startForeground方法,并传入一个通知对象,将该服务置于前台运行状态。这样可以使得该服务的优先级更高,从而减少被系统杀死的概率。

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需要以下几个步骤:

1、获取 JobScheduler 对象:通过Binder机制获取该JobScheduler系统服务;

// 创建 JobSchedulerJobScheduler 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

4、开启定时任务;

// 开启定时任务jobScheduler.schedule(jobInfoBuilder.build);

5、7.0 以上的特殊处理,由于在7.0 以上的系统中设置了延迟执行,需要在JobService 的 onStartJob 方法中再次开启一次 JobScheduler 任务执行,也就是重复上述1 ~ 4执行, 这样就实现了周期性执行的目的;

public class KeepAliveJobService extends JobService {@Overridepublic 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);}@Overridepublic Result doWork {// Do the work herexxxxx;// Indicate whether the work finished successfully with the Resultreturn 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 statementsinterface 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 个正在运行的 ServiceList 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 {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 服务绑定成功时回调}@Overridepublic 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 {@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString) throws RemoteException {// 通信内容}}@Overridepublic IBinder onBind(Intent intent) {return myBinder;}@Overridepublic 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

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 {@Overridepublic void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString) throws RemoteException {// 通信内容}}@Overridepublic IBinder onBind(Intent intent) {return myBinder;}@Overridepublic 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

6、启动两个服务:

import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;public class MainActivity extends AppCompatActivity {@Overrideprotected 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 {@Overridepublic 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;}@Overridepublic boolean onStopJob(JobParameters params) {Log.i("KeepAliveJobService", "JobService onStopJob 关闭");return false;}public static void startJob(Context context){// 创建 JobSchedulerJobScheduler 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

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

来源:大醉狂侠7

相关推荐