这是一个轻量级的库,配置几行代码,就能够实如今android上实现进程常驻,也就是在系统强杀下,以及360获取root权限下,clean master获取root权限下都没法杀死进程java
支持系统2.3到6.0linux
支持大部分设备,包括三星,华为,oppo,nexus,魅族等等android
能够简单对开机广播进行保护git
github地址:github
https://github.com/Marswin/MarsDaemonapi
原理分析:安全
Android 进程常驻(0)----MarsDaemon使用说明
函数
Android 进程常驻(2)----细数利用android系统机制的保活手段
spa
Android 进程常驻(3)----native保活5.0如下方案推演过程以及代码详述
Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述
Android 进程常驻(5)----开机广播的简单守护以及总结
正文:
今天继续昨天,一气呵成,争取这个礼拜所有写完。
上一篇文章留了一个别人的github连接,他里面的native保活实现方案也是大多数公司采用的方案。
他是首先开启一个c进程,将须要保活的service名字传递进去
而后定时给本身主进程发一个intent,若是主进程挂掉了,就能够顺利拉起来保证存活。
并且,这是要创建在保证c进程不挂的基础上,才能轮询,可是就目前来看,只有5.0如下的非国产机才会有这样的漏洞。也就是说在force close的时候,系统忽略c进程的存在,5.0以上包括5.0的哪怕源生系统也会连同c进程一块儿清理掉,国产机就更不用说了。就算是这样,在5.0如下的非国产机上,若是安装了获取root权限的360\cm的话,也是能够直接清理掉,也就是说会失效。
并且他不但不算守护,并且仍是单向的,也就是说只能a保b,b保不了a;a保b也不是在b死了马上拉起来,要等到了时间才会去拉。
最后,就算把刚才说的都排除掉,在不多的一部分手机,也就是低端且没有安装安全软件的手机上,他仍然没法保证时时存活。
那么怎么样才能实现双向守护呢?首先咱们想到的是fork这个函数,他会建立一个子进程,而后在父进程中调用waitpid()这个函数,这是一个阻塞函数,他会一直wait到子进程挂掉,才会继续向下执行,利用这个机制,咱们能够在主进程的c层fork一个子进程,而后父进程就能够监听到子进程的死亡,死亡的时候再重启子进程。
这样作,普通杀是没有问题。可是force close不会按照你的要求先杀孩子,等你把孩子启动起来,再杀父亲,而后坐视子进程在那无论,三方软件自没必要说。那么先杀父进程的话,子进程就没办法监听到父进程的死亡吗?
有朋友要说能够利用linux的进程领养机制,若是父进程挂掉,那么子进程就会被linux的init进程领养,进程所对应的父进程id也会变成1。这的确是一个很好的标示,可是要怎样监听这个状态的变化呢?轮询获取父进程id,而后判断是否等于1?那么轮询的间隔为多少合适?1秒间隔算短算长?设为1秒的话force close掉你的时候,根本不会等到你那每秒正时正点的轮询点上,会被forceclose直接干掉,我试过更短的时间,基本要小到小于10毫秒的时间间隔,才有可能再force close的时候检测到,并成功拉起父进程。注意个人关键词,小于10毫秒!才可能!对,一秒100次的检查,才有可能,只是可能!手机待机十分钟就已经能够开始烫手,三个小时电池发出了低电量警告。代码:
好,咱们继续。
waitpid是阻塞函数,因此他必定是没有耗电问题的,即时性也没有问题。那么问题就集中在了子进程如何监听到父进程的死亡上面,把那个轮询替掉。
而后,我想到的是管道,linux中有多种ipc通讯机制,管道是最基本的一种通讯方式,且这种管道只能在父子进程间创建,因而我想是否能利用这个机制呢?在父子进程间创建管道,可是并不写入数据,只是使用阻塞方法在另外一端去读取管道,这样若是对方进程挂掉,管道会被破坏,那么另外一端的读取方法就会执行返回,由此肯定对方挂掉而后重启对方。
代码:
这作样确实解决了耗电问题,父子进程两端的监听都是阻塞方法,耗电基本能够忽略不记,也基本实现双向守护。(以上代码不在项目工程里)
可是问题又来了
一、用ps命令发现fork出来的进程内存占用很大
二、fork出来的进程名字与父进程名字相同
缘由:
一、fork函数调用的时候,会复制父进程的所有内存,由于父进程必定是咱们须要保证常驻的java进程,他在初始化的时候是fork的一个zygote进程,即时在应用刚初始化的时候fork,进程里面是有一个java虚拟机的内存在里面的,最少一二十兆是有了。fork出来子进程多的内存最后都会算到咱们本身应用的内存中。
二、机制如此
咱们继续讨论内存的问题,如何不用fork也能创建管道呢。因而我想到了运行一个二进制可执行文件,这样他是一个相对独立的进程,可是又能够创建父子进程之间的管道。将真正用来实现的子进程写到一个二进制文件中(对应文件源码/MarsDaemon/LibMarsdaemon/jni/daemon.c),这样既解决了内存问题,又能够本身给新的进程命名。
问题解决了吗?没有,直接execute一个binary文件以后
一、发现代码再也不继续向下执行
二、waitpid又不能用了
缘由和解决方法:
一、直接运行一个二进制文件,他会占用原进程,因而咱们这里仅将fork用做一个启动binary的工具,fork终于回归到了Linus但愿他做用的地方
二、父子进程间的管道是单向的,因而咱们能够建两根管道。ab两个进程,建12两个管道。a进程关掉管道1的写端,堵瑟调用管道2的读取方法;b进程关掉管道2的写端,堵瑟调用管道1的读取方法。这样就能够实现双向监听。任何一方监听到对方死掉就做出相应的动做,启动对方。至此,彻底摒弃开始的fork方案。
代码:
此为最终方案,代码见下
下面讲一下代码
二进制文件存放在assets下面,程序第一次启动的时候会将他拷贝到手机项目/data/data/...下,而后
源码/MarsDaemon/LibMarsdaemon/src/main/java/com/marswin89/marsdaemon/strategy/DaemonStrategyUnder21.java
load对应c库,执行代码
源码/MarsDaemon/LibMarsdaemon/jni/daemon_api20.c
一、将对应的packagename,servicename以及二进制可执行文件的路径传进来
二、清理僵尸进程,就像最开始讲的,低端手机会忽略c进程,若是咱们恰巧运行在低端手机上,那么c进程得不到释放会愈来愈多,咱们称他为僵尸进程,须要清理一下
三、创建两条管道
四、执行二进制文件,将上面的参数传递进去
五、而后关掉本身管道1的写端和管道2的读端,而后阻塞读取管道1,若是读取到,则表明子进程挂掉
再来看二进制可执行文件的代码
源码/MarsDaemon/LibMarsdaemon/jni/daemon.c
二进制文件在程序启动起来的时候,将参数解析出来
关掉管道1的读端和管道2的写端,而后调用管道2的阻塞读取方法,若是执行过去,说明父进程死掉
这里用fork是为了让他的父进程id好看一些,别无他意
监听到以后的策略看下面。
==========================分割线=========================
而后说一下监听到对方进程死后的策略
你会说谁监听到对方死了,就直接拉起来就行了呀。
问题:
一、从新拉起来要从新创建双管道,子进程挂掉,父进程把他重启起来创建双管道还好说,若是父进程挂掉,子进程把父进程启动起来,他们之间就没法创建链接,并且若是中间出了差错,同步起来很费劲,因而我选择,不管谁监听到谁死了,都重启对方,而后自杀,从新初始化!
二、若是执行force close, 系统先杀父进程,子进程监听到以后拉起父进程而后自杀,可是系统杀你两个进程的间隔时间很是很是短,父进程刚起来还没来得及初始化,系统赶过来杀父进程。有的手机强杀以后很短一段时间没法拉起父进程。因而我选择用第三个进程。
第三个进程和以前的父子进程都没有任何关系,他的做用只是用作拉起常驻进程。父子进程不管谁监听到谁死,都拉起第三个进程,第三个进程负责拉起常驻进程,而后自杀。(用户其实是看不到他的存在的,由于他可能只存活不到一秒就自杀了)
代码
/MarsDaemon/LibMarsdaemon/src/main/java/com/marswin89/marsdaemon/strategy/DaemonStrategyUnder21.java
在常驻进程初始化的时候,初始化一个alarm,保存在内存中,以便之后使用
监听到子进程死了时候使用闹钟拉起第三个进程,二进制文件监听到父进程死掉,直接用c代码发intent,见上面c代码
第三进程启动起来,就是负责把常驻进程拉起来,而后自杀掉。
==========================分割线=========================
好了,这只是5.0如下的策略,5.0以上,以及6.0都很差用
下一篇咱们开始分析5.0+的策略