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

描述

Android 应用进程保活

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

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

  1. 使用前台服务(Foreground Service):将服务调用startForeground()方法,并传入一个通知对象,将该服务置于前台运行状态。这样可以使得该服务的优先级更高,从而减少被系统杀死的概率。
  2. 使用JobScheduler:使用setPeriodic()方法可以让应用程序周期性地执行任务,从而避免长时间占用CPU资源, setPersisted(true)方法则表示当设备重启后,该任务仍然需要继续执行。
  3. 使用AlarmManager:使用这个API可以让应用程序在指定的时间间隔内执行任务。例如,可以设置一个闹钟,每隔一段时间唤醒应用程序并执行一些操作。
  4. 使用守护进程:启动一个后台守护进程,监控应用程序的状态并在应用程序被杀死时重新启动它,使用守护进程需要申请额外的权限。
  5. 使用双进程保活:启动两个相互绑定的进程,在其中一个进程被杀死时,另一个进程可以重新启动它。
  6. 使用WorkManger: 这是目前比较新的保活机制,用于取代JobScheduler。

 

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

  1. JobScheduler 用法简介

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

在应用中创建一个 JobService 服务,JobService 需要 API Level 21以上才可以使用,该服务注册时必须声明 android.permission.BIND_JOB_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;    } }

 

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); } @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. 输入数据如何传递给工作等等。

 

4.双进程保活

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

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

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

 

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

这种方案是在 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());    } }

6.参考文献

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

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分