转载请注明链接

因为Android的内存管理机制,当系统内存紧张时,App如果运行在后台,容易被LowMemoryKiller杀死。为了保证App的Service能够在杀死后重启,下面介绍一种在进程杀死后能够重新拉起App的方式。

1. 思路:

简单来讲,就是开启linux守护进程,轮询App进程是否存活,如果发现进程已被杀死,通过am start将App的Service拉起。
由于此linux进程与App进程不在同一进程空间,LowMemoryKiller杀死App进程时会放过linux进程,这样就保证了linux进程作为守护进程在后台存活,监视App进程。

为了比较显著得观察到守护进程拉起App进程的过程,本例使用拉起Activity的方式演示。

2. linux进程中用到的方法:

int getopt_long(int argc, char * const argv[],
                  const char *optstring,
                  const struct option *longopts, int *longindex);

获取命令行参数,此处获取由Activity启动守护进程时传递进来的守护进程名、包名、Activity名、轮询间隔时间。

signal(SIGCHLD,SIG_IGN);

防止内核在子进程结束时产生僵尸进程。

int daemon(int nochdir,int noclose) 

使进程成为守护进程,在后台运行。监视App进程是否存活。

int system(const char * cmdstring)

fork出shell子进程执行cmd,父进程等待shell执行完毕。用以执行am start命令,拉起App。

FILE *popen(const char *command, const char *type);

管道形式fork子进程执行command,从管道中读或写。用以读取ps命令返回的存活进程。

3. 代码:

代码实现的思路如下:

  • 实现守护进程,并通过Android.mk,在源码编译或是ndk-build生成可执行程序appdaemon。
  • 将守护进程程序重名为libappdaemon.so,并放在App工程ibs目录下。
  • Run App安装,libappdaemon.so会拷贝在/data/data/com.example.immortalapp/lib目录下。
  • 重命名libappdaemon.so为appdaemon,并chmod其执行权限。
  • shell执行appdaemon,启动守护进程,并传递相关am start参数至守护进程。
  • 守护进程轮询ps命令返回的存活进程,如有发现App死亡,便以am start命令启动App。

3.1 linux进程部分

#include <signal.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/log.h>


int chk_process(const char *process_name) {
    FILE *stream;
    char *line = NULL;
    size_t len = 0;
    ssize_t read_len;
    //执行ps命令,获取当前进程list
    stream = popen("ps", "r");
    if (stream == NULL)
        return -1;

    int exists = 0;
    while (getline(&line, &len, stream) != -1) {
        int len = strlen(line);
        char *cmd = line + len;
        while (len > 0) {
            len--;
            if (*cmd == ' ') {
                cmd++;
                break;
            }
            cmd--;
        }
        //进程还在运行
        if (strncmp(cmd, process_name, strlen(process_name)) == 0) {
            exists = 1;
            break;
        }
    }

    pclose(stream);
    if (line != NULL)
        free(line);

    return exists;
}

void amstart(const char *process_name, const char *package_name, const char *activity_name,
             int interval_sec) {
    char *start_command = NULL;
    // am start -n com.example.immortalapp/.MainActivity
    asprintf(&start_command, "/system/bin/am start -n %s/%s", package_name, activity_name);
    __android_log_print(ANDROID_LOG_DEBUG, "immortalapp", "am start_command: %s", start_command);
    while (1) {
        if (chk_process(process_name) == 0) {
            // system方式执行start command
            system(start_command);
            // 注意释放free
            free(start_command);
        }
        // sleep 时间间隔参数
        sleep(interval_sec);
    }
}

int main(int argc, char *argv[]) {
    signal(SIGTERM, SIG_IGN);
    const char *process_name = NULL;
    const char *package_name = NULL;
    const char *activity_name = NULL;
    int interval_sec = 30;
    struct option options[] =
            {
                    {"process_name",  required_argument, 0, 'p'},
                    {"package_name",  required_argument, 0, 'a'},
                    {"activity_name", required_argument, 0, 'c'},
                    {"interval_sec",  required_argument, 0, 'i'},
                    {0, 0,                               0, 0}
            };

    int c;

    for (;;) {
        c = getopt_long(argc, argv, "p:a:c:i:", options, NULL);
        if (c == -1) {
            break;
        }
        switch (c) {
            case 'p':
                process_name = optarg;
                break;
            case 'a':
                package_name = optarg;
                break;
            case 'c':
                activity_name = optarg;
                break;
            case 'i':
                interval_sec = atoi(optarg);
                break;
            default:
                exit(EXIT_FAILURE);
        }
    }
    if (process_name == NULL || package_name == NULL || activity_name == NULL)
        exit(EXIT_FAILURE);

    daemon(1, 1);

    amstart(process_name, package_name, activity_name, interval_sec);

    return 0;
}

3.2 App部分

package com.example.immortalapp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;

public class MainActivity extends Activity {

    private static final String TAG = "immortalapp";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String packageName = getPackageName();
        String path = "/data/data/" + packageName;
        String command = "dd if=" + path + "/lib/libappdaemon.so of=" + path + "/appdaemon";
        execCommand(command, packageName);
        command = "chmod 777 appdaemon";
        execCommand(command, packageName);
        // 通过daemon进程执行循环检查,监听到进程died,立即am start命令重新启动
        command = "./appdaemon -p " + packageName + " -a " + packageName + " -c " + ".MainActivity" + " -i " + "10";
        execCommand(command, packageName);
    }

    public boolean execCommand(String command, String packageName) {
        Process process = null;
        DataInputStream inputStream = null;
        DataOutputStream outputStream = null;
        try {
            // shell process
            process = Runtime.getRuntime().exec("sh");
            inputStream = new DataInputStream(process.getInputStream());
            outputStream = new DataOutputStream(process.getOutputStream());

            // change到app的data目录下
            outputStream.writeBytes("cd /data/data/" + packageName + "\n");
            outputStream.writeBytes(command + " \n");
            outputStream.writeBytes("exit\n");
            outputStream.flush();
            // 注意阻塞
            process.waitFor();

            byte[] buffer = new byte[inputStream.available()];
            inputStream.read(buffer);
            String result = new String(buffer);
            Log.i(TAG, "command execute result result:" + result);
        } catch (Exception e) {
            Log.i(TAG, "exception caught e:" + e);
            return false;
        } finally {
            try {
                inputStream.close();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

3.3 Android.mk部分

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := appdaemon
LOCAL_SRC_FILES := daemon.c
LOCAL_LDLIBS    += -llog
include $(BUILD_EXECUTABLE)

测试在小米5,android5.0上可用。不保证所有手机可用。

Logo

更多推荐