前言
目前市场上主流的项目应用app,在其进程被杀掉之后,还是可以继续运行在后台(保活);比如,微信,淘宝,钉钉,QQ等。类似耍流氓,保证应用进程不被杀死。当然优雅的说法:常驻进程。不过现在各个手机厂商都有白名单,将应用加入到白名单,可100%解决进程保活的需求。
差强人意的方法
网上给一些常见的方法:
提高优先级
这个办法对普通应用而言,
应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!让service.onStartCommand返回START_STICKY,START_STICKY是service被kill掉后自动重启
保活100%
通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的情况(或者用360手机卫士进行清理操作),
如果服务的onStartCommand返回START_STICKY,
在进程管理器中会发现过一小会后被杀死的进程的确又会出现在任务管理器中,貌似这是一个可行的办法。
但是如果在系统设置的App管理中选择强行关闭应用,这时候会发现即使onStartCommand返回了START_STICKY,应用还是没能重新启动起来!android:persistent="true"
网上还提出了设置这个属性的办法,通过实验发现即使设置了这个属性,应用程序被kill之后还是不能重新启动起来的!让应用成为系统应用
实验发现即使成为系统应用,被杀死之后也不能自动重新启动。
但是如果对一个系统应用设置了persistent="true",情况就不一样了。
实验表明对一个设置了persistent属性的系统应用,即使kill掉会立刻重启。
一个设置了persistent="true"的系统应用,
android中具有core service优先级,这种优先级的应用对系统的low memory killer是免疫的!设置闹钟,定时唤醒
这个效果是百分百的,但是不符合实际业务场景。
应用优先级
Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收
Android将进程分为5个等级,它们按优先级顺序由高到低依次是:
- 空进程 Empty process
- 可见进程 Visible process
- 服务进程 Service process
- 后台进程 Background process
- 前台进程 Foreground process
如何在程序杀死的清下重启进程-----SIGLE信号
- 思路
- 利用am命令,启动主进程的一个service
- SIGLE信号,通过SIGLE信号来判断程序是否被杀死
在Linux系统下,如果使用sigaction将信号SIGCHLD的sa_flags中的SA_NOCLDSTOP选项打开,
当子进程停止(STOP作业控制)时,
不产生此信号(即SIGCHLD)。不过,当子进程终止时,仍旧产生此信号(即SIGCHLD)。
僵尸
sigaction函数:
函数功能是:检查或修改与指定信号相关联的处理动作
sigaction(SIGCHLD, &sa, NULL);
wait()函数
函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
int status;
wait(&status);
查看Android进程
uid Android用户id 号
pid 当前的进程号
ppid 当前进程的父进程号
开始撸码
由于上面讲的内容都是在c++实现的,所以搞个jni工程
- 创建native方法
public native void watcher(String userId, int processId);
- 主进程创建一个service,用来在主进程被杀的时候,通过am命令进行重启主进程
public class KeepProcessService extends Service {
private static final String TAG = "BAO";
private int i = 0;
@Override
public void onCreate() {
super.onCreate();
ProcessWatcher watcher = new ProcessWatcher();
watcher.watcher(String.valueOf(Process.myUid()), Process.myPid());
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Log.i(TAG, "服务进程,运行中 i = "+i);
i++;
}
}, 0, 3000);
}
......省略其他代码
}
- C++的实现
const char *_user_id;
int _process_id;
//子进程变成僵尸进程会调用这个方法
void sig_handler(int sino) {
int status;
//阻塞式函数
LOGE("等待死亡信号");
wait(&status);
LOGE("创建进程");
create_child_process();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_jason_signal_process_ProcessWatcher_watcher(JNIEnv *env, jobject thiz, jstring user_id, jint process_id) {
_process_id = process_id;
_user_id = env->GetStringUTFChars(user_id, NULL);
//为了防止子进程被弄成僵尸进程
struct sigaction sa;
sa.sa_flags=0;
sa.sa_handler = sig_handler;
sigaction(SIGCHLD, &sa, NULL);
create_child_process();
}
void create_child_process() {
//创建一个子进程
pid_t pid = fork();
if(pid < 0) {
LOGE("创建子进程失败!");
} else if(pid > 0 && pid < getppid()) {
LOGE("这个是父进程!");
} else {
LOGE("创建子进程成功!");
LOGE("进程PID是%d", getpid());
LOGE("进程PPID是%d", getppid());
LOGE("创建的子进程ID:%d", pid);
create_process_monitor();
}
}
void *thread_fun_signal(void *data) {
//ppid 表示的是父进程号 pid表示当前进程号
pid_t pid;
while((pid = getppid()) != 1) {
sleep(2);
LOGE("循环 %d ",pid);
}
//当子进程的父进程号等于1 ,表示主进程被杀死了,子进程被init进程托管了
LOGE("重启父进程");
// 用am命令 启动KeepProcessService,来启动主进程
execlp("am", "am", "startservice", "--user", _user_id,
"com.jason.signal.process/com.jason.signal.process.KeepProcessService", (char*)NULL);
}
//创建一个线程
void create_process_monitor() {
pthread_t pt_t;
pthread_create(&pt_t, NULL, thread_fun_signal, NULL);
}
以上就是利用Android的linux内核的signal信号来,重启被杀掉的进程。
如何在程序杀死的清下重启进程-----socket方式 进程间通信
- 思路
- 创建一个子进程作为socket的的服务端
- 将主进程作为客户端,通过socket进行连接,当主进程被杀死之后,子进程服务端会受到一个主进程被杀的消息,这个时候通过am命令启动service重新启动主进程。
- 介绍函数
- int socket()函数
int socket(int protofamily, int type, int protocol);//返回sockfd
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
参数 | 说明 |
---|---|
protofamily | 即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型 |
type | 指定socket类型, 常用的socket类型SOCK_STREAM IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应 流协议,TCP传输协议、UDP传输协议、 STCP传输协议、TIPC传输协议 |
protocol | socket支持哪些协议,https://www.cnblogs.com/liyuanhong/articles/10591069.html |
- bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数介绍:
参数 | 说明 |
---|---|
sockfd | socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个socket |
addr | 一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同 |
addrlen | 对应的是地址的长度 |
开始撸码
- 创建native方法
public native void watch(String userId);
public native void connect();
- 同上方法创建service,在service的oncreate,进行socket的创建和连接
watcher.watch(String.valueOf(Process.myUid()));
watcher.connect();
- C++的实现:子进程创建socket的服务单,主进程进行连接
int m_child;
const char *userId;
const char *PATH = "/data/data/com.jason.socket.process/my.sock";
extern "C"
JNIEXPORT void JNICALL
Java_com_jason_socket_process_Watcher_watch(JNIEnv *env, jobject thiz, jstring user_id) {
userId = env->GetStringUTFChars(user_id, NULL);
create_child_process();
}
void create_child_process() {
pid_t pid = fork();
if(pid < 0) {
} else if (pid > 0) {
} else {
do_child_work();
}
}
void do_child_work() {
//1 在子进程建立socket服务,作为服务端,等待父进程连接
//2 读取消息来自父进程的消息:这边唯一的消息是父进程被杀掉
if(create_socket_server()) {
child_listen_msg();
}
}
int create_socket_server() {
//1 创建socket对象
int listenId = socket(AF_LOCAL, SOCK_STREAM, 0);
//2 断开之前的连接
unlink(PATH);
struct sockaddr_un addr;
//3 清空内存
memset(&addr, 0, sizeof(sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
int connfd = 0;
LOGE("绑定端口号");
if(bind(listenId, (const sockaddr *) &addr, sizeof(addr))<0) {
LOGE("绑定错误");
return 0;
}
//设置最大的连接数
listen(listenId, 5);
while (1) {
LOGE("子进程循环等待连接 %d ",m_child);
// 不断接受客户端请求的数据
// 等待 客户端连接 accept阻塞式函数
if ((connfd = accept(listenId, NULL, NULL)) < 0) {
if (errno == EINTR) {
continue;
} else{
LOGE("读取错误");
return 0;
}
}
//apk 进程连接上了
m_child = connfd;
LOGE("apk 父进程连接上了 %d ",m_child);
break;
}
LOGE("返回成功");
return 1;
}
void child_listen_msg() {
fd_set rfds;
while (1) {
// 清空端口号
FD_ZERO(&rfds);
// 设置新的端口号
FD_SET(m_child,&rfds);
// 设置超时时间
struct timeval timeout={3,0};
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));
// 读到内容的唯一方式 是客户端断开
LOGE("重启父进程 %d ",result);
LOGE("读到信息 %d userid %d ",result, userId);
execlp("am", "am", "startservice", "--user", userId,
"com.jason.socket.process/com.jason.socket.process.KeepProcessService", (char*)NULL);
break;
}
}
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_jason_socket_process_Watcher_connect(JNIEnv *env, jobject thiz) {
//主进程socket连接父进程
int sockfd;
struct sockaddr_un addr;
while (1) {
LOGE("客户端 父进程开始连接");
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
return;
}
memset(&addr, 0, sizeof(sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
if (connect(sockfd, (const sockaddr *) &addr, sizeof(addr)) < 0) {
LOGE("连接失败 休眠");
// 连接失败
close(sockfd);
sleep(1);
// 再来继续下一次尝试
continue;
}
// 连接成功
m_parent = sockfd;
LOGE("连接成功 父进程跳出循环");
break;
}
}
以上就是通过socket进行进程间通信,来实现进程保活。
结语
上面两种进程被杀重启的方式,只能实现支持大部分的手机,有部分厂商进行底层修改。这两种只是提供了两种思路方案。