Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述

这是一个轻量级的库,配置几行代码,就能够实如今android上实现进程常驻,也就是在系统强杀下,以及360获取root权限下,clean master获取root权限下都没法杀死进程java

支持系统2.3到6.0linux

支持大部分设备,包括三星,华为,oppo,nexus,魅族等等android

能够简单对开机广播进行保护git


github地址:github

https://github.com/Marswin/MarsDaemonwindows

原理分析:api

Android 进程常驻(0)----MarsDaemon使用说明
session

Android 进程常驻(1)----开篇
多线程

Android 进程常驻(2)----细数利用android系统机制的保活手段
测试

Android 进程常驻(3)----native保活5.0如下方案推演过程以及代码详述

Android 进程常驻(4)----native保活5.0以上方案推演过程以及代码详述

Android 进程常驻(5)----开机广播的简单守护以及总结



正文:



上一篇咱们经过父子进程间创建双管道,来监听进程死掉,通过测试,无耗电问题,无内存消耗问题,能够在设置中force close下成功拉起,也能够在获取到root权限的360/cleanmaster下成功存活。


但是放到5.0+的系统就不能用了,为何呢?咱们来看源码4.4系统和5.0系统在系统force close的时候都作了什么修改。


4.4.3的ActivityManagerService

实如今这里



而后5.0的AMS



实现




能够看出来5.0的源码中系统强杀的时候会连同同group中的全部进程也一块儿干掉,使用上一篇的策略经过打印log咱们也能够看出,在父进程被杀死的时候,子进像是被冻结了同样,作不了任何事情,拿不到监听事件。那么咱们该怎么办呢?

开始本身头脑风暴,想出如下几种解决策略:


一、放弃java进程,使用两个纯native的binary进程,相互保活,确保native进程常驻,而后按时拉起须要常驻的java进程。

二、使用setsid将子进程的session改变,是否可让系统不冻结子进程。

三、子进程建立子进程,而后自杀,重复1000次,也就是父进程和本身子进程的子进程的子进程重复一千遍的进程之间互保,是否可让系统不冻结子进程


通过代码尝试,结果你能够猜到,没错,然并卵。这几种方案都没法阻挡系统的force close,就更不用说360/cm with root了。


因而换一个思路,再也不纠结于父子进程,而是两个普通进程之间是否能互保


鉴于以前pipe管道的使用,首先想到的是fifo管道,这是linux下能够创建在两个没有关系的进程间的双向管道。一样两个进程间在c层创建fifo管道,而后阻塞读取。可问题在于,这个fifo与pipe是有本质区别的,fifo是经过一个文件作通讯的,对方进程挂掉以后,本身这边既不会报错也不会返回,而是一直阻塞,直到管道里面有数据。因此,然并卵。


那么,当进程死的时候,本身往管道里面写数据,另外一边阻塞读到数据不就能够得知这边死掉了吗?当时确实有这样一瞬间的好笑想法,可是这要首先要本身知道本身的死亡啊。


这个好笑的想法没有什么意义,可是他启发了我,当一个进程死掉的时候,他和另外一个进程之间的什么会发生变化呢?


顺着这个思路,我想到了文件锁。不论windows仍是linux都会有一套文件的进程同步机制,为了各个进程间文件的同步问题,一个进程能够给一个文件上锁,操做完了以后再解锁,其余进程若是担忧同步问题,会首先检查一下文件是否有锁,若是有锁,表明别人正在操做,就会延迟对文件的操做。那么当一个进程挂掉,他给文件加的锁也天然会消失。


因而按照这个思路,进程a给文件1加锁,而后阻塞读取文件2的锁,进程b给文件2加锁,而后阻塞读取文件1的锁,若是对方进程挂掉,他所在进程所持有的文件锁会当即释放,那么另外一个进程就能够读取到被释放文件锁的文件,监听到对方挂掉。


实现这个方案,首先使用的java代码,java里是有一套文件锁的api的,可是写完以后编译时报错。我就不写demo截图了,错误的信息是死锁exception,两个进程不能互相持有对方的锁。java的编译器可真多事儿,因而我想能不能用三个进程,1拿2的锁,2拿3的锁,3拿1的锁,仍是不行,deadlock exception.


怀着忐忑的心情我用c来实现尝试,庆幸的是c中没有问题。原理通了,剩下的就是把代码写健壮,最难搞的问题就是同步问题,ab两个进程,若是b进程还没给文件加锁,a就开始读锁,那么就会误觉得b进程挂掉了。需求:
一、须要让a进程先把文件1锁了,而后不去读文件2,等b进程作一样的操做以后,两边再同时读取对方的锁。
二、须要考虑第程序从新进入的初始化状态与单个进程等待的状态冲突问题
三、不能耗时!!时间很重要,后面会说到


这一块确实想了一夜才想出来合适的解决方案,首先确定不能用管道、信号这些进程间通讯,连memset都不要考虑,由于耗时,java层的机制就更不用考虑了!那么就只能用一些标示位手段,好比一个文件在了就表明一个进程在了,一个文件没了,就表明一个进程锁好了。
因而最后的同步方案是:

一、4个文件,a进程文件a1,a2,b进程b1,b2


二、a进程加锁文件a1,b进程同理


三、a进程建立a2文件,而后轮询查看b2文件是否存在(这里能够轮询,由于时间很短),不存在表明b进程还没建立,b进程同理


四、a进程轮询到b2文件存在了,表明b进程已经建立并可能在对b1文件加锁,此时删除文件b2,表明a进程已经加锁完毕,容许b进程读取a进程的锁,b进程同理


五、a进程监听文件a2,若是a2被删除,表明b进程进行到了步骤4已经对b1加锁完成,能够开始读取b1文件的锁(不能直接监听a2文件删除,也就是不能跳过34步,这也是最难想的一部分,若是那样可能此时b进程还没建立,和b进程建立完成并加锁完成的状态是同样的,就会让进程a误觉得进程b加锁完成),b进程同理




看代码
/MarsDaemon/LibMarsdaemon/jni/daemon_api21.c


其中a和b两个进程都会执行这块代码,对于a进程来讲,indicator_self_path对应a1,indicator_daemon_path对应b1,observer_self_path对应a2,observer_daemon_path对应b2,b进程同理

首先本身加锁,而后加入同步模块notify_and_waitfor


以上代码就是上述同步逻辑

同步完成后,两个进程同时监听对方的锁,一方挂掉另外一方马上能够监听到。这套方案在5.0+上能够作到互相监听对方死亡状态,由于都是阻塞方法,因此无耗电效率问题。

这里要说一下,也许你看完源码你会问为何步在5.0如下采用这种方案,由于这种方案是须要挂两个进程的,虽然与父子进程相比,在linux下都是两个进程两个pid,可是在android系统的设置里面,父子进程中的native进程是android系通通计不到的,因此不会列出来,不会让用户以为你为啥启这么多进程。因此从用户体验角度,5.0如下仍是采用上一篇博文采用的方案。



ok,监听成功,对方死亡就把对方拉起来,而后自杀,从新初始化。把方案替换进来以后发现5.0是ok的,360/cm with root也是ok的,可是在5.1又失效了。为何?


打印log咱们发现,5.1上监听死亡状态是ok的,可是却没法将对方拉起来。


好了,咱们要开始第二个重点了,那就是如何启动对方。


原先咱们的方案是用一个闹钟启动对方,可是在5.1上,闹钟在force close以后,闹钟一样会被废掉,以前的拉起方案行不通了,那么开始想如何将对方的进程拉起来,想了好久也没有相出什么别的方法,发送intent彷佛根本不执行,可是此时这种情形只能继续研究发的intent为何没有执行。


经过打印log,咱们才发现,force close的时候,系统杀应用对应进程的时候,速度很是快,在a进程监听到b进程被杀的时候,系统的死亡镰刀已经伸向了a进程,大概只有几十毫秒的时间!而发送一个intent咱们知道他是在本身进程中使用系统ActivityManagerService的一个代理类,经过一个binder将intent传给系统,虽然大部分操做是在ams中,可是咱们这边执行时间竟然也在百毫秒级的,难怪intent发不出去。


因此时间很重要!!!咱们要跟系统抢时间



那么如今的问题是怎样才能把intent发出去,查看源码,咱们能够看出intent的发送其实是ActivityManagerNative这个类,他持有一个binder,用来与系统的ActivityManagerService通讯,而后咱们发送intent的时候会初始化一个Parcel,经过binder transcate过去。



时间都消耗在了pacel的建立上。

那么咱们可不能够把这些耗时操做放在进程开始的时候就完成,等到监听到进程挂掉,直接调用。

可是那个binder咱们无法直接拿到,这里须要用到反射。

看代码:


代码com.marswin89.marsdaemon.strategy.DaemonStrategy22.java



 拿到ActivityManagerNative实例而后拿binder,也就是他的一个成员变量mRemote




  而后把Pacel初始化出来,这里注释掉的是我一行一行试验的,为了节省时间,在能达到目的的前提下,时间越少越好,因此参数能少传就少传。




而后在检测到对方进程死掉的时候,直接调用transcate方法。




ok,5.1上也能够实现双向守护。

可是很遗憾,6.0上又跪了。

而后通过排查,发现经过以上方式用binder transcate的方法没法启动一个service。那该怎么办呢?抱着试试看的内心尝试了broadcastreceiver,果真广播是能够的。一样的原理用ActivityManagerNative中的binder transcate一个pacel来启动一个broadcast拉起进程。

也是仿照系统源码来作的广播的Pacel

代码com.marswin89.marsdaemon.strategy.DaemonStrategy22.java



ok,6.0也搞定。

在系统force close时成功拉起对方,可是在360\clean master with root的强杀下仍然有必定的概率会跪。经过log能够看出来,在一键清理的时候是有成功拉起的,可是360\cm均会重复杀,被拉起来的新进程还没初始化完成,就又被杀掉了。



再次,时间很重要!!!咱们要跟360\cm抢时间


这里咱们在进程刚建立的时候使用多线程,将文件的同步加锁监听与启动另外一个进程同时进行,历来能节约100毫秒左右的时间



大功告成!


从5.0到6.0,在force close和360\cm with root的一键清理下均可以实现互相守护了。

相关文章
相关标签/搜索