那我们在开发和运行维护app的过程中,应该碰到过例如应用被杀死或者后台服务被回收等,导致的消息无法及时推送传达,那像这种情况下我们很多候选都是发短信啊等等,增加成本的同时也不一定能达到预期的效果。而且一部分社交及时消息类的app这类候选基本都行不通。那这种情况下我们就必要应用保活的问题,当然我们也不可能是在用户明确杀掉应用的情况下,还保留前台页面,那是流氓软件做的事。我们这里做的是服务保活,比如说接收推送消息的服务的保活。
说到保活,那我们有很多的方式,比如广播,轮询的方式,耗费资源的同时又可能达不到我们需要的效果,往往很轻易就被收拾掉了,这时候我们只能沮丧的望着手机上被杀掉的app沉默,这时候我们来电话了,接完电话突然想到,为什么电话应用没见死过,还有很多的系统服务都没被杀死过,系统应用为什么就杀不死呢?
我们了解过后发现系统从init一号进程启动开始,系统启动了很多的保活子进程
这些init启动的子进程中 名字后面带d (daemon)的就是守护进程了,当然截图只是一部分,
这些进程间相互监听相互唤醒以一种很巧妙的方式维持住了应用的持续存在,那是一个很复杂的关系,有兴趣的可以在init里面看源码也可以去查相关的资料去了解更多,这个init可以做很多事,比如我们的xpose,很多人觉得这个东西啊,很牛逼,无多不能,其实也是在这个init上面做文章通过替换修改实现自己的目的。
那我们现在从里面简单的提取出来方案,进程守护来实现保活。那现在很多的进程守护都是在java层调调系统方法开启,配置一下android:process就行,那这种方案比较容易被针对,限制起来也容易。你可能用着用着,哪个版本一升就不好使了,所以我们在native里面做,用c写,系统调linux系统开进程,我们也调linux开进程。
那我们定了双进程守护,我们两个进程之间怎么才能知道某个被杀掉了,总不能在进程被杀掉的时候喊一声兄弟,我要被干死了吧,这种情况下多半是来不及出声就被灭口了。那我们怎么才能知道了,有人用轮询,我打电话一直问兄弟你挂了没,还好我还活着,,突然有天没人接了,这回应该是挂了。我要救活他。这种方式有点low,也有点耗费资源。那到底要怎么办呢这时候我们就又打开init去找一找,系统居然是用的本地socket的机制来完成的。
我们可以看到在客户端close的时候我们read()被调用了,这时候我们知道他被干掉了,我们可以救他了。
哇,一不小心到零点了,不写小说了,直接上代码
这是native中的方法
#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的方法创建监听服务端守护服务进程,杀死后被守护进程复活。
测试中我们发现客户端服务被杀死后 轻松复活。
这里我们只是简单的用两个进程示例在这里耍,守护进程也是可以监听多个客户端进程的。那我们就可以守护多个客户端进程,
多个产品。
那如果很不幸的是我们的守护进程被杀掉了怎么办呢,这时候我们是不是需要多个进程来守护呢,那多个被杀掉了呢。
那我们守护进程之间是不是可以互相监听呢,这种情况下是不是就可以实现我们类似那啥的全家桶套餐了。沉思…
很多时候时候开发和用户之间都有不同的立场,那我们开发的时候可能需要做一些事,作为用户的时候可能自己都要吐槽。两者兼顾很重要