Android开发中双进程守护的尝试与分析实例

作者:简简单单 2015-10-30

最近在做一个Android的项目,其包含一个消息推送的后台服务。由于该服务可能会有重要的信息推送,因此并不希望当APP程序退出、APP程序被一键清理、APP被强制停止等用户操作发生时,这个后台服务也随之被杀死。这个问题也就是所谓的“内存永驻”。关于这个问题,网上有很多说法,如调用startforehand函数以提高service的优先级、在service中创建一个不能被删掉的notification(或者产生一个其他的与用户界面交互的UI控件)、在service的onDestroy函数中重启这个服务、修改onstartcommand函数的返回值等等。这些方法,笔者都一一试过,但都没有效果。但是,我们可以看到市面上也确实存在一些App在一定的时间后可以自动重启,说明仍然是存在方法可以完成这项任务的。

文章中介绍的方法涉及到Android的JNI编程,主要思想就是通过调用native函数创建一个子进程。父子进程相互监听,若子进程死去,父进程妥善处理后重新创建新的子进程;若父进程死去,子进程使用AM命令重启父进程。这种思想唯一的缺陷就是如何保证父子进程不被同时杀死的情况。子进程能不能被杀死,只能用实验来证明。

首先笔者按照文章介绍的,整理了代码,并将相关代码植入到自己的项目中。

步骤1)编写Watcher类。它为上面的Java程序调用提供必要的接口,声明需要native语言实现的的具体函数。native语言主要是指C/C++语言。上层的Java程序只需要创建一个Watcher类并调用它的createAppMonitor(String userId)函数即可。

public class Watcher {
    private static final String PACKET = 
"com.example.dameonservice";
    private String mMonitoredService = 
"com.example.mqtt.MQTTSubscribeService";
    private volatile boolean 
bHeartBreak = false;
    private Context mContext;
    private boolean 
mRunning = true;
    
    public void createAppMonitor(String 
userId)
    {
        if(!createWatcher(userId))
        
{
            Log.e("Watcher", "<>");
        }
    }
    
    public Watcher(Context 
context)
    {
        mContext = context;
    }
   
    
/*创建一个监视子进程
     *userId 当前进程的用户ID,子进程重启当前进程时需要用到当前进程的用户ID
     *return  
若子进程创建成功返回TRUE,否则返回FALSE
     */
    private native boolean 
createWatcher(String userId);
    
    /* 让当前进程连接到监视进程
     * return 
连接成功返回TRUE,否则返回FALSE
     */
    private native boolean 
connectToMonitor();
    
    /*向监视进程发送任意信息
     * msg 
发给monitor的信息
     * return 实际发送的字节数
     */
    private native int 
sendMsgToMonitor(String msg);
    
    static
    {
        
System.loadLibrary("monitor");   //这里要和后面的Android.mk中模块名对应
    }
}

2)编译上面的文件会在bin/classes 目录下生成相对应的Watcher.class文件,通过DOs界面进入该bin/classes 目录下,通过javah命令生成C/C++对应的头文件。

“javah 包名+类名” 得到以下头文件:

#include
/* Header for class com_example_dameonservice_Watcher */

#ifndef _Included_com_example_dameonservice_Watcher
#define _Included_com_example_dameonservice_Watcher
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_dameonservice_Watcher
 * Method:    createWatcher
 * Signature: (Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_com_example_dameonservice_Watcher_createWatcher
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_example_dameonservice_Watcher
 * Method:    connectToMonitor
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_com_example_dameonservice_Watcher_connectToMonitor
  (JNIEnv *, jobject);

/*
 * Class:     com_example_dameonservice_Watcher
 * Method:    sendMsgToMonitor
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_com_example_dameonservice_Watcher_sendMsgToMonitor
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

3)创建JNI文件夹,将得到的头文件移到该文件夹下,继续在该文件夹下创建与上面得到的头文件同名的C/C++文件,然后实现头文件中提到的方法。(具体实现太多,这里就不再贴出来了)

4)添加Android.mk文件。这个文件的格式基本是统一的。只需要修改LOCAL_MODULE和LOCAL_SRC_FILES两处即可。如果你还有添加Log打印函数,还要在这里添加 “LOCAL_LDLIBS := -lm -llog”。

下面一张图来说明整体的文件结构分布:


其中com_example_dameonservice_Watcher.c和com_example_dameonservice_Watcher.cpp内容相同。process.cpp定义一些辅助类。

实验结果:

这当然是大家最关心的。测试的手机选用的小米,感觉 小米在这一块的优化还是很不错的,所以用它来试试。最终的测试结果是:被杀死的服务概率性地可以重启成功,且失败的概率更大。通过Log分析,不能重启的时候是因为子进程也死掉了。截止到笔者写下这篇文章,还没有抓住其中的规律。一键清理和子进程的被杀死没有绝对的对应关系。而且即使是在App运行的时候,也会发现子进程会被杀死,然后又被父进程重启。子进程被杀死是重启失败的主要原因。但现在的现象无法确定子进程被杀死的确切原因,有一种可能是被系统杀死了,但这样的不确定性太大,对效果也不能有很好的保证。

虽然没有完美解决问题,但至少比前面的办法强很多,至少它也重启成功过。这个方法感觉继续优化一下还是可以做好的。



Android实现双进程守护


做过android开发的人应该都知道应用会在系统资源匮乏的情况下被系统杀死!当后台的应用被系统回收之后,如何重新恢复它呢?网上对此问题有很多的讨论。这里先总结一下网上流传的各种解决方案,看看这些办法是不是真的可行。

1.提高优先级
这个办法对普通应用而言,应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!
    
2.让service.onStartCommand返回START_STICKY
通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的情况(或者用360手机卫士进行清理操作),如果服务的onStartCommand返回START_STICKY,在eclipse的进程管理器中会发现过一小会后被杀死的进程的确又会出现在任务管理器中,貌似这是一个可行的办法。但是如果在系统设置的App管理中选择强行关闭应用,这时候会发现即使onStartCommand返回了START_STICKY,应用还是没能重新启动起来!

3.android:persistent="true"
网上还提出了设置这个属性的办法,通过实验发现即使设置了这个属性,应用程序被kill之后还是不能重新启动起来的!

4.让应用成为系统应用
实验发现即使成为系统应用,被杀死之后也不能自动重新启动。但是如果对一个系统应用设置了persistent="true",情况就不一样了。实验表明对一个设置了persistent属性的系统应用,即使kill掉会立刻重启。一个设置了persistent="true"的系统应用,在android中具有core service优先级,这种优先级的应用对系统的low memory killer是免疫的!

OK,说了半天,只有core service优先级的应用才能保证在被意外杀死之后做到立刻满血复活。而普通应用要想成为系统应用就必须要用目标机器的签名文件进行签名,但这样又造成了应用无法保证兼容所有不同厂商的产品。那么该怎么办呢?这里就来说一说双进程守护。网上也有人提到过双进程守护的办法,但是很少能搜索到类似的源码!如果从进程管理器重观察会发现新浪微博或者360卫视都有两个相关的进程,其中一个就是守护进程,由此可以猜到这些商业级的软件也采用了双进程守护的办法。

什么是双进程守护呢?顾名思义就是两个进程互相监视对方,发现对方挂掉就立刻重启!不知道应该把这样的一对进程是叫做相依为命呢还是难兄难弟好呢,但总之,双进程守护的确是一个解决问题的办法!相信说到这里,很多人已经迫切的想知道如何实现双进程守护了。这篇文章就介绍一个用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++中完成!

package com.example.dameonservice;
import java.util.ArrayList;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;

/**
* 监视器类,构造时将会在Native创建子进程来监视当前进程,
* @author wangqiang
* @date 2014-04-24
*/
public class Watcher
{
//TODO Fix this according to your service
private static final String PACKAGE = "com.example.dameonservice/";
private String mMonitoredService = "";
private volatile boolean bHeartBreak = false;
private Context mContext;
private boolean mRunning = true;

public void createAppMonitor(String userId)
{
if( !createWatcher(userId) )
{
Log.e("Watcher", "<>");
}
}

public Watcher( Context context)
{
mContext = context;
}

private int isServiceRunning()
{
ActivityManager am=(ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
ArrayList runningService = (ArrayList)am.getRunningServices(1024);
for( int i = 0; i < runningService.size(); ++i )
{
if( mMonitoredService.equals(runningService.get(i).service.getClassName().toString() ))
{
return 1;
}
}
return 0;
}

/**
* 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接口,但是要添加简直是轻而易举的事!


Ok,JAVA只是个壳,内部的实现还得是C++,为了让程序更加的面向对象,在实现native时,我们用一个ProcessBase基类来对父子进程进行一个抽象,把父子进程都会有的行为抽象出来,而父子进程可以根据需要用自己的方式去实现其中的接口,先来看看这个抽象了父子进程共同行为的ProcessBase基类:
#ifndef _PROCESS_H
#define _PROCESS_H


#include
#include select.h>
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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"
#include "Utils.h"

extern ProcessBase *g_process;

extern const char* g_userId;

extern JNIEnv* g_env;

//子进程有权限访问父进程的私有目录,在此建立跨进程通信的套接字文件
static const char* PATH = "/data/data/com.example.dameonservice/my.sock";

//服务名称
static const char* SERVICE_NAME = "com.example.dameonservice/com.example.dameonservice.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("<>");

m_jobj = env->NewGlobalRef(jobj);
}

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

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("<>");

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("<>", 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("<>", 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("<>");

create_child();
}

bool Parent::create_child( )
{
pid_t pid;

if( (pid = fork()) < 0 )
{
return false;
}
else if( pid == 0 ) //子进程
{
LOGE("<>", getpid() );

Child child;

ProcessBase& ref_child = child;

ref_child.do_work();
}
else if( pid > 0 )  //父进程
{
LOGE("<>", 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( "<>" );

restart_parent();
}

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

/**
* 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("<>", errno);

return false;
}

listen( listenfd, 5 );

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

return false;
}
}

set_channel(connfd);

break;
}

LOGE("<>", 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("<>", 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"
#include "Utils.h"


/**
* 全局变量,代表应用程序进程.
*/
ProcessBase *g_process = NULL;

/**
* 应用进程的UID.
*/
const char* g_userId = NULL;

/**
* 全局的JNIEnv,子进程有时会用到它.
*/
JNIEnv* g_env = NULL;

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

JNIEXPORT jboolean JNICALL Java_com_example_dameonservice_Watcher_connectToMonitor( JNIEnv*, jobject );

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

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

JNIEXPORT jboolean JNICALL Java_com_example_dameonservice_Watcher_createWatcher( JNIEnv* env, jobject thiz, jstring user )
{
g_process = new Parent( env, thiz );

g_userId  = (const char*)jstringTostr(env, user);

g_process->catch_child_dead_signal();

if( !g_process->create_child() )
{
LOGE("<>");

return JNI_FALSE;
}

return JNI_TRUE;
}


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

return JNI_FALSE;
}
}

把上面这些代码整合起来,一个双进程守护的实现就完成了,只需要调用一下Watcher.java的createAppMonitor,你的应用就会有一个守护进程来监视,被杀死后也会立刻重新启动起来!是不是很有意思呢?


相关文章

精彩推荐