Android 使用NDK編寫 基於C層的守護程序
做過android開發的人應該都知道GC會在資源不夠用的時候會無情的回收掉我們寫的程序,但是有時候我們需要我們的程序常駐後臺。這該怎麼辦呢?
首先說下我試過的還有網上看到過的方法吧!
1.提高優先順序
<receiver android:name="com.leon.test" android:enabled="true" > <intent-filter android:priority="10000" > <action android:name="android.intent.action.LOCALE_CHANGED" /> </intent-filter> </receiver>
這個辦法只是降低了應用被殺死的概率,但是如果真的被系統回收了,我們也只能對著系統呵呵噠!
擴充套件下,有人也寫過雙service守護程序,service1發現service2死了,他就復活service2。然後service2發現service1死了,他就復活service1。
這樣寫雖然在一部份情況下還是能堅持一會的。但是遇到一些清理軟體,service1和service2都會瞬間死亡。守護功能當然也談不上。
2.重寫service.onStartCommand返回START_STICKY
如果在adb shell當中kill掉程序模擬應用被意外殺死的情況或者用360手機衛士進行清理操作(當然這裡是沒有ROOT的手機,據說360root後會在記憶體層面殺死程式,這個誰都擋不住的),如果服務的onStartCommand返回START_STICKY,在程序管理器中會發現被殺死的程序的確又會出現在工作管理員中。但是如果關閉程序的命令來自底層(比如系統命令adb shell am force-stop com.leon.test),這時候會發現即使onStartCommand返回了START_STICKY,應用還是沒能重新啟動起來!@Override public int onStartCommand(Intent intent, int flags, int startId) { flags = START_STICKY; return super.onStartCommand(intent, flags, startId); }
3.讓應用成為系統應用
實驗發現即使成為系統應用(在燒rom的時候吧直接把APK扔到SYSTEM內),被殺死之後也不能自動重新啟動。但是如果對一個系統應用設定了persistent="true",情況就不一樣了。實驗表明對一個設定了persistent屬性的系統應用,即使kill掉會立刻重啟。一個設定了persistent="true"的系統應用,在android中具有core service優先順序,這種優先順序的應用對系統的low memory killer是免疫的!
看了上面大家也有應該發現了一問題。只有越接近Android核心的應用才能保證在被意外殺死之後做到立刻復活。那麼該怎麼辦呢?這裡就來說一說雙程序守護。網上也有人提到過雙程序守護,這裡給大家安利一個GitHub上面的守護程序(傳送門:https://github.com/Coolerfall/AndroidAppDaemon)但是由於使用的方法並不符合我的需求,所以並沒有使用。
雙守護程序的原理請參考1的擴充套件,簡而言之就是互相監視,一個死了另一個就復活他。
在寫之前希望大家去了解兩個命令
fork()和 execlp
OK。那開始貼程式碼了(注意這個是C++程式碼,建工程的時候請使用cpp字尾)。
#ifndef _PROCESS_H
#define _PROCESS_H
#include <jni.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/types.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/system_properties.h>
#define LOG_TAG "Native"
#define LOGE(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
static bool DEBUG = true;
/**
* 功能:對父子程序的一個抽象
* @author LeonWang
* @date 2015-12-28
*/
class ProcessBase {
public:
ProcessBase();
/**
* 父子程序要做的工作不相同,留出一個抽象介面由父子程序
* 自己去實現.
*/
virtual void do_work() = 0;
/**
* 程序可以根據需要建立子程序,如果不需要建立子程序,可以給
* 此介面一個空實現即可.
*/
virtual bool create_child() = 0;
/**
* 捕捉子程序死亡的訊號,如果沒有子程序此方法可以給一個空實現.
*/
virtual void catch_child_dead_signal() = 0;
/**
* 在子程序死亡之後做任意事情.
*/
virtual void on_child_end() = 0;
virtual ~ProcessBase();
};
/**
* 功能:父程序的實現
* @author LeonWang
* @date 2015-12-28
*/
class Parent: public ProcessBase {
public:
Parent(JNIEnv* env, jobject jobj);
virtual bool create_child();
virtual void do_work();
virtual void catch_child_dead_signal();
virtual void on_child_end();
virtual ~Parent();
bool create_channel();
/**
* 獲取父程序的JNIEnv
*/
JNIEnv *get_jni_env() const;
/**
* 獲取Java層的物件
*/
jobject get_jobj() const;
};
/**
* 子程序的實現
* @author LeonWang
* @date 2015-12-28
*/
class Child: public ProcessBase {
public:
Child();
virtual ~Child();
virtual void do_work();
virtual bool create_child();
virtual void catch_child_dead_signal();
virtual void on_child_end();
private:
/**
* 處理父程序死亡事件
*/
void handle_parent_die();
/**
* 重新啟動父程序.
*/
void restart_parent();
/**
* 執行緒函式,用來檢測父程序是否掛掉
*/
void* parent_monitor();
void start_parent_monitor();
/**
* 這個聯合體的作用是幫助將類的成員函式做為執行緒函式使用
*/
union {
void* (*thread_rtn)(void*);
void* (Child::*member_rtn)();
} RTN_MAP;
};
extern ProcessBase *g_process;
extern const char* g_objname;
extern const char* g_type;
extern JNIEnv* g_env;
int get_version();
ProcessBase::ProcessBase() {
}
ProcessBase::~ProcessBase() {
}
Parent::Parent(JNIEnv *env, jobject jobj) {
if (DEBUG) {
LOGE("<<new parent instance>>");
}
}
Parent::~Parent() {
if (DEBUG) {
LOGE("<<Parent::~Parent()>>");
}
g_process = NULL;
}
void Parent::do_work() {
}
/**
* 子程序死亡會發出SIGCHLD訊號,通過捕捉此訊號父程序可以
* 知道子程序已經死亡,此函式即為SIGCHLD訊號的處理函式.
*/
static void sig_handler(int signo) {
pid_t pid;
int status;
//呼叫wait等待子程序死亡時發出的SIGCHLD
//訊號以給子程序收屍,防止它變成殭屍程序
pid = wait(&status);
if (DEBUG) {
LOGE("<<sig_handler>>");
}
if (g_process != NULL) {
g_process->on_child_end();
}
}
void Parent::catch_child_dead_signal() {
if (DEBUG) {
LOGE("<<process %d install child dead signal detector!>>", getpid());
}
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sig_handler;
sigaction(SIGCHLD, &sa, NULL);
}
void Parent::on_child_end() {
if (DEBUG) {
LOGE("<<on_child_end:create a new child process>>");
}
create_child();
}
bool Parent::create_child() {
pid_t pid;
if ((pid = fork()) < 0) {
return false;
} else if (pid == 0) //子程序
{
if (DEBUG) {
LOGE("<<In child process,pid=%d>>", getpid());
}
Child child;
ProcessBase& ref_child = child;
ref_child.do_work();
} else if (pid > 0) //父程序
{
if (DEBUG) {
LOGE("<<In parent process,pid=%d>>", getpid());
}
}
return true;
}
bool Child::create_child() {
//子程序不需要再去建立子程序,此函式留空
return false;
}
Child::Child() {
RTN_MAP.member_rtn = &Child::parent_monitor;
}
Child::~Child() {
}
void Child::catch_child_dead_signal() {
//子程序不需要捕捉SIGCHLD訊號
return;
}
void Child::on_child_end() {
//子程序不需要處理
return;
}
void Child::handle_parent_die() {
//子程序成為了孤兒程序,等待被Init程序收養後在進行後續處理
while (getppid() != 1) {
usleep(500); //休眠0.5ms
}
//重啟父程序服務
if (DEBUG) {
LOGE("<<parent died,restart now>>");
}
restart_parent();
}
void Child::restart_parent() {
if (DEBUG) {
LOGE("<<restart_parent enter>>");
}
/**
* TODO 重啟父程序,通過am啟動Java空間的任一元件(service或者activity等)即可讓應用重新啟動
*/
if (strcmp(g_type, "Activity") == 0) {
if (DEBUG) {
LOGE("<<restart_Activity>>");
}
execlp("am", "am", "start","-e","daemon","triger","--user", "0", "-n", g_objname, "-a",
"android.intent.action.VIEW", "-d", "", (char *) NULL);
} else if (strcmp(g_type, "Service") == 0) {
//在api17之後AM命令有些不同這裡需要寫相容。獲取版本號的方法已經寫在了下面。
<pre name="code" class="cpp"> int g_version=get_version();
if (g_version >= 17 || g_version == 0) {
if (DEBUG) {
LOGE("<<restart_service more than 17>>");
}
int ret = execlp("am", "am", "startservice","-e","daemon","triger","--user", "0", "-n",g_objname, (char *) NULL);
} else {
if (DEBUG) {
LOGE("<<restart_service enter bleow 17>>");
}
execlp("am", "am", "startservice","-e","daemon","triger","-n", g_objname, (char *) NULL);
}
}
}
void* Child::parent_monitor() {
handle_parent_die();
}
void Child::start_parent_monitor() {
pthread_t tid;
pthread_create(&tid, NULL, RTN_MAP.thread_rtn, this);
pthread_join(tid, NULL);
}
void Child::do_work() {
start_parent_monitor(); //啟動監視執行緒
if (DEBUG) {
LOGE("<<start_parent_monitor>>");
}
}
char* jstringToString(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes","(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize array_lenth = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (array_lenth > 0) {
rtn = (char*) malloc(array_lenth + 1);
memcpy(rtn, ba, array_lenth);
rtn[array_lenth] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
int get_version()
{
char value[8] = "";
__system_property_get("ro.build.version.sdk", value);
return atoi(value);
}
#endif
守護程序的主程式就是這樣了。
我封裝好了一個可以直接使用的Jar包,有需要的可以自行下載。
地址:http://download.csdn.net/detail/kakathya/9385869
過fork分支一個子程序,子程序發現父程序死亡就重啟服務或者activity。
後面有時間再寫一個NDK的基礎教程吧。
祝大家2016新年快樂。