NDK實現後臺服務保活
阿新 • • 發佈:2019-01-08
現在的國產手機基本都是不能保證的 隨隨便便一殺就沒了 復活不了;我的華為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一下 跑起來就知道怎麼回事了;記錄一下 雖然現在限制的很多 基本白費勁 但是也學習了一種方式; 好了 下班!