1. 程式人生 > >Android實現雙程序守護

Android實現雙程序守護

如何保證Service不被Kill

(1)onStartCommand方法,返回START_STICKY

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    flags = START_STICKY;  
    return super.onStartCommand(intent, flags, startId);  
}  

手動返回START_STICKY,親測當service因記憶體不足被kill,當記憶體又有的時候,service又被重新建立,比較不錯,但是不能保證任何情況下都被重建,比如程序被幹掉了。

正常執行:
這裡寫圖片描述

殺程序:
這裡寫圖片描述

程序自動重啟:
這裡寫圖片描述

重啟成功:
這裡寫圖片描述

(2)Application加上Persistent屬性
看Android文件知道,當程序長期不活動,或系統需要資源時,會自動清理門戶,殺死一些Service,和不可見的Activity等所在的程序。但是如果某個程序不想被殺死(如資料快取程序,或狀態監控程序,或遠端服務程序),可以這麼做:

<application  
    android:allowBackup="true"  
    android:icon="@drawable/ic_launcher"  
    android:label="@string
/app_name"
android:persistent="true" //設定Persistent android:theme="@style/AppTheme" > </application>

據說這個屬性不能亂設定,不過設定後,的確發現優先順序提高不少,或許是相當於系統級的程序,但是還是無法保證存活。

(3)提升service優先順序
在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority = “1000”這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時適用於廣播。

<service
android:name="com.hx.doubleprocess.MyService" android:enabled="true" >
<intent-filter android:priority="1000" > <action android:name="com.hx.myservice" /> </intent-filter> </service>

目前看來,priority這個屬性貌似只適用於broadcast,對於Service來說可能無效。

(4)提升service程序優先順序
Android中的程序是託管的,當系統程序空間緊張的時候,會依照優先順序自動進行程序的回收。Android將程序分為5個等級,它們按優先順序順序由高到低依次是:

  • 前臺程序 Foreground process
  • 可見程序 Visible process
  • 服務程序 Service process
  • 後臺程序 Background process
  • 空程序 Empty process

當service執行在低記憶體的環境時,將會kill掉一些存在的程序。因此程序的優先順序將會很重要,可以使用startForeground 將service放到前臺狀態。這樣在低記憶體時被kill的機率會低一些。

@Override  
    public void onCreate() {  
        super.onCreate();
        Intent notificationIntent = new Intent(this, MainActivity.class);  
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
        Notification notification = new Notification.Builder(this)
            .setSmallIcon(R.drawable.ic_launcher)
            .setWhen(System.currentTimeMillis())
            .setTicker("有通知到來") 
            .setContentTitle("這是通知的標題") 
            .setContentText("這是通知的內容")
            .setOngoing(true)
            .setContentIntent(pendingIntent)
            .build();
        /*使用startForeground,如果id為0,那麼notification將不會顯示*/
        startForeground(1, notification);
    }

注意在onDestroy裡還需要stopForeground(true)。
如果在極度極度低記憶體的壓力下,該service還是會被kill掉,並且不一定會restart。

(5)onDestroy方法裡重啟service
service +broadcast 方式,就是當service走ondestory的時候,傳送一個自定義的廣播,當收到廣播的時候,重新啟動service;

<receiver android:name="com.hx.doubleprocess.BootReceiver" >  
    <intent-filter>  
        <action android:name="android.intent.action.BOOT_COMPLETED" />  
        <action android:name="android.intent.action.USER_PRESENT" />  
        <action android:name="com.hx.destroy"/> //這個就是自定義的action  
    </intent-filter>  
</receiver> 

在onDestroy時:

@Override  
public void onDestroy() {  
    stopForeground(true);  
    Intent intent = new Intent("com.hx.destroy");  
    sendBroadcast(intent);  
    super.onDestroy();  
} 

在BootReceiver裡:

public class BootReceiver extends BroadcastReceiver {    
    @Override  
    public void onReceive(Context context, Intent intent) {  
        if (intent.getAction().equals("com.hx.destroy")) {  
            //在這裡寫重新啟動service的相關操作  
            startUploadService(context);  
        }    
    }    
}  

也可以直接在onDestroy()裡startService:

@Override  
public void onDestroy() {    
     Intent sevice = new Intent(this, MyService.class);  
     this.startService(sevice);    
     super.onDestroy();  
}  

當使用類似360管家等第三方應用或是在setting裡-應用-強制停止時,APP程序可能就直接被幹掉了,onDestroy方法都進不來,所以還是無法保證。

(6)監聽系統廣播判斷Service狀態
通過系統的一些廣播,比如:手機重啟、介面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的Service是否還存活,別忘記加許可權啊。

<receiver android:name="com.hx.doubleprocess.BootReceiver" >  
    <intent-filter>  
        <action android:name="android.intent.action.BOOT_COMPLETED" />  
        <action android:name="android.intent.action.USER_PRESENT" />  
        <action android:name="android.intent.action.PACKAGE_RESTARTED" />  
        <action android:name="com.hx.destroy" />
    </intent-filter>  
</receiver> 

BroadcastReceiver中:

@Override  
public void onReceive(Context context, Intent intent) {  
    if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {  
        System.out.println("手機開機了....");  
        startUploadService(context);  
    }  
    if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {  
        startUploadService(context);  
    }  
}  

這也能算是一種措施,不過感覺監聽多了會導致Service很混亂,帶來諸多不便。

(7)將APK安裝到/system/app,變身系統級應用
這種方式適合除錯來用,並不算是一種解決辦法,不推薦使用,因為你的APP是給使用者使用的。
需要root許可權的手機,將APK檔案拷貝到/system/app目錄下,重啟手機。設定-應用程式管理,可以看到我們的APP已經無法解除安裝了,只能停用。系統級的APP,這樣一些第三方的管家軟體,就無法殺掉我們,除非自己把APP停用掉,或是強制停止。
即使成為系統應用,被殺死之後也不能自動重新啟動。但是如果對一個系統應用設定了persistent=”true”,情況就不一樣了。實驗表明對一個設定了persistent屬性的系統應用,即使kill掉會立刻重啟。一個設定了persistent=”true”的系統應用,在android中具有core service優先順序,這種優先順序的應用對系統的low memory killer是免疫的!

雙程序守護

如果從程序管理器觀察會發現新浪微博、支付寶和QQ等都有兩個以上相關程序,其中一個就是守護程序,由此可以猜到這些商業級的軟體都採用了雙程序守護的辦法。
這裡寫圖片描述

什麼是雙程序守護呢?顧名思義就是兩個程序互相監視對方,發現對方掛掉就立刻重啟!不知道應該把這樣的一對程序是叫做相依為命呢還是難兄難弟好呢,但總之,雙程序守護的確是一個解決問題的辦法!相信說到這裡,很多人已經迫切的想知道如何實現雙程序守護了。這篇文章就介紹一個用NDK來實現雙程序保護的辦法,不過首先說明一點,下面要介紹的方法中,會損失不少的效率,反應到現實中就是會使手機的耗電量變大!但是這篇文章僅僅是拋磚引玉,相信看完之後會有更多高人指點出更妙的實現辦法。

需要了解些什麼?
這篇文章中實現雙程序保護的方法基本上是純的NDK開發,或者說全部是用C++來實現的,需要雙程序保護的程式,只需要在程式的任何地方呼叫一下JAVA介面即可。下面幾個知識點是需要了解的:

  • 1.linux中多程序;
  • 2.unix domain套接字實現跨程序通訊;
  • 3.linux的訊號處理;
  • 4.exec函式族的用法;

其實這些東西本身並不是多複雜的技術,只是我們把他們組合起來實現了一個雙程序守護而已,沒有想象中那麼神祕!在正式貼出程式碼之前,先來說說幾個實現雙程序守護時的關鍵點:

  • 1.父程序如何監視到子程序(監視程序)的死亡?
    很簡單,在linux中,子程序被終止時,會向父程序傳送SIG_CHLD訊號,於是我們可以安裝訊號處理函式,並在此訊號處理函式中重新啟動建立監視程序;
  • 2.子程序(監視程序)如何監視到父程序死亡?
    當父程序死亡以後,子程序就成為了孤兒程序由Init程序領養,於是我們可以在一個迴圈中讀取子程序的父程序PID,當變為1就說明其父程序已經死亡,於是可以重啟父程序。這裡因為採用了迴圈,所以就引出了之前提到的耗電量的問題。
  • 3.父子程序間的通訊
    有一種辦法是父子程序間建立通訊通道,然後通過監視此通道來感知對方的存在,這樣不會存在之前提到的耗電量的問題,在本文的實現中,為了簡單,還是採用了輪詢父程序PID的辦法,但是還是留出了父子程序的通訊通道,雖然暫時沒有用到,但可備不時之需!

OK, 下面就貼上程式碼!首先是Java部分,這一部分太過簡單,只是一個類,提供了給外部呼叫的API介面用於建立守護程序,所有的實現都通過native方法在C++中完成!

/**
 * 監視器類,構造時將會在Native建立子程序來監視當前程序
 */

public class Watcher {

    public void createAppMonitor(String userId) {
        if (!createWatcher(userId)) {
            MainActivity.showlog("<<Monitor created failed>>");
        } else {
            MainActivity.showlog("<<Monitor created success>>");
        }
        if (!connectToMonitor()) {
            MainActivity.showlog("<<Connect To Monitor failed>>");
        } else {
            MainActivity.showlog("<<Connect To Monitor success>>");
        }   
    }

    /**
     * Native方法,建立一個監視子程序.
     *
     * @param userId
     *            當前程序的使用者ID,子程序重啟當前程序時需要用到當前程序的使用者ID.
     * @return 如果子程序建立成功返回true,否則返回false
     */
    private native boolean createWatcher(String userId);

    /**
     * Native方法,讓當前程序連線到監視程序.
     *
     * @return 連線成功返回true,否則返回false
     */
    private native boolean connectToMonitor();

    /**
     * Native方法,向監視程序傳送任意資訊
     *
     * @param 發給monitor的資訊
     * @return 實際傳送的位元組
     */
    private native int sendMsgToMonitor(String msg);

    static {
        System.loadLibrary("monitor");
    }
}

只需要關心createAppMonitor這個對外介面就可以了,它要求傳入一個當前程序的使用者ID,然後會呼叫createWatcher本地方法來建立守護程序。還有兩個方法connectToMonitor用於建立和監視程序的socket通道,sendMsgToMonitor用於通過socket向子程序傳送資料。由於暫時不需要和子程序進行資料互動,所以這兩個方法就沒有新增對外的JAVA介面,但是要新增簡直是輕而易舉的事!

JAVA只是個殼,內部的實現還得是C++,為了讓程式更加的面向物件,在實現native時,我們用一個ProcessBase基類來對父子程序進行一個抽象,把父子程序都會有的行為抽象出來,而父子程序可以根據需要用自己的方式去實現其中的介面,先來看看這個抽象了父子程序共同行為的ProcessBase基類:

#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 "constants.h"

#define LOG_TAG "Native"

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

/**
 * 功能:對父子程序的一個抽象
 * @author wangqiang
 * @date 2014-03-14
 */
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;

    /**
     * 建立父子程序通訊通道.
     */
    bool create_channel();

    /**
     * 給程序設定通訊通道.
     * @param channel_fd 通道的檔案描述
     */
    void set_channel(int channel_fd);

    /**
     * 向通道中寫入資料.
     * @param data 寫入通道的資料
     * @param len  寫入的位元組數
     * @return 實際寫入通道的位元組數
     */
    int write_to_channel(void* data, int len);

    /**
     * 從通道中讀資料.
     * @param data 儲存從通道中讀入的資料
     * @param len  從通道中讀入的位元組數
     * @return 實際讀到的位元組數
     */
    int read_from_channel(void* data, int len);

    /**
     * 獲取通道對應的檔案描述符
     */
    int get_channel() const;

    virtual ~ProcessBase();

protected:

    int m_channel;
};

只是很簡單的一個類,相信看看註釋就知道是什麼意思了,比如父子程序可能都需要捕獲他的子孫死亡的訊號,於是給一個catch_child_dead_signal函式,如果對子程序的死活不感興趣,可以給個空實現,忽略掉就可以了。由於用了純虛擬函式,所以ProcessBase是一個抽象類,也就是說它不能有自己的例項,只是用來繼承的,它的子孫後代可以用不同的方式實現它裡面的介面從而表現出不一樣的行為,這裡父程序和子程序的行為就是有區別的,下面就先為諸君奉上父程序的實現:

/**
 * 功能:父程序的實現
 * @author wangqiang
 * @date 2014-03-14
 */
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;

private:

    JNIEnv *m_env;

    jobject m_jobj;

};

以上是定義部分,其實JNIEnv和jobject基本上沒用到,完全可以給剃掉的,大家就當這兩個屬性不存在就是了!實現部分如下:

#include "process.h"

extern ProcessBase *g_process;

extern const char* g_userId;

extern JNIEnv* g_env;

//子程序有許可權訪問父程序的私有目錄,在此建立跨程序通訊的套接字檔案
static const char* PATH = "/data/data/com.hx.doubleprocess/my.sock";

//服務名稱
static const char* SERVICE_NAME = "com.hx.doubleprocess/com.hx.doubleprocess.MyService";

bool ProcessBase::create_channel() {
}

int ProcessBase::write_to_channel(void* data, int len) {
    return write(m_channel, data, len);
}

int ProcessBase::read_from_channel(void* data, int len) {
    return read(m_channel, data, len);
}

int ProcessBase::get_channel() const {
    return m_channel;
}

void ProcessBase::set_channel(int channel_fd) {
    m_channel = channel_fd;
}

ProcessBase::ProcessBase() {

}

ProcessBase::~ProcessBase() {
    close(m_channel);
}

Parent::Parent(JNIEnv *env, jobject jobj) :
        m_env(env) {
    LOGE("<<new parent instance>>");

    m_jobj = env->NewGlobalRef(jobj);
}

Parent::~Parent() {
    LOGE("<<Parent::~Parent()>>");

    g_process = NULL;
}

void Parent::do_work() {
}

JNIEnv* Parent::get_jni_env() const {
    return m_env;
}

jobject Parent::get_jobj() const {
    return m_jobj;
}

/**
 * 父程序建立通道,這裡其實是建立一個客戶端並嘗試
 * 連線伺服器(子程序)
 */
bool Parent::create_channel() {
    int sockfd;

    sockaddr_un addr;

    while (1) {
        sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);

        if (sockfd < 0) {
            LOGE("<<Parent create channel failed>>");

            return false;
        }

        memset(&addr, 0, sizeof(addr));

        addr.sun_family = AF_LOCAL;

        strcpy(addr.sun_path, PATH);

        if (connect(sockfd, (sockaddr*) &addr, sizeof(addr)) < 0) {
            close(sockfd);

            sleep(1);

            continue;
        }

        set_channel(sockfd);

        LOGE("<<parent channel fd %d>>", m_channel);

        break;
    }

    return true;
}

/**
 * 子程序死亡會發出SIGCHLD訊號,通過捕捉此訊號父程序可以
 * 知道子程序已經死亡,此函式即為SIGCHLD訊號的處理函式.
 */
static void sig_handler(int signo) {
    pid_t pid;

    int status;

//呼叫wait等待子程序死亡時發出的SIGCHLD
//訊號以給子程序收屍,防止它變成殭屍程序
    pid = wait(&status);

    if (g_process != NULL) {
        g_process->on_child_end();
    }
}

void Parent::catch_child_dead_signal() {
    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() {
    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) //子程序
    {
        LOGE("<<In child process,pid=%d>>", getpid());

        Child child;

        ProcessBase& ref_child = child;

        ref_child.do_work();
    } else if (pid > 0)  //父程序
    {
        LOGE("<<In parent process,pid=%d>>", getpid());
    }

    return true;
}

這裡先要說明一下三個全域性變數:

  • g_process是父程序的指標;
  • g_userId是父程序使用者ID,由Java側傳遞過來,我們需要把它用全域性變數儲存起來,因為子程序在重啟父程序的時候需要用到使用者ID,否則會有問題,當然這裡也得益於子程序能夠繼承父程序的全域性變數這個事實!
  • g_env是JNIEnv的指標,把這個變數也作為一個全域性變數,是保留給子程序用的;

父程序在create_child中用fork建立了子程序,其實就是一個fork呼叫,然後父程序什麼都不做,子程序建立一個Child物件並呼叫其do_work開始做自己該做的事!

父程序實現了catch_child_dead_signal,在其中安裝了SIG_CHLD訊號處理函式,因為他很愛他的兒子,時刻關心著他。而在訊號處理函式sig_handler中,我們留意到了wait呼叫,這是為了防止子程序死了以後變成殭屍程序,由於我們已經知道父程序最多隻會建立一個子監視程序,所以wait就足夠了,不需要waitpid函式親自出馬!而訊號處理函式很簡單,重新呼叫一下on_child_end,在其中再次create_child和他親愛的夫人在make一個小baby就可以了!

最後要說說create_channel這個函式,他用來建立和子程序的socket通道,這個程式設計模型對於有網路程式設計經驗的人來說顯得非常親切和熟悉,他遵循標準的網路程式設計客戶端步驟:建立socket,connect,之後收發資料就OK了,只是這裡的協議用的是AF_LOCAL,表明我們是要進行跨程序通訊。由於域套接字用的不是IP地址,而是通過指定的一個檔案來和目標程序通訊,父子程序都需要這個檔案,所以這個檔案的位置指定在哪裡也需要注意一下:在一個沒有root過的手機上,幾乎所有的檔案都是沒有寫入許可權的,但是很幸運的是linux的子程序共享父程序的目錄,所以把這個位置指定到/data/data/下應用的私有目錄就可以做到讓父子程序都能訪問這個檔案了!

接下來是子程序的實現了,它的定義如下:

/**
 * 子程序的實現
 * @author wangqiang
 * @date 2014-03-14
 */
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();

    bool create_channel();

private:

    /**
     * 處理父程序死亡事件
     */
    void handle_parent_die();

    /**
     * 偵聽父程序傳送的訊息
     */
    void listen_msg();

    /**
     * 重新啟動父程序.
     */
    void restart_parent();

    /**
     * 處理來自父程序的訊息
     */
    void handle_msg(const char* msg);

    /**
     * 執行緒函式,用來檢測父程序是否掛掉
     */
    void* parent_monitor();

    void start_parent_monitor();

    /**
     * 這個聯合體的作用是幫助將類的成員函式做為執行緒函式使用
     */
    union {
        void* (*thread_rtn)(void*);

        void* (Child::*member_rtn)();
    } RTN_MAP;
};
#endif 

注意到裡面有個union,這個聯合體的作用是為了輔助把一個類的成員函式作為執行緒函式來傳遞給pthread_create,很多時候我們都希望執行緒能夠像自己人一樣訪問類的私有成員,就像一個成員函式那樣,用friend雖然可以做到這一點,但總感覺不夠優美,由於成員函式隱含的this指標,使我們完全可以將一個成員函式作為執行緒函式來用。只是由於編譯器堵死了函式指標的型別轉換,所以這裡就只好用一個結構體。

廢話不多說,看看子程序的實現:

bool Child::create_child() {
//子程序不需要再去建立子程序,此函式留空
    return false;
}

Child::Child() {
    RTN_MAP.member_rtn = &Child::parent_monitor;
}

Child::~Child() {
    LOGE("<<~Child(), unlink %s>>", PATH);

    unlink (PATH);
}

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
    }

    close (m_channel);

//重啟父程序服務
    LOGE("<<parent died,restart now>>");

    restart_parent();
}

void Child::restart_parent() {
    LOGE("<<restart_parent enter>>");

    /**
     * TODO 重啟父程序,通過am啟動Java空間的任一元件(service或者activity等)即可讓應用重新啟動
     */
    execlp("am", "am", "startservice", "--user", g_userId, "-n", SERVICE_NAME, //注意此處的名稱
            (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);
}

bool Child::create_channel() {
    int listenfd, connfd;

    struct sockaddr_un addr;

    listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);

    unlink (PATH);

    memset(&addr, 0, sizeof(addr));

    addr.sun_family = AF_LOCAL;

    strcpy(addr.sun_path, PATH);

    if (bind(listenfd, (sockaddr*) &addr, sizeof(addr)) < 0) {
        LOGE("<<bind error,errno(%d)>>", errno);

        return false;
    }

    listen(listenfd, 5);

    while (true) {
        if ((connfd = accept(listenfd, NULL, NULL)) < 0) {
            if (errno == EINTR)
                continue;
            else {
                LOGE("<<accept error>>");

                return false;
            }
        }

        set_channel(connfd);

        break;
    }

    LOGE("<<child channel fd %d>>", m_channel);

    return true;
}

void Child::handle_msg(const char* msg) {
//TODO How to handle message is decided by you.
}

void Child::listen_msg() {
    fd_set rfds;

    int retry = 0;

    while (1) {
        FD_ZERO(&rfds);

        FD_SET(m_channel, &rfds);

        timeval timeout = { 3, 0 };

        int r = select(m_channel + 1, &rfds, NULL, NULL, &timeout);

        if (r > 0) {
            char pkg[256] = { 0 };

            if (FD_ISSET(m_channel, &rfds)) {
                read_from_channel(pkg, sizeof(pkg));

                LOGE("<<A message comes:%s>>", pkg);

                handle_msg((const char*) pkg);
            }
        }
    }
}

void Child::do_work() {
    start_parent_monitor(); //啟動監視執行緒

    if (create_channel())  //等待並且處理來自父程序傳送的訊息
    {
        listen_msg();
    }
}

子程序在他的do_work中先建立了一個執行緒輪詢其父程序的PID,如果發現變成了1,就會呼叫restart_parent,在其中呼叫execlp,執行一下am指令啟動JAVA側的元件,從而實現父程序的重啟!這裡請留意一下execlp中給am傳入的引數,帶了–user並加上了之前我們在全域性變數中儲存的user id,如果不加這個選項,就無法重啟父程序,我在這花費了好長時間哦!

子程序剩餘的工作很簡單,建立通道,監聽來自父程序的訊息,這裡我們用select來監聽,由於實際上只有一個客戶端(父程序),所以用select有點脫褲子放屁,把簡單問題複雜化的嫌疑,但是實際上也沒啥太大影響!

有了以上的實現,JNI的實現就相當的簡單了:

#include "process.h"

/**
 * 全域性變數,代表應用程式程序.
 */
ProcessBase *g_process = NULL;

/**
 * 應用程序的UID.
 */
const char* g_userId = NULL;

/**
 * 全域性的JNIEnv,子程序有時會用到它.
 */
JNIEnv* g_env = NULL;

extern "C" {
JNIEXPORT jboolean JNICALL Java_com_hx_doubleprocess_Watcher_createWatcher(
        JNIEnv*, jobject, jstring);

JNIEXPORT jboolean JNICALL Java_com_hx_doubleprocess_Watcher_connectToMonitor(
        JNIEnv*, jobject);

JNIEXPORT jint JNICALL Java_com_hx_doubleprocess_Watcher_sendMsgToMonitor(
        JNIEnv*, jobject, jstring);

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM*, void*);
};

JNIEXPORT jboolean JNICALL Java_com_hx_doubleprocess_Watcher_createWatcher(
        JNIEnv* env, jobject thiz, jstring user) {
    g_process = new Parent(env, thiz);//建立父程序
    g_userId = (const char*) env->GetStringUTFChars(user,0);//使用者ID
    g_process->catch_child_dead_signal();//接收子執行緒死掉的訊號

    if (!g_process->create_child()) {
        LOGE("<<create child error!>>");
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

JNIEXPORT jboolean JNICALL Java_com_hx_doubleprocess_Watcher_connectToMonitor(
        JNIEnv* env, jobject thiz) {
    if (g_process != NULL) {
        if (g_process->create_channel()) {
            return JNI_TRUE;
        }
        return JNI_FALSE;
    }
}

把上面這些程式碼整合起來,一個雙程序守護的實現就完成了,只需要呼叫一下Watcher.java的createAppMonitor,你的應用就會有一個守護程序來監視,被殺死後也會立刻重新啟動起來!是不是很有意思呢?

相關推薦

Android實現程序守護

如何保證Service不被Kill (1)onStartCommand方法,返回START_STICKY @Override public int onStartCommand(Intent intent, int flags, int star

Android NDK程序守護(Socket)

NDK雙程序守護(單工機制) 最近在系統的學習Android NDK開發於是想著把剛學完的一個知識點總結寫一篇部落格,就是我今天要說的NDK程序守護。目前市面上的應用,貌似除了微信和手Q都會比較擔心被使用者或者系統(廠商)殺死的問題。而最近學的雙程序守護就能很

Android NDK(C++) 程序守護

雙程序守護如果從程序管理器觀察會發現新浪微博、支付寶和QQ等都有兩個以上相關程序,其中一個就是守護程序,由此可以猜到這些商業級的軟體都採用了雙程序守護的辦法。 什麼是雙程序守護呢?顧名思義就是兩個程序互相監視對方,發現對方掛掉就立刻重啟!不知道應該把這樣的一對程序是叫做相依為命呢還是難兄難弟好呢,但總之

Android程序守護service保活

package com.guardservice; import com.guardservice.aidl.GuardAidl; import android.app.Notification; import android.app.PendingIntent; import android.app.Se

android 使用Service進行程序守護,防止程序被殺

public class MyService extends Service { String msg; public MyService() { msg = "Msg from MyService"; } @Override public IBind

安卓開發之使用程序守護程序提權來實現服務程序保活

一、程序保活 在 如何讓我們的Android應用程序保活? 文章裡總結了一些程序保活方法,本文以雙程序守護和程序提權來保活我們的服務程序。 雙程序守護: 主要設計AB兩個不同服務程序,A程序的服務輪詢檢查B程序的服務是否存活,沒存活的話將其拉起,

Android 程序守護

前言   最近有在專案中用到高德的定位SDK,功能是每隔一定的時間獲取一次使用者的地理位置,採取的方案是在後臺開啟一個 Service,監聽高德地圖的位置變化。   該功能在使用者手機螢幕亮時完美實現,但是當螢幕被關閉的時候,位置資訊卻無法被獲取了,

android——實現程序訪問數據

允許 selection stat 查詢 add provider null roi auth 使用之前的SQLite存儲的應用程序。首先需要在這個應用程序中創建內容提供器,右擊com.example.administrator.exp7包→New→Other→Conten

android實現擊返回鍵提示退出

/** * 監聽返回鍵 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK &

Android實現擊事件的監聽

今天寫專案時,要求仿微信朋友圈,雙擊頂欄置頂,於是封裝了雙擊回撥介面:/** * Created by Administrator on 2018/4/24. * 雙擊 */ public cl

安卓應用保活實踐(程序守護

         研究安卓應用保活黑科技已經有一段時間了,其實很多都是看看文章,部落格,看完就忘了,今天休息,在家寫寫Demo,研究一下,跟大家分享。 學習資料:http://www.oschina.

程序守護APP保活方案

探討一種新型的雙程序守護應用保活方法(轉載請宣告出處:http://blog.csdn.net/andrexpert/article/details/53485360)        情景再現:“在高版本Android系統中,應用能否常駐記憶體,我想一直以來都是某些APP頭疼

Android Service後臺程序守護

看了很多資料,說是在android java層實現服務自啟動,但是親測,無論怎麼做都達不到自啟動。 後面看了網上一些資料,從linux層面著手,感覺可行,不過很多資料都採用輪詢的方法去啟動服務,個人感覺輪詢從耗電等各方面考慮並不是最優方案,因為如果可以檢測到服務掛了然後直接

Android實現屏異顯

主要類:Presentation、DisplayManager、Display 1.如何獲取裝置上的螢幕?     DisplayManager  mDisplayManager;//螢幕管理類     Display[]  displays;//螢幕陣列     mDi

程序守護保證Service後臺任務一直執行不被殺死

本來想實現一個一直執行的Service,使用alarmManger定時開啟Service實現重新整理功能,可是發現在android5.0之後會在清理後臺任務時殺死,在android5.0之前的如果使用360軟體也能被殺死。我用的是小米6.0,和華為5.0測試的都會被系統自帶

Android程序保護實現的思考及過程說明

採用雙程序的方式,對父程序進行保護,基於訊號的傳送和接收,實現相互的保護防止被動態攻擊。 雙程序程序保護主要功能: 1、保護父程序,ptrace所有執行緒,防止被附加、除錯、暫停; 2、保護子程序,防止被暫停、異常退出; 對應說明圖: 不足之處與修

android程序守護程序實現程序拉活

1,概念 1)守護程序(Daemon) 是一種執行在後臺的特殊程序,它獨立於控制終端並且週期性的執行某些任務。android中守護程序的實現主要由Service來完成。Android繼承了Linux的lowmemorykiller,為了實現程序常駐,需要應用到守護程序。

Android------實現圖片擊放大,縮小,左右滑動的多種方式

params getcount androi nullable per try out 多圖 tro 項目中常常有圖片瀏覽功能。像微信朋友圈圖片瀏覽,QQ空間照片瀏覽 的功能。 實現圖片雙擊放大,縮小,左右滑動等效果。 來看看我的效果圖,希望能滿足你的要求 前

android實現異網待識別運營商網路

檢測的程式碼 private void checkIMSi() {         boolean simStateBySlotIdx = SimUtils.getSimStateBySlotIdx(this, 0);     &n

Android程序守護,讓APP在系統記憶體中常駐(一)

       其實我們開發者並不想讓自己做的應用變成流氓軟體,但是沒辦法, 你的老闆需要,你要不想讓你的應用常駐,那咱就常駐不了了。。。所以說,言歸正傳。。。       第一篇準備使用系統的服務保活。如果想看提高app的程序等級來實現應用保活,可以直接進行點選Androi