Android守护进程

2023-05-16

守护进程

守护进程: 一直在后台运行的进程。本文主要讲解一些android比较常用的守护进程的方法。

实现思想:
1.保活,通过提高进程优先级,降低进程被杀死的概率
2.拉起,进程被杀死后,进行拉起

相关基础知识

Android进程优先级

在Android中,进程粗略的分成五个等级,分别是:

1.前台进程
2.可见进程
3.服务进程
4.后台进程
5.空进程

此类相关知识可以在https://developer.android.com/guide/components/processes-and-threads 查询阅读

前台进程:

用户当前操作所必须的进程。如果一个进程满足以下任一条件,即可视为前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,后者绑定到用户正在交互的 Activity
托管正在“前台”运行的 Service(服务已调用 startForeground())
托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver
通常,只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。

可见进程:

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。(除了对话框可以是透明的Activity)
托管绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程

后台进程:

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态

空进程:

不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

Android进程优先级的依据

1.每一个Android应用进程中,都可能包含四大组件中的一个或多个
应用进程是由AMS发送请求让zygote创建的,并由AMS对于每一个运行的都有一个ProcessRecord对象与之对应。组件的状态就是其所在进程优先级的决定性因素。组件的状态指:
Activity是否在前台,用户是否可见
Service正在被哪些客户端使用
ContentProvider正在被哪些客户端使用, BroadcastReceiver是否在接受广播
在这里插入图片描述

常见的使用方案

1.白色保活
2.灰色保活
3.黑色保活
4.双进程守护
5.JobService轮询

白色保活

所谓白色保活,就是通过启动前台服务使得进程优先级提高到前台进程。
优点:写法简单,处理方便
缺点:前台服务和通知绑定在一起,意味着开启服务要伴随一条通知在通知栏,用户有感知 。
API:
startForeground(int id, Notification notification);
target26,并且系统8.0之后由于谷歌对后台服务的限制,做法改为:
startForegroundService(context, intent);
并且在创建服务后的五秒内调用
startForeground(0, new Notification());
不调用会发生anr

灰色保活

所谓灰色保活,就是利用系统漏洞开启前台服务。
优点:开启前台服务的情况下,可以去掉通知,使得用户无感知
缺点:target26 8.0以上的系统该漏洞已修复,因此不适用
做法:
正常开启一个服务并发出通知后,开启另一个服务也发出通知,保持两条通知的id一致,关掉第二个服务并且在onDestroy中调用stopForeground(true)去掉通知。此时第一个服务仍为前台服务。

黑色保活

所谓黑色保活,就是利用通知拉起进程。
适用对象:腾讯系全家桶,阿里系全家桶,应用之间互相拉起

双进程守护

所谓双进程守护,就是指两个进程互相监视,一旦有一个进程死了,另一个进程监听到就拉起。
依托这个原理,衍生出的双进程守护的方案有多种,比如利用监听socket连接中断实现,利用文件锁实现,利用android的绑定服务实现。以服务绑定为例来说:
context.bindService(intent, serviceconnection, flag);
这里的serviceconnection就是监听回调,回调中有onServiceConnected方法和onServiceDisconnected方法这两个,通过onServiceDisconnected可以监听到另一个服务是否还存活。把两个服务放在两个进程就能够做到监听拉起进程。

JobService

通过定时触发任务,判定进程是否存活,如果不存活了,则拉起
优点:5.0以后出现的JobService是官方推荐的方式,比较稳定
缺点:触发时机不够实时,JobService的触发时机会是充电时,闲暇时等特殊时机或者是周期性执行

Demo举例:

将灰色保活,双进程守护,JobService融合在一起使用。

LocalService(写应用逻辑的代码)
public class LocalService extends Service {
    private final static int NOTIFICATION_ID = 1003;
    private static final String TAG = "LocalService";
    private ServiceConnection serviceConnection;

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

    @Override
    public void onCreate() {
        super.onCreate();
        //此处可以写上一些业务逻辑
            try {
            Notification notification = new Notification();
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                startForeground(NOTIFICATION_ID, notification);
            } else {
                startForeground(NOTIFICATION_ID, notification);
                // start InnerService
                startService(new Intent(this, InnerService.class));
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        serviceConnection = new LocalServiceConnection();
        startService(new Intent(this, RemoteService.class));
        bindService(new Intent(this, RemoteService.class), serviceConnection, BIND_AUTO_CREATE);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }


    class LocalServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "bind RemoteService");
            //服务连接后回调
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "remote service died,make it alive");
            //连接中断后回调
            startService(new Intent(LocalService.this, RemoteService.class));
            bindService(new Intent(LocalService.this, RemoteService.class), serviceConnection,
                    BIND_AUTO_CREATE);
        }
    }


    public static class InnerService extends Service {

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

        @Override
        public void onCreate() {
            super.onCreate();
            try {
                startForeground(NOTIFICATION_ID, new Notification());
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            stopSelf();
        }

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

    static 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 void onDestroy() {
        super.onDestroy();
    }
}

从代码中可以看出这里使用了灰色保活,开启了另一个服务,使用同一通知id,之后又将该服务关闭,从而去掉了通知栏的通知,注意该方法在target26 8.0的手机上已无效,target26 8.0中官方修复了该漏洞,所以只能乖乖的弹出通知保持前台服务。

RemoteService:
public class RemoteService extends Service {
    private final static int NOTIFICATION_ID = 1002;
    private static final String TAG = "RemoteService";
    private ServiceConnection serviceConnection;

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

    @Override
    public void onCreate() {
        super.onCreate();
        try {
            Notification notification = new Notification();
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                startForeground(NOTIFICATION_ID, notification);
            } else {
                startForeground(NOTIFICATION_ID, notification);
                // start InnerService
                startService(new Intent(this, InnerService.class));
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }

        serviceConnection = new RemoteServiceConnection();
        startService(new Intent(this, LocalService.class));
        bindService(new Intent(this, LocalService.class), serviceConnection, BIND_AUTO_CREATE);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }


    class RemoteServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //服务连接后回调
            Log.d(TAG, "bind LocalService");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "main process local service died,make it alive");
            //连接中断后回调
            startService(new Intent(RemoteService.this, LocalService.class));
            bindService(new Intent(RemoteService.this, LocalService.class), serviceConnection,
                    BIND_AUTO_CREATE);
        }
    }


    public static class InnerService extends Service {

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


        @Override
        public void onCreate() {
            super.onCreate();
            try {
                startForeground(NOTIFICATION_ID, new Notification());
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            stopSelf();
        }

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

    static class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }


}
DaemonJobService:
public class DaemonJobService extends JobService {
    private static final String TAG = "MyJobService";

    public static void startJob(Context context) {
        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

        JobInfo.Builder builder = new JobInfo.Builder(10, new ComponentName(context.getPackageName(), DaemonJobService.class.getName())).setPersisted(true);

        //小于7.0
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // 每隔1s 执行一次 job
            builder.setPeriodic(1_000);
        } else {
            //延迟执行任务
            builder.setMinimumLatency(1_000);
        }

        if (jobScheduler != null) {
            jobScheduler.schedule(builder.build());
        }
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        //如果7.0以上 轮训
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJob(this);
        }

        //JobSchedule结合双进程守护
        boolean isLocalRun = ProcessUtils.isRunningService(this, LocalService.class.getName());
        boolean isRemoteRun = ProcessUtils.isRunningService(this, RemoteService.class.getName());
        if (!isLocalRun || !isRemoteRun) {
            startService(new Intent(this, LocalService.class));
            startService(new Intent(this, RemoteService.class));
        }
        return false;
    }

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

使用方法
public class DaemonApp extends BaseApp {

    @Override
    public void onCreate() {
        super.onCreate();
        //守护进程初始化
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH){
            DaemonJobService.startJob(this);
        } else {
            startService(new Intent(this, LocalService.class));
            startService(new Intent(this, RemoteService.class));
        }
    }
}

使用adb命令查看效果

adb shell dumpsys activity services 包名
可以查看某个应用对应的服务的情况

在这里插入图片描述

图中createdFromFg代表该服务是前台服务,而从自身进程名和绑定的进程名称可以看出这两个服务在两个进程,并且相互绑定了,即双进程守护构建完成

在Application中进行初始化即可
Demo下载链接:https://download.csdn.net/download/breeze048/10771453

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android守护进程 的相关文章

  • esp8266对接天猫精灵(11)终端编程

    一 编写lua脚本获取控制信息 xff08 8266 xff09 前边也说过 xff0c 这个脚本要实现的步骤可以分三步 xff0c 第一步是联网 xff0c 第二步是使用http get到数据 xff0c 然后控制要控制的设备 我们的lu
  • Altium Designer -- 精心总结

    如需转载请注明出处 xff1a http blog csdn net qq 29350001 article details 52199356 以前是使用DXP2004来画图的 xff0c 后来转行 想来已经有一年半的时间没有画过了 突然转
  • Arduino点亮ws2812

    先加载ws2812库文件 span style color rgb 68 68 68 font family none font size 14px background color rgb 255 255 255 a target bla
  • Arduino 旋转编码器ky-040

    int pinA 61 3 Connected to CLK on KY 040 CLK接 pin3 int pinB 61 4 Connected to DT on KY 040 DT接pin4 SW是按键 xff0c 不用接 int e
  • Dockerfile

    Dockerfile文件 xff0c 自创镜像 一 概念 xff1a 二 案例 xff1a 1 先创建一个Dockerfile文件2 编写内容3 构建镜像4 运行 三 指令 xff1a FROM xff1a MAINTAINER xff1a
  • 关于嵌入式无人机之模型创建-使用3D打印装配体01

    做嵌入式的同学离不开硬件的支撑 那么无人机作为一套综合性工程 span class token punctuation span 涉及软件 span class token punctuation span 嵌入式 span class t
  • 牢记公式,ardupilot EKF2就是纸老虎(三)!

    版权声明 xff1a 本文为博主原创文章 xff0c 转载请附上博文链接 xff01 三 掀开EKF2的神秘面纱 EKF2是EKF算法在ardupilot上的代码实现 读到这里你也许已经忘了 xff0c EKF的5大公式 虽然下面是7个公式
  • ROS节点 报错process has died [pid 3322522, exit code -11

    偶尔出现 我的情况是两个节点 ros span class token double colon punctuation span span class token function init span span class token p
  • JS的冒泡函数

    今天下午学习了冒泡函数的加载和运行 一 var a 61 20 30 40 20 15 5 25 for var i 61 0 i lt a length i 43 43 for var j 61 1 j lt a length j 43
  • ubuntu server 版安装桌面

    安装xfce4 xff08 或其他桌面 xff09 amp xinit sudo apt install xfce4 xinit安装Display Manager 安装xdm xff0c 虽然有着古早的界面 xff0c 但是不会安装任何依赖
  • ubuntu下gazebo加载很慢解决办法

    ubuntu下gazebo加载很慢解决办法 前言 刚安装好 r o s ros r o s 后 xff0c 在终端输入命令 gazebo 启动 g
  • 从用户态是怎么切换到核心态的?

    此文章参考了 Linux 用户态通过中断切换到内核态详解 简答来说 xff0c 用户态和核心态的区别就是 xff1a 两者的操作权限不同 xff0c 用户态的进程能够访问的资源受到了极大的控制 xff0c 而运行在内核态的进程可以 为所欲为
  • 将rgbd数据集制作成rosbag,并发布图片和camera_info消息

    因为最近做的项目需要和别的开源项目做一些对比 xff0c 比如rgbdslamV2 xff0c 但是rdgbslamV2使用的输入是rosbag xff0c 并且他必须要订阅四个话题才能运行 xff0c 这四个话题分别是 xff1a cam
  • PX4添加自定义日志消息

    固件版本 xff11 11 一 将要观察的数据声明成uORB消息 xff0c 并发布 我这里随便添加了一个 在logged topics cpp里的add default topics函数里加上一行add topic fanbu 100 或
  • ROS常用知识点总结——Cmakelist

    Cmakelist编译规则 1 Cmakelist通常需要添加可执行文件名称 xff0c 并且指定编译的源文件即可 2 添加头文件编译时候 xff0c 应当在Cmakelist文件中打开以下标记出的部分 xff0c 否则编译找不到头文件 C
  • agx xavier rtso1001载板启动

    recovery按键长按 gt reset键按一下 gt 3s后松开recovery键 ubuntu命令行 lsusb lsusb Bus 004 Device 001 ID 1d6b 0003 Linux Foundation 3 0 r
  • 了解卡尔曼滤波器1--状态观测器

    在本文中 xff0c 我们将讨状态观测器 这个概念将有助于解释卡尔曼滤波器是什么以及它是如何工作的 让我们从一个例子开始 这是小蒂米 你想知道他的心情以及他现在的感受 然而 xff0c 没有直接的方法能测量他的心情 所以 xff0c 你要做
  • 牢记公式,ardupilot EKF2就是纸老虎(五)!

    版权声明 xff1a 本文为博主原创文章 xff0c 转载请附上博文链接 xff01 四 一睹EKF2芳容 xff08 上接牢记公式 xff0c ardupilot EKF2就是纸老虎 xff08 四 xff09 xff09 更新 本博客讲
  • jsp页面引入jstl标签后台报java.lang.ArrayIndexOutOfBoundsException

    jsp引用了jstl的core标签和fmt标签 xff0c 但访问jsp时后台报java lang ArrayIndexOutOfBoundsException xff0c 如下图所示 xff1a 花了很长时间上网查找原因也没有找到答案 x
  • 了解卡尔曼滤波器4--非线性状态估算器(EKF,UKF,PF)

    一般来说 xff0c 我们希望我们的生活是线性的 xff0c 就像这条线 xff0c 这可能表示成功 收入或者幸福 但实际上 xff0c 生活并不是线性的 xff0c 它充满了起伏 xff0c 有时甚至更复杂 如果您是工程师 xff0c 您

随机推荐

  • Velodyne VLP16激光雷达在ubuntu中的使用教程

    Velodyne VLP16激光雷达在ubuntu中的使用教程 设备连接工作使用ROS驱动并添加点云客官 xff01 如果觉得有用 xff0c 给俺点赞支持下呗 xff01 设备连接工作 连接雷达有线网络进入有线设置 xff08 点击ubu
  • 在ROS中用openCV显示摄像头并发布图像数据

    在ROS中用openCV显示摄像头并发布图像数据 前言使用C 43 43 实现CMakeLists txt配置 使用python实现查看usb串口使用情况永久打开串口权限ROS使用openCV进行图片接收 xff0c 并显示客官 xff01
  • D435i标定摄像头和IMU笔记一(配置环境篇)

    IntelRealSense D435i摄像头和IMU标定笔记一 xff08 配置环境篇 xff09 一 标定方案二 环境要求三 环境配置3 1 基本环境安装3 1 1 for ubuntu16 043 1 2 for ubuntu18 0
  • D435i标定摄像头和IMU笔记二(RGB摄像头标定篇)

    D435i标定摄像头和IMU教程二 xff08 RGB摄像头标定篇 xff09 一 文件准备二 录制摄像头标定rosbag三 开始标定四 标定结果 参考视频 D435i标定摄像头和IMU笔记一 xff08 配置环境篇 xff09 D435i
  • D435i标定摄像头和IMU笔记三(IMU标定篇)

    D435i标定摄像头和IMU笔记三 xff08 IMU标定篇 xff09 一 RealSense官网驱动标定IMUbias和三个轴的对齐1 1 驱动安装1 2 标定 二 港科大imu utils标定IMU随机游走2 1 环境及文件准备2 2
  • D435i标定摄像头和IMU笔记四(RGB摄像头和IMU联合标定篇)

    D435i标定摄像头和IMU教程四 xff08 RGB摄像头和IMU联合标定篇 xff09 一 前提二 准备标定文件 2 1 标定板yaml文件2 2 相机标定yaml文件2 3 imu标定yaml文件2 4 录制rosbag文件2 5 标
  • D435i标定摄像头和IMU笔记二-2(RGB+双目多摄像头标定篇)

    D435i标定摄像头和IMU笔记二 2 xff08 RGB 43 双目多摄像头标定篇 xff09 一 文件准备二 标定准备三 开始标定四 标定结果 参考视频 D435i标定摄像头和IMU笔记一 xff08 配置环境篇 xff09 D435i
  • D435i标定摄像头和IMU笔记四-2(双目摄像头与IMU联合标定篇)

    D435i标定摄像头和IMU笔记四 2 xff08 双目摄像头与IMU联合标定篇 xff09 一 前提二 文件准备2 1 标定板文件checkerboard yaml xff08 前面多次用到 xff09 xff1a 2 2 imu标定结果
  • px4+vins+ego单机鲁棒飞行一(px4+mavros篇)

    px4 43 vins 43 ego单机鲁棒飞行一 xff08 px4 43 mavros篇 xff09 一 mavors安装二 mavros中的重要插件Plugins三 mavros中的重要topic四 mavros中的tf树五 px4校
  • ardupilot在Linux上设置SITL(FlightGear)

    本页介绍如何在Linux上设置SITL xff08 软件在环路中 xff09 特定命令在Ubuntu上从12 10到16 04进行了测试 概观 SITL模拟器允许您在没有任何硬件的情况下运行Plane xff0c Copter或Rover
  • px4+vins+ego单机鲁棒飞行二(外部位姿估计篇)

    px4 43 vins 43 ego单机鲁棒飞行二 xff08 外部位姿估计篇 xff09 一 使用px4的EKF2 xff08 扩展卡尔曼 xff09 估计测试过程及结果 xff1a 二 使用px4的LPE估计测试过程及结果 xff1a
  • px4+vins+ego单机鲁棒飞行三(realsense_ros配置及经验篇)

    px4 43 vins 43 ego单机鲁棒飞行三 xff08 realsense ros配置及经验篇 xff09 一 驱动及realsense ros安装二 参数设置三 经验 一 驱动及realsense ros安装 D435i标定摄像头
  • px4+vins+ego单机鲁棒飞行四(PX4飞控日志分析篇)

    px4 43 vins 43 ego单机鲁棒飞行四 xff08 PX4飞控日志分析篇 xff09 一 FlightPlot安装二 记录日志二 取出日志三 分析日志 一 FlightPlot安装 参考博客 参考视频 二 记录日志 在QGC中参
  • px4+vins+ego单机鲁棒飞行二-1(更改px4外部视觉估计固件)

    px4 43 vins 43 ego单机鲁棒飞行二 1 xff08 更改px4外部视觉估计固件 xff09 一 EKF2源码 获取视觉里程计信息二 EKF2源码 设置外部视觉数据三 源码中对位置的发送四 测试 前提 xff1a 固件1 11
  • px4+vins+ego单机鲁棒飞行五(坐标系变换篇)

    px4 43 vins 43 ego单机鲁棒飞行五 xff08 坐标系变换篇 xff09 一 齐次矩阵变换原理二 无人机上利用旋转矩阵求飞机中心位置 一 齐次矩阵变换原理 参考一 参考二 二 无人机上利用旋转矩阵求飞机中心位置 首先写出相机
  • 编译多版本opencv,并在cmakelists中链接

    编译多版本opencv xff0c 并在cmakelists中链接 一 下载二 编译三 链接四 替代系统的 xff08 可选 xff0c 但不建议 xff09 五 链接了 xff0c 但无法找到 一 下载 github链接 自己选择版本 x
  • CMakeLists笔记

    CMakeLists笔记 一 路径名二 函数三 常用 一 路径名 PROJECT SOURCE DIR xff1a 一般为catkin ws src xff0c 是cmakelists的绝对路径PROJECT BINARY DIR xff1
  • 源码编译安装openvino

    源码编译安装openvino 1 原地升级cmake2 编译opencv4 5 33 下载openvino4 配置usb规则 参考博客 交叉编译方式 1 原地升级cmake 方法一 xff1a 下载3 19 0中的CMake 3 19 0
  • 【ros】读取串口数据

    文章目录 一 自定义 gnrmc msg二 代码三 结果四 注意点 有时候 有的设备是通过串口发送数据 xff0c 想要在 ros 中 xff0c 读取串口数据 xff0c 记录一下操作 xff1a 一 自定义 gnrmc msg 首先需要
  • Android守护进程

    守护进程 守护进程 一直在后台运行的进程 本文主要讲解一些android比较常用的守护进程的方法 实现思想 1 保活 xff0c 通过提高进程优先级 xff0c 降低进程被杀死的概率 2 拉起 xff0c 进程被杀死后 xff0c 进行拉起