1. 程式人生 > >Android 使用NDK編寫 基於C層的守護程序

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

 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        flags = START_STICKY;
        return super.onStartCommand(intent, flags, startId); 
    }
如果在adb shell當中kill掉程序模擬應用被意外殺死的情況或者用360手機衛士進行清理操作(當然這裡是沒有ROOT的手機,據說360root後會在記憶體層面殺死程式,這個誰都擋不住的),如果服務的onStartCommand返回START_STICKY,在程序管理器中會發現被殺死的程序的確又會出現在工作管理員中。但是如果關閉程序的命令來自底層(比如系統命令adb shell am force-stop com.leon.test),這時候會發現即使onStartCommand返回了START_STICKY,應用還是沒能重新啟動起來!

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新年快樂。