我们在开发和运行维护app的过程中,应该碰到过例如应用被杀死或者后台服务被回收等,导致的消息无法及时推送传达,那像这种情况下我们很多候选都是发短信啊等等,增加成本的同时也不一定能达到预期的效果。而且一部分社交及时消息类的app这类候选基本都行不通。那这种情况下我们就必要应用保活的问题,当然我们也不可能是在用户明确杀掉应用的情况下,还保留前台页面,那是流氓软件做的事。我们这里做的是服务保活,比如说接收推送消息的服务的保活。
说到保活,那我们有很多的方式,比如广播,轮询的方式,耗费资源的同时又可能达不到我们需要的效果,往往很轻易就被收拾掉了,这时候我们只能沮丧的望着手机上被杀掉的app沉默,这时候我们来电话了,接完电话突然想到,为什么电话应用没见死过,还有很多的系统服务都没被杀死过,系统应用为什么就杀不死呢?
我们了解过后发现系统从init一号进程启动开始,系统启动了很多的保活子进程
这些init启动的子进程中 名字后面带d (daemon)的就是守护进程了
这些进程间相互监听相互唤醒以一种很巧妙的方式维持住了应用的持续存在,那是一个很复杂的关系,有兴趣的可以在查看android 系统init文件里面的源码
也可以去查相关的资料去了解更多,这个init可以做很多事,比如我们的xpose,很多人觉得这个东西啊,很牛逼,无所不能,其实也是在这个init上面做文章通过替换修改实现自己的目的。
那我们现在从里面简单的提取出来方案,进程守护来实现保活。那现在很多的进程守护都是在java层调调系统方法开启,配置一下android:process就行,那这种方案比较容易被针对,限制起来也容易。你可能用着用着,哪个版本一升就不好使了,所以我们在native里面做,用c写,系统调linux系统开进程,我们也调linux开进程。
两个进程之间怎么才能知道某个被杀掉了,系统是用的本地socket的机制来完成的,所以我们也来试一试
原理就是如图
我们可以看到在客户端close的时候服务端read()被调用了,这时候我们知道他被干掉了,我们就可以救他了。
系统init的关键代码
监听我们的应用进程
上面是我们在收到主进程的阻塞消息后去重启,很复杂的一套逻辑,但最简单的原理就是socket监听
我们自己写一个简单的监听
#include
#include
#include
#include "native-lib.h"
const char *PATH = "/data/data/com.zy.serviced/mysock";
int m_child;
const char *userId;
void child_do_work() {
//开始socket 服务端
if (child_create_channel()) {
child_listen_msg();
}
}
void child_listen_msg() {
fd_set rfds;
struct timeval timeout = {3, 0};
while (1) {
FD_ZERO(&rfds);
FD_SET(m_child, &rfds);
int r = select(m_child + 1, &rfds, NULL, NULL, &timeout);
LOGE("读取消息前 %d", r);
if (r > 0) {
char pkg[256] = {0};
if (FD_ISSET(m_child, &rfds)) {
//接收到阻塞消息
int result = read(m_child, pkg, sizeof(pkg));
//开启服务
execlp("am", "am", "startservice", "--user", userId,
"com.zy.serviced/com.zy.serviced.ProcessService", (char *) NULL);
break;
}
}
}
}
int child_create_channel() {
int listenFd = socket(AF_LOCAL, SOCK_STREAM, 0);
unlink(PATH);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
if (bind(listenFd, (const sockaddr *) &addr, sizeof(sockaddr_un)) < 0) {
LOGE("绑定错误");
return 0;
}
listen(listenFd, 5);
int connfd = 0;
while (1) {
if ((connfd = accept(listenFd, NULL, NULL)) < 0) {
if (errno == EINTR) {
continue;
} else {
LOGE("读取错误");
return 0;
}
}
m_child = connfd;
LOGE("apk父进程连接上了 %d", m_child);
break;
}
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_zy_serviced_Watcher_createWatcher(JNIEnv *env, jobject instance, jstring userId_) {
userId = env->GetStringUTFChars(userId_, 0);
//开双进程
pid_t pid = fork();
if (pid < 0) {
} else if (pid == 0) {
//子进程 守护
child_do_work();
} else if (pid > 0) {
//父进程
}
env->ReleaseStringUTFChars(userId_, userId);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_zy_serviced_Watcher_connectMonitor(JNIEnv *env, jobject instance) {
int socked;
struct sockaddr_un addr;
while (1) {
LOGE("客服端 进程开始连接");
socked = socket(AF_LOCAL, SOCK_STREAM, 0);
if (socked < 0) {
LOGE("连接失败");
return;
}
memset(&addr, 0, sizeof(sockaddr));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
if (connect(socked, (const sockaddr *) &addr, sizeof(sockaddr_un)) < 0) {
LOGE("连接失败");
close(socked);
sleep(1);
continue;
}
LOGE("连接成功");
break;
}
}
主要就是按照图上的步骤我们提供-创建socket->绑定客户端进程->消息监听->直到我们接到客户端服务销毁的阻塞消息-重启客户端服务
客户端服务在创建的时候就可以调native的方法创建监听服务端守护服务进程,杀死后被守护进程复活。
客户端应用进程要做的就是启动守护进程并传入进程号,开启监听方法就行
这里只是简单的用两个进程示例在这里耍,守护进程也是可以监听多个客户端进程的。那我们就可以守护多个客户端进程,多个产品。
那如果很不幸的是我们的守护进程被杀掉了怎么办呢,这时候我们是不是需要多个进程来守护呢,那多个被杀掉了呢。
那我们守护进程之间是不是可以互相监听呢,这种情况下是不是就可以实现我们类似那啥的全家桶套餐了。有兴趣的可以在系统源码中去学习
发现新世界