最近看了动脑学院的一个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

Logo

更多推荐