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

发表时间:2024-10-15 11:40

1.Android 应用进程保活方法介绍

在Android应用程序中,为了保证应用的正常运行和稳定性,有时需要对应用进程进行保活。以下是一些实现进程保活的方法:

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

2、使用JobScheduler:使用setPeriodic()方法可以让应用程序周期性地执行任务,从而避免长时间占用CPU资源,setPersisted(true)方法则表示当设备重启后,该任务仍然需要继续执行。

3、使用AlarmManager:使用这个API可以让应用程序在指定的时间间隔内执行任务。例如,可以设置一个闹钟,每隔一段时间唤醒应用程序并执行一些操作。

4、使用守护进程:启动一个后台守护进程,监控应用程序的状态并在应用程序被杀死时重新启动它,使用守护进程需要申请额外的权限。

5、使用双进程保活:启动两个相互绑定的进程,在其中一个进程被杀死时,另一个进程可以重新启动它。

6、使用WorkManger: 这是目前比较新的保活机制,用于取代JobScheduler。


需要注意的是,为了避免滥用和浪费系统资源,Android系统不断升级后,已经严格限制应用程序使用过多的后台资源和保活机制。


2.JobScheduler 用法简介

JobScheduler 是系统服务,由系统负责调度第三方应用注册的JobScheduler ,定时完成指定任务。

在应用中创建一个 JobService 服务,JobService 需要 API Level 21以上才可以使用,该服务注册时必须声明 android.permission.BIND_JOB_SERVICE 权限:


<!-- JobScheduler 拉活 --><serviceandroid:name=".jobscheduler.KeepAliveJobService"android:enabled="true"android:exported="true"android:permission="android.permission.BIND_JOB_SERVICE"></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 < 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 {@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;}}


3.WorkManager用法简介

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、输入数据如何传递给工作等等。


4.双进程保活

双进程保活的方式就是在运行了一个主进程之外,还运行了一个 “本地前台进程”,并绑定“远程前台进程”, “远程前台进程”与“本地前台进程”实现了相同的功能,代码基本一致,这两个进程都是前台进程,都进行了提权,并且互相绑定,当监听到绑定的另外一个进程突然断开连接,则本进程再次开启前台进程提权,并且重新绑定对方进程,以达到拉活对方进程的目的。

双进程保活的实现步骤如下:

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<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 {@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 < 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);}@Overridepublic 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 {@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 < 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);}@Overridepublic 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 {@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));}}


5.双进程保活+JobScheduler整合方案

这种方案是在 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 < 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



文章列表
新年伊始,喜讯传来!武汉万象奥科凭借过去一年在RK系列嵌入式技术方案开发、市场推广等方面的努力和积累,从Rockchip众多合作伙伴中脱颖而出,荣获瑞芯微“2024年度优秀合作奖”!这一荣誉的获得,是对武汉万象奥科技术实力和服务水平的充分肯定,更是双方合作、共赢的最佳见证。过去一年,万象奥科与瑞芯微紧密合作,成功推出并量产了搭载瑞芯微RK3588、RK3576、RK3562、RK3506等芯...
`overlayroot` 是一种使用 OverlayFS 实现的功能,可将根文件系统挂载为只读,并通过一个临时的写层实现对文件系统的修改。这种方法非常适合嵌入式设备或需要保持系统文件完整性和安全性的场景。下文以 RK3568 平台为例,介绍制作 overlayroot 的详细步骤。1. 制作精简文件系统ramdisk1.1   环境准备1. 目标系统:确保系统支持 OverlayFS(内核...
RK3506是一款高性能三核Cortex-A7处理器,内部集成Cortex-M0核心, 适合于工业控制、工业通信、人机交互等应用场景。为方便开发、使用,万象奥科推出HD-RK3506G-MINI单板机,仅需99元即可入手。单板机接口包含:以太网、RS232/RS485、CAN总线、液晶屏,支持接口扩展!产品特点:  多核异构3xCortex-A7+Cortex-M0,低功耗、高实时  外设接...
RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,...
RK3506单板机(卡片电脑)是一款高性能三核Cortex-A7处理器,内部集成Cortex-M0核心,RK3506单板机具有接口丰富、实时性高、显示开发简单、低功耗及多系统支持等特点,非常适合于工业控制、工业通信、人机交互等应用场景。 多核异构3xCortex-A7+Cortex-M0 外设接口丰富,板载网络、串口、CAN总线 支持Buildroot、Yocto系统,支持AMP混合部署 支...
作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!
UGFC是Unified Gold Finger Core Board的缩写(意指:统一接口定义金手指核心板),为武汉万象奥科电子有限公司基于企业标准定义的一种针对嵌入式、低功耗、通用型的小型计算机模块标准,采用204Pin金手指连接器,基于ARM架构的MPU平台,主要面向泛工业领域的数据采集、边缘数据处理、接口通讯与人机交互等应用场景。
1. 概念SMARC(Smart Mobility ARChitecture,智能移动架构)是一种通用的小型计算机模块定义,基于ARM和X86技术的模块化计算机低功耗嵌入式架构平台,旨在满足低功耗、低成本和高性能的应用需求。这些模块通常使用与平板电脑和智能手机中相似的ARM SOC,或其他低功耗SOC和CPU。  图片(314 Pin金手指)2. 起源SMARC最初名为ULP-COM,主要发...
网络时间协议NTP(Network TimeProtocol)是用于互联网中时间同步的标准互联网协议,可以把计算机的时间同步到某些时间标准。NTP对于我们产品来说有什么用呢,简单的讲,当你的设备时间不准确了,你可以接入到互联网,从网上同步一下时间,非常方便。
在 Android 开发中,源码管理是一项至关重要且颇具挑战性的任务。面对包含数百个 git 库的 Android 源码,如何高效地进行下载、管理和协作开发成为了开发者们必须攻克的难题!
武汉万象奥科电子有限公司
服务热线:027-59218026
销售邮箱:sales@vanxoak.com
主营产品:ARM核心板、ARM工控板
工业网关、软硬件定制设计
地址:武汉东湖新技术开发区大学园路长城园路8号海容基孵化园B5

扫描二维码
关注公众号
产品及方案咨询
专业专注   合作共赢
移动电话:181 2058 0511 (湖北)
移动电话:175 7010 9551 (华南、华中)
移动电话:133 0386 9667 (华东、华北)
移动电话:187 3812 7320 (其他)