Android implements two-process daemon

Keywords: Android Java socket Linux

How to ensure that Service is not Kill

For knowledge about Service, please refer to Comprehensive Analysis of Android Service This article is written in great detail.

(1) onStartCommand method, returning START_STICKY

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    flags = START_STICKY;  
    return super.onStartCommand(intent, flags, startId);  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

Return START_STICKY manually, and test that when the service is killed due to insufficient memory, and when there is memory, the service is recreated, which is good, but it can not guarantee that it will be rebuilt in any case, such as when the process is killed.

Normal operation:

Killing process:

Process automatic restart:

Successful restart:

(2) Application plus Persistent attribute
see Android Documents know that when a process is inactive for a long time or the system needs resources, it automatically cleans up the portal, kills some services, and invisible Activities and other processes. But if a process does not want to be killed (such as a data caching process, a status monitoring process, or a remote Service process), you can do this:

<application  
    android:allowBackup="true"  
    android:icon="@drawable/ic_launcher"  
    android:label="@string/app_name"  
    android:persistent="true" //Setting up Persistent
    android:theme="@style/AppTheme" >  
</application>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

It is said that this property can not be set randomly, but after setting it, we do find that the priority has been raised a lot, which may be equivalent to the system-level process, but it still can not guarantee survival.

(3) Improving service priority
For intent-filter in Android Manifest. XML file, the highest priority can be set by the attribute of android:priority = 1000, which is the highest value. If the number is smaller, the lower the priority, and it is suitable for broadcasting.

<service  
    android:name="com.hx.doubleprocess.MyService"  
    android:enabled="true" >  
    <intent-filter android:priority="1000" >  
        <action android:name="com.hx.myservice" />  
    </intent-filter>  
</service>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

At present, the attribute priority seems to only apply to broadcast, which may not be valid for Service.

(4) Improving service process priority
Processes in Android are managed, and when the system process space is tight, the process is automatically recycled according to priority. Android divides processes into five levels, which are in the order of priority from high to low:

  • Foreground process
  • Visible process
  • Service process
  • Background process
  • Empty process

When service runs in a low memory environment, it kills some existing processes. Therefore, the priority of the process will be very important. You can use start Foreground to put the service in the foreground state. This reduces the chance of killing in low memory.

@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("Notice arrives") 
            .setContentTitle("This is the title of the notice.") 
            .setContentText("This is the content of the notice.")
            .setOngoing(true)
            .setContentIntent(pendingIntent)
            .build();
        /*Using startForeground, if id is 0, notification will not be displayed*/
        startForeground(1, notification);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Note that stopForeground(true) is also required in onDestroy.
If under extremely low memory pressure, the service will still be kill ed, and not necessarily restart.

(5) Restart service in onDestroy method
service +broadcast means that when service goes ondestory, it sends a custom broadcast and restarts service when it receives the broadcast.

<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"/> //This is the custom action.  
    </intent-filter>  
</receiver> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

On Destroy:

@Override  
public void onDestroy() {  
    stopForeground(true);  
    Intent intent = new Intent("com.hx.destroy");  
    sendBroadcast(intent);  
    super.onDestroy();  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

In BootReceiver:

public class BootReceiver extends BroadcastReceiver {    
    @Override  
    public void onReceive(Context context, Intent intent) {  
        if (intent.getAction().equals("com.hx.destroy")) {  
            //Write about restarting service here  
            startUploadService(context);  
        }    
    }    
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

You can also start Service directly in onDestroy():

@Override  
public void onDestroy() {    
     Intent sevice = new Intent(this, MyService.class);  
     this.startService(sevice);    
     super.onDestroy();  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

When using third-party applications such as 360 housekeepers or in setting s - applications - forced stops, the APP process may be killed directly, and the onDestroy method is not available, so it is still not guaranteed.

(6) Monitoring System Broadcasting Judgment Service Status
Some broadcasts of the system, such as cell phone reboot, interface wake-up, application status change, etc., are monitored and captured, and then we can judge whether our Service is still alive. Don't forget the weighting limit.

<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> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

In Broadcast Receiver:

@Override  
public void onReceive(Context context, Intent intent) {  
    if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {  
        System.out.println("The cell phone is on....");  
        startUploadService(context);  
    }  
    if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {  
        startUploadService(context);  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

This may be a measure, but feeling that more monitoring will lead to confusion and inconvenience in Service.

(7) Install APK into / system/app and transform it into a system-level application
This method is suitable for debugging. It is not a solution. It is not recommended because your APP is for users.
Mobile phones requiring root privileges copy APK files to the / system/app directory and restart the phone. Settings - Application Management, you can see that our APP has been unable to uninstall, can only be disabled. System-level APP, such third-party housekeeping software, can't kill us unless we stop using APP or force it to stop.
Even if it becomes a system application, it cannot be automatically restarted after being killed. But if persistent= "true" is set for a system application, the situation is different. Experiments show that a system application with persistent property is restarted immediately even if kill is dropped. A system application with persistent = "true" has core service priority in android. This priority application is immune to the low memory killer of the system.

Two-process daemon

If you observe from the process manager, you will find that there are more than two related processes in Sina micro-blog, Alipay and QQ. One of them is the daemon, so you can guess that these business level software adopts the dual process guard method.

What is a two-process guardian? As the name implies, two processes monitor each other and restart immediately when they find that the other is dead. I don't know whether we should call such a pair of processes dependent or difficult, but in a word, two-process guardianship is really a way to solve the problem! ___________ I believe that at this point, many people have been eager to know how to achieve the two-process guardianship. This article introduces a way to realize two-process protection with NDK, but first of all, the following methods will lose a lot of efficiency, reflecting that in reality it will make the power consumption of mobile phones larger! But this article is just a start, I believe that after reading, there will be more senior people pointing out better ways to achieve.

What do you need to know?
The method of realizing two-process protection in this article is basically pure NDK development, or all of them are implemented in C++. Programs requiring two-process protection only need to be invoked anywhere in the program. Java Interface is enough. The following knowledge points need to be understood:

  • 1. Multi-process in linux;
  • 2.unix domain socket realizes cross-process communication;
  • 3.linux signal processing;
  • 4. The usage of exec function family;

In fact, these things themselves are not so complex technology, but we put them together to achieve a two-process guardianship, not as mysterious as imagination! Before posting the code, let's talk about some key points when implementing two-process daemon:

  • 1. How does the parent process monitor the death of the child process?
    Simply, in linux, when the child process is terminated, the SIG_CHLD signal is sent to the parent process, so we can install the signal processing function and restart the creation of the monitoring process in this signal processing function.
  • 2. How does the child process (monitor process) monitor the death of the parent process?
    When the parent process dies, the child process becomes an orphan process and is adopted by the Init process. So we can read the parent process PID of the child process in a loop. When the parent process changes to 1, it means that the parent process has died, so we can restart the parent process. Because of the cycle, the problem of power consumption mentioned earlier is raised.
  • 3. Communication between father and son processes
    One way is to establish a communication channel between father and son processes, and then to perceive each other's existence by monitoring this channel, so there will not be the problem of power consumption mentioned earlier. In this paper, in order to be simple, the method of polling the father and son process PID is adopted, but the communication channel between father and son process is still left out. Although it is not used for the time being, it can be backed up from time to time!

OK, here's the code! First, the Java part, which is too simple, just a class, provides API interfaces for external calls to create daemons. All implementations are done in C++ through the native method.

/**
 * Monitor class, constructed to create subprocesses in Native to monitor the current process
 */

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 Method to create a monitoring subprocess.
     *
     * @param userId
     *            User ID of the current process and user ID of the current process are needed when the child process restarts the current process.
     * @return If the child process is created successfully, it returns true, otherwise it returns false.
     */
    private native boolean createWatcher(String userId);

    /**
     * Native Method to connect the current process to the monitoring process.
     *
     * @return Connection returns true successfully, otherwise false
     */
    private native boolean connectToMonitor();

    /**
     * Native Method to send arbitrary information to the monitoring process
     *
     * @param Information sent to monitor
     * @return Bytes actually sent
     */
    private native int sendMsgToMonitor(String msg);

    static {
        System.loadLibrary("monitor");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

Just care about the external interface of createAppMonitor, which requires the user ID of the current process to be passed in, and then calls the createWatcher local method to create the daemon. Two other methods, connectToMonitor, are used to create and monitor socket channels for processes, and sendMsgToMonitor is used to send data to subprocesses through sockets. Because there is no need for data interaction with sub-processes at present, these two methods do not add external JAVA interfaces, but it is easy to add them!

JAVA is only a shell, and its internal implementation is still C++. In order to make the program more object-oriented, when implementing native, we use a ProcessBase base class to abstract the parent-child process, abstract the behavior of both father-child process, and the parent-child process can implement the interface in its own way according to the need. First, we look at the abstract parent-child process together. Peer-to-peer ProcessBase base classes:

#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__)

/**
 * Function: An abstraction of the father-son process
 * @author wangqiang
 * @date 2014-03-14
 */
class ProcessBase {
public:

    ProcessBase();

    /**
     * The parent-child process does different things, leaving an abstract interface for the parent-child process.
     * Do it yourself.
     */
    virtual void do_work() = 0;

    /**
     * Processes can create sub-processes as needed, and if no sub-processes need to be created, they can give
     * An empty implementation of this interface is enough.
     */
    virtual bool create_child() = 0;

    /**
     * Capture the signal of the death of the sub-process. If there is no sub-process, this method can give an empty implementation.
     */
    virtual void catch_child_dead_signal() = 0;

    /**
     * Do anything after the child process dies.
     */
    virtual void on_child_end() = 0;

    /**
     * Create parent-child process communication channels.
     */
    bool create_channel();

    /**
     * Set up communication channels for processes.
     * @param channel_fd File Description of Channels
     */
    void set_channel(int channel_fd);

    /**
     * Write data to the channel.
     * @param data Write channel data
     * @param len  Number of bytes written
     * @return Number of bytes actually written to the channel
     */
    int write_to_channel(void* data, int len);

    /**
     * Read data from channels.
     * @param data Save data read from channels
     * @param len  The number of bytes read from the channel
     * @return Number of bytes actually read
     */
    int read_from_channel(void* data, int len);

    /**
     * Get the file descriptor corresponding to the channel
     */
    int get_channel() const;

    virtual ~ProcessBase();

protected:

    int m_channel;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

Just a very simple class, I believe you can see what it means by looking at the annotations. For example, both father and son processes may need to capture the death signal of their descendants, so give a catch_child_dead_signal function. If you are not interested in the death or life of the child process, you can give an empty implementation and ignore it. ProcessBase is an abstract class because of the use of pure virtual functions. That is to say, it can not have its own instances, but is used for inheritance. Its descendants can implement the interfaces in it in different ways, thus showing different behaviors. Here, the behavior of the parent process and the child process is different. Here, we present the implementation of the father process for the emperors first time.

/**
 * Function: Implementation of parent process
 * @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();

    /**
     * Get the JNIEnv of the parent process
     */
    JNIEnv *get_jni_env() const;

    /**
     * Getting Objects in the Java Layer
     */
    jobject get_jobj() const;

private:

    JNIEnv *m_env;

    jobject m_jobj;

};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

Above is the definition part. In fact, JNIEnv and jobject are basically not used. They can be shaved off completely. Everyone assumes that these two attributes do not exist. The realization part is as follows:

#include "process.h"

extern ProcessBase *g_process;

extern const char* g_userId;

extern JNIEnv* g_env;

//The child process has access to the private directory of the parent process, where it establishes a socket file for cross-process communication
static const char* PATH = "/data/data/com.hx.doubleprocess/my.sock";

//Service name
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;
}

/**
 * The parent process creates the channel, which is actually creating a client and trying
 * Connect servers (subprocesses)
 */
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;
}

/**
 * The SIGCHLD signal is emitted when the child process dies. By capturing the SIGCHLD signal, the parent process can
 * Knowing that the subprocess has died, this function is the SIGCHLD signal processing function.
 */
static void sig_handler(int signo) {
    pid_t pid;

    int status;

//SIGCHLD issued while waiting for the child process to die by calling wait
//Signal to collect the child process and prevent it from becoming a zombie process
    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) //Child process
    {
        LOGE("<<In child process,pid=%d>>", getpid());

        Child child;

        ProcessBase& ref_child = child;

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

    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166

Three global variables are described here:

  • g_process is a pointer to the parent process.
  • g_userId is the user ID of the parent process, which is passed from the Java side. We need to save it with global variables, because the child process needs to use user ID when restarting the parent process, otherwise there will be problems. Of course, this also benefits from the fact that the child process can inherit the global variables of the parent process!
  • g_env is JNIEnv's pointer, and it is reserved for sub-processes as a global variable.

The parent creates a Child process with fork in create_child, which is actually a fork call. Then the parent process does nothing. The Child process creates a Child object and calls its do_work to start doing what it should do!

The parent process implements catch_child_dead_signal, in which SIG_CHLD signal processing function is installed, because he loves his son very much and cares about him all the time. In the signal processing function sig_handler, we notice the wait call, which is to prevent the child process from becoming a zombie process after it dies. Since we already know that the parent process can only create a child monitoring process at most, wait is enough, and waitpid function is not required to jump out in person! And the signal processing function is very simple, just call on_child_end again, and create_child and his dear wife again in make a little baby!

Finally, let's talk about the function of create_channel, which is used to create socket channels for sub-processes. This programming model is very familiar to those who have network programming experience. He follows the standard network programming client steps: create socket, connect, then send and receive data, OK, but the protocol here uses AF_LOCAL, which indicates that we are going to make strides. Cheng communications. Since domain sockets use not IP addresses, but communicate with the target process through a specified file, both parent and child processes need this file, so the location of this file should also be noted: on a mobile phone without root, almost all files are not authorized to write, but fortunately, they are not authorized to write. Linux The child process shares the directory of the parent process, so specifying this location to the private directory of the application under / data/data / enables the parent and child processes to access the file!

Next comes the implementation of the subprocess, which is defined as follows:

/**
 * Implementation of Subprocesses
 * @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:

    /**
     * Dealing with parent process deaths
     */
    void handle_parent_die();

    /**
     * Listen for messages sent by the parent process
     */
    void listen_msg();

    /**
     * Restart the parent process.
     */
    void restart_parent();

    /**
     * Processing messages from parent processes
     */
    void handle_msg(const char* msg);

    /**
     * Thread function to detect whether the parent process is suspended
     */
    void* parent_monitor();

    void start_parent_monitor();

    /**
     * The purpose of this union is to help use class member functions as thread functions
     */
    union {
        void* (*thread_rtn)(void*);

        void* (Child::*member_rtn)();
    } RTN_MAP;
};
#endif 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

Notice that there is a union in it. The purpose of this union is to assist in passing member functions of a class to pthread_create as thread functions. Many times, we hope that threads can access private members of a class like their own. Just like a member function, friend can do this, but it always feels less graceful because member functions are implicit. The this pointer makes it possible for us to use a member function as a thread function. Just because the compiler blocked the type conversion of function pointers, there's only one structure to use here.

Not much nonsense, look at the implementation of sub-processes:

bool Child::create_child() {
//Subprocesses do not need to create subprocesses, this function is left blank
    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() {
//Subprocesses do not need to capture SIGCHLD signals
    return;
}

void Child::on_child_end() {
//Subprocesses do not need to be processed
    return;
}

void Child::handle_parent_die() {
//The child process becomes an orphan process, waiting to be adopted by the Init process for subsequent processing.
    while (getppid() != 1) {
        usleep(500); //Dormant 0.5ms
    }

    close (m_channel);

//Restart parent process service
    LOGE("<<parent died,restart now>>");

    restart_parent();
}

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

    /**
     * TODO Restart the parent process to restart the application by starting any component of Java space (service or activity, etc.) through am
     */
    execlp("am", "am", "startservice", "--user", g_userId, "-n", SERVICE_NAME, //Notice the name here.
            (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(); //Start monitoring threads

    if (create_channel())  //Wait and process messages sent from the parent process
    {
        listen_msg();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143

In his do_work, the child process first creates a thread to poll the PID of its parent process. If it finds that it has become 1, it will call restart_parent, call execlp in it, execute am instruction to start the component on JAVA side, so as to restart the parent process! Here, please note the parameters that are passed in to am in execlp with user and the user id we saved in the global variable. Without this option, the parent process can not be restarted. I spent a long time here.

The rest of the work of the child process is very simple. Create channels and listen for messages from the parent process. Here we use select to listen. Because there is actually only one client (parent process), we use select to fart a little, which complicates the simple problem, but in fact it doesn't have much impact.

With the above implementation, the implementation of JNI is quite simple:

#include "process.h"

/**
 * Global variables, representing the application process.
 */
ProcessBase *g_process = NULL;

/**
 * UID of application process.
 */
const char* g_userId = NULL;

/**
 * Global JNIEnv, which is sometimes used by subprocesses.
 */
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);//Create a parent process
    g_userId = (const char*) env->GetStringUTFChars(user,0);//User ID
    g_process->catch_child_dead_signal();//Receiving a signal that a child thread has died

    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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

Integrate the above code, and a two-process daemon is completed. Just call Watcher.java's createAppMonitor, and your application will have a daemon to monitor and restart immediately after being killed! Is it interesting?

Demo Download Address

Posted by dzoddi on Thu, 28 Mar 2019 18:00:28 -0700