android 使用ndk(socket)进程保活
最近看了动脑学院的一个ndk进程守护的课程,特在此记录一下。由于个人之前对linux系统还是有所学习的,所以代码看起来基本没什么问题,在这里主要记录一下相关主要知识点、进程保活的主要实现原理。代码我进行了整理,可参考git地址:https://github.com/ChenSWD/ProcessKeepAliveDemo.git1.保活实现原理:在ndk层,fork()一个子进程,子进程作...
最近看了动脑学院的一个ndk进程守护的课程,特在此记录一下。由于个人之前对linux系统还是有所学习的,所以代码看起来基本没什么问题,在这里主要记录一下相关主要知识点、进程保活的主要实现原理。
代码我进行了整理,可参考git地址:https://github.com/ChenSWD/ProcessKeepAliveDemo.git
1.保活实现原理:在ndk层,fork()一个子进程,子进程作为一个socket的服务端,应用进程(父进程)作为socket的客户端,客户端与服务端使用socket进行连接,正常情况下客户端并不向服务端发送任何数据,read()函数一直阻塞,当客户端被kill之后,连接就会断开,read()函数被执行,然后使用execlp()函数执行am startservice命令重启服务。 .
根据个人对linux的系统的了解,所谓进程保活就是在进程被kill之后,能够重新开启服务,使用其它方式应该也是可以的,如使用信号量机制,进程是可以注册监听某些信号量和屏蔽某些信号的,当接收到自己被kill的信号的时候,可以重启服务。
2.还是贴出主要的代码:一个cpp文件,主要实现了socket的服务端和客户端
//头文件
#ifndef SERVICEALIVETEST_NATIVE_LIB_H
#define SERVICEALIVETEST_NATIVE_LIB_H
#include <jni.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <android/log.h>
#define LOG_TAG "KEEP_ALIVE"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
//child是服务端,开始准备,使用的是unix域套接字
void child_prepare_socket();
//创建服务端的socket,并等待客户端的连接
int child_create_socket();
//读取socket内容
void child_listen_socket();
#endif
//**下面是cpp文件**
//socket 实现进程保活,除非卸载
#include "native_lib.h"
const char *PATH = "/data/data/com.example.chen.servicealivetest/keep_alive.socket";
const char *userId;
int m_client_fd;
//该函数用来创建服务端socket,并实现监听
extern "C"
JNIEXPORT void JNICALL Java_com_example_chen_servicealivetest_KeepService_createWatcher(
JNIEnv *env,
jobject thiz, jstring userId_) {
userId = env->GetStringUTFChars(userId_, 0);
LOGI("userId %s", userId);
//fork 一个新的进程
pid_t pid = fork();
//新建进程失败
if (pid < 0) {
}
//子进程
else if (pid == 0) {
child_prepare_socket();
}
//父进程返回新建的子进程的pid
else if (pid > 0) {}
env->ReleaseStringUTFChars(userId_, userId);
}
void child_prepare_socket() {
if (child_create_socket()) {
child_listen_socket();
}
}
int child_create_socket() {
LOGI("开始创建服务端socket");
//AF_LOCAL:本地类型socket,SOCK_STREAM:面向tcp的流,参数0,代表自己选择网络协议
int sfd = 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(sfd, reinterpret_cast<const sockaddr *>(&addr), sizeof(sockaddr_un)) < 0) {
LOGE("服务端绑定socket出错");
return 0;
}
//5:最大连接数量,实际上由系统决定,
listen(sfd, 5);
int coonfd = 0;
//监听客户端的连接
while (1) {
//没有客户端请求到来,会阻塞直到一个请求的到来,返回客户端地址
if ((coonfd = accept(sfd, NULL, NULL)) < 0) {
if (errno == EINTR) {
continue;
} else {
LOGE("服务端 accept 出错");
return 0;
}
}
m_client_fd = coonfd;
LOGI("服务端连接成功,客户端的 fd %d", m_client_fd);
break;
}
return 1;
}
void child_listen_socket() {
LOGI("服务端监听客户端的消息");
fd_set fds;
struct timeval timeout = {10, 0};
while (1) {
//使用select I/O多路复用
//所有位置0
FD_ZERO(&fds);
//指定位上的值置为1,
FD_SET(m_client_fd, &fds);
//最长等待时间10s,timeout为null,表示一直阻塞直到收到客户端的消息
int r = select(m_client_fd + 1, &fds, NULL, NULL, &timeout);
LOGI("读取消息前 select %d", r);
if (r > 0) {
char pkg[256] = {0};
if (FD_ISSET(m_client_fd, &fds)) {
//如果阻塞结束,说明客户端进程被kill
int result = read(m_client_fd, pkg, sizeof(pkg));
LOGE("服务端开始重启服务");
//api<17
/*execlp("am", "am", "startservice", "-n"
"com.example.chen.servicealivetest/com.example.chen.servicealivetest.KeepService",
(char *) NULL);*/
//>=api17 不过好像两种 api 这种方式都能启动服务
//执行linux命令:这里是开启一个service
//am startservice -n com.example.chen.servicealivetest/com.example.chen.servicealivetest.KeepService
//execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名, 找到后便执行该文件,
//然后将第二个以后的参数当做该文件的argv[0]、argv[1]……, 最后一个参数必须用空指针(NULL)作结束。
execlp("am", "am", "startservice", "--user", userId, "-n"
"com.example.chen.servicealivetest/com.example.chen.servicealivetest.KeepService",
(char *) NULL);
break;
}
}
}
}
//该函数用来创建客户端socket,连接服务端
extern "C"
JNIEXPORT void JNICALL
Java_com_example_chen_servicealivetest_KeepService_connectServer(JNIEnv *env, jobject instance) {
int socketfd;
struct sockaddr_un addr;
while (1) {
socketfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (socketfd < 0) {
LOGE("客户端新建创建失败");
return;
}
memset(&addr, 0, sizeof(sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
if (connect(socketfd, reinterpret_cast<const sockaddr *>(&addr), sizeof(sockaddr_un))) {
LOGE("客户端连接失败");
close(socketfd);
sleep(1);
continue;
}
LOGE("客户端连接成功");
break;
}
}
3.最后稍微列出一些简单am命令以作备忘:am命令是通过命令行启动activity、service、broadcast(搬运自这篇博客:https://blog.csdn.net/electricity/article/details/6409354)
① 拨打一个电话:
am start -a android.intent.action.CALL -d tel:10086
这里-a表示动作,-d表述传入的数据,还有-t表示传入的类型。
②打开一个网页:
am start -a android.intent.action.VIEW -d http://www.baidu.com (这里-d表示传入的data)
③打开音乐播放器:
am start -a android.intent.action.MUSIC_PLAYER 或者
am start -n com.android.music/om.android.music.MusicBrowserActivity
④启动activity
$ am start -n {包(package)名}/{包名}.{活动(activity)名称}
如:启动浏览器
am start -n com.android.browser/com.android.browser.BrowserActivity
⑤启动一个服务:
am startservice <服务名称>
例如:am startservice -n com.android.music/com.android.music.MediaPlaybackService (这里-n表示组件)
或者 am startservice -a com.smz.myservice (这里-a表示动作,就是你在Androidmanifest里定义的)
⑥发送一个广播:
am broadcast -a <广播动作>
例如: am broadcast -a com.smz.mybroadcast
更多推荐
所有评论(0)