1. 程式人生 > >NDK實現後臺服務保活

NDK實現後臺服務保活

現在的國產手機基本都是不能保證的 隨隨便便一殺就沒了 復活不了;我的華為P9就不行,其他的我覺得也夠嗆,但是使用公司自己得MTK5.0平臺, 那真是特嗎和病毒沒什麼區別。。。

我這是在騰訊課堂裡學習來得;動腦學院,挺好得一個學習android看視訊得地方;

1:  java方式守護

AndroidMainfast.xml 中定義process = “:remote”欄位;
手機廠商可以修改xml解析的方式,導致程序不能成功開啟;普及率低

輪詢;

2: ndk方式守護

  雙程序保活;

宿主程序 /   守護程序
客戶端    /   服務端
客戶端: socket()---> connect
() ---> write() <---> read() ----> close() 服務端: socket() --->bind() ---->listen() ---->accept()----->阻塞狀態,直到客戶端連線上----> read() <----> write() ----> read()這裡會收到客戶端close()時的訊息------>close(); linux socket 等待連線;與java不同這裡是分開的; 讀取訊息;分開的; socket跨程序 基於檔案 讀寫; 額外一個比較有趣的打電話: am start -a android .intent.action.CALL -d tel:10086

首先先來個service,記得註冊;

/**
 * 所守護的service
 */
public class ProcessService extends Service {

    private static final String TAG = ProcessService.class.getSimpleName();
    private int i;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public
int onStartCommand(Intent intent, int flags, int startId) { // return super.onStartCommand(intent, flags, startId); return START_STICKY; } @Override public void onCreate() { super.onCreate(); // 這裡是JNI; Watcher watcher = new Watcher(); watcher.createWatcher(String.valueOf(Process.myUid())); watcher.connectMonitor(); Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { Log.e(TAG, "service is opening ... " + i); i++; // 這裡只是在手機的一個目錄下一直寫東西;;;就不貼了; FileUtils.JsonWrite(Environment.getExternalStorageDirectory() + "//textDoubleProtect.txt", i + "\n"); } }, 0, 1000 * 3); } @Override public void onDestroy() { // 這裡直接殺掉是不走這裡的; Log.e(TAG, "service onDestroy"); super.onDestroy(); } }

watcher類;

/**
 * Created by bysd-2 on 2018/6/13.
 */
public class Watcher {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    public native void createWatcher(String userId); // jni

    public native void connectMonitor();

}

jni的程式碼;
這裡是標頭檔案;

//
// Created by bysd-2 on 2018/6/13.
//

#ifndef DOUBLEPROCESSPROTECT_NATIVE_LIB_H
#define DOUBLEPROCESSPROTECT_NATIVE_LIB_H

#endif //DOUBLEPROCESSPROTECT_NATIVE_LIB_H

// 所需標頭檔案
#include <sys/select.h>
#include <unistd.h>
#include <sys/socket.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
#include <android/log.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <linux/signal.h>
#include <android/log.h>
#define  LOG_TAG "tuch"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

// 方法在c裡面;
void child_do_work();
int child_create_channel();
void child_listen_msg();

//

#include <jni.h>
#include <string>

#include <unistd.h>

// child_create_channel()
#include "native-lib.h"

const char *PATH = "****************************這裡自己定義路徑  如  /data/data/com.example.demo/...";
int m_child;
const char *userId;

/**
 * 建立socket;
 */
void child_do_work() {
    // 守護程序為服務端;
    // open socket
    if (child_create_channel()) {
        child_listen_msg();
    }
}

/**
 * 建立服務端socket
 */
void child_listen_msg() {
    fd_set rfds;
    struct timeval timeout = {3, 0};

    while (1) {

        // 清空內容,快取,埠號等等;
        FD_ZERO(&rfds);
        // 賦值
        FD_SET(m_child, &rfds);
        // 設定客戶端最大數目+1;
        int r = select(m_child + 1, &rfds, NULL, NULL, &timeout);
//        LOGE("讀取訊息前  %d  ", r);

        if (r > 0) { // 選擇了客戶端
            // 緩衝區
            char pkg[256] = {0};

            // 保證所讀到的資訊是指定apk的客戶端;
            if (FD_ISSET(m_child, &rfds)) {
                int result = read(m_child, pkg, sizeof(pkg));
                LOGE("userId  %s  ",userId);
                // 開啟服務;
                execlp("am", "am", "startservice", "--user", userId,
                       "包名/service的全類名",
                       (char *) NULL);

                break;
            }
        }
    }
}

/**
 * 服務端讀取資訊;
 * 客戶端;
 */
int child_create_channel() {
    // socket 第一步;
    int listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
    //
    unlink(PATH);

    // bind
    struct sockaddr_un addr;
    // 清空記憶體,全部置0;
    memset(&addr, 0, sizeof(sockaddr_un));
    addr.sun_family = AF_LOCAL;
    //
    int connfd = 0;

    // 複製賦值;
    strcpy(addr.sun_path, PATH);
    //
    int a = bind(listenfd, (const sockaddr *) &addr, sizeof(sockaddr_un));
    if (a < 0) {
        // 繫結錯誤;
        LOGE("繫結錯誤!!!");
        return 0;
    }
    listen(listenfd, 5);//  監聽最大客服端數目: 5

    // 保證 宿主程序連線成功;
    while (1) {
        // 返回值為客戶端的地址;  阻塞式函式;
        connfd = accept(listenfd, NULL, NULL); // 執行完 errno會有值;
        if (connfd < 0) { // 連線失敗;
            if (errno == EINTR) {  // errno為linux內建成員變數;accept執行完會賦值;
                continue;
            } else {
                LOGE("讀取錯誤!!!");
                return 0;
            }
        }
        m_child = connfd;
        break;
    }
    return 1;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_htnova_doubleprocessprotect_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;
        }
        // 清空記憶體,全部置0;
        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;
    }
}

extern "C"
JNIEXPORT
void JNICALL
Java_com_htnova_doubleprocessprotect_Watcher_createWatcher(JNIEnv *env, jobject instance,
                                                           jstring userId_) {
    userId = env->GetStringUTFChars(userId_, 0);

    // TODO  建立socket;
    // linux 開啟雙程序
    pid_t pid = fork();
    if (pid < 0) {
        // 失敗;
    }
    else if (pid == 0) {
        // 子程序;  守護程序
        child_do_work();
    } else if (pid > 0) {
        // 父程序;
    }
    env->ReleaseStringUTFChars(userId_, userId);
}

到這裡就可以了,c裡面的東西可能比較難懂,但主要看service裡面的東西 基本能知道大概;不懂得話copy一下 跑起來就知道怎麼回事了;記錄一下 雖然現在限制的很多 基本白費勁 但是也學習了一種方式; 好了 下班!