最近博主尝试了Android 5.0 以下版本的Native保活机制,感觉收获颇丰,在此写下一篇博客记录一下。
首先把整个保活流程通过图片的形式描述下:
首先是AndroidManifest 中注册的控件:
1 | <activity android:name=".MainActivity"> |
主要有三个控件:一个用于启动服务的Activity,一个在:persist 子进程中的运行任务的服务Service,以及一个在:wake_up 子进程中用于拉活Service的BroadcastReceiver
MainActivity过于简单在此就不做介绍了,PersistService的代码如下:
1 | public class PersistService extends Service { |
在PersistService的onCreate方法中,调用了Daemon类启动守护进程对PersistService类进行守护,并启动了新线程计数模拟工作。
Daemon类代码如下:
1 | private static final String DIR_NAME = "bin"; |
在run方法中,首先调用了Command工具类根据系统ABI获取在assets文件夹下相应的二进制文件,复制到特定地点后更改权限等待执行
然后启动子线程通过JNI调用native的start方法,传入包名、需要唤醒的类名以及二进制文件的路径:
1 | extern "C" |
在start方法中,我们首先获取了从Java层传过来的参数,然后通过pipe函数开启了两条匿名管道用于服务进程与其后代守护进程监听存活情况:
- pipe1:管道1,用于父进程监听子进程的存活情况,父进程关闭pipe1的写端,子进程关闭pipe1的读端
- pipe2:管道2,用于子进程监听父进程的存活情况,父进程关闭pipe2的读端,子进程关闭pipe2的写端
然后通过fork创建相应的子进程,在父进程中通过管道监听子进程的状况,在子进程中通过exec函数执行二进制文件重生为守护进程。
当父进程管道读取返回时,代表子进程已被销毁。此时,父进程通过回调Java层的onDeamonDead方法唤醒拉活进程并自杀等待重启:
1 | public static void onDaemonDead() { |
二进制文件代码:
1 | int main(int argc, char *argv[]) { |
在二进制文件中,首先检查了传入的参数,然后使该进程成为Linux的守护进程(没有关闭标准输入输出错误、关闭后执行am指令会出问题),并清理了以前剩下的僵尸守护进程,然后通过管道监听父进程的存活情况,当父进程死亡时,通过exec函数执行am指令发送广播唤醒拉活进程,并退出程序关闭守护进程。
负责拉活进程的广播接收器如下:
1 | public class WakeUpBroadcastReceiver extends BroadcastReceiver { |
下面是在Android 4.3 CoolPad手机上测试的效果:
首次启动:
在adb shell中通过force-stop关闭服务进程:
守护进程检测到Service进程被杀死,进行拉活处理:
此次守护进程的pid为21561,我们尝试通过kill命令杀死守护进程
Service进程检测到守护进程被杀死,进行拉活处理:
通过测试发现,无论是Service进程死亡还是守护进程死亡,另一方都能通过唤醒拉活进程将其救活,真正实现了Native层的双进程守护功能。
然而,在Android 5.0以上版本该方法并没有效果……目前博主正在进行进一步的学习研究。