转载请注明链接
因为Android的内存管理机制,当系统内存紧张时,App如果运行在后台,容易被LowMemoryKiller杀死。为了保证App的Service能够在杀死后重启,下面介绍一种在进程杀死后能够重新拉起App的方式。
简单来讲,就是开启linux守护进程,轮询App进程是否存活,如果发现进程已被杀死,通过am start将App的Service拉起。
由于此linux进程与App进程不在同一进程空间,LowMemoryKiller杀死App进程时会放过linux进程,这样就保证了linux进程作为守护进程在后台存活,监视App进程。
为了比较显著得观察到守护进程拉起App进程的过程,本例使用拉起Activity的方式演示。
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命令返回的存活进程。
代码实现的思路如下:
#include
#include
#include
#include
#include
#include
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;
}
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;
}
}
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上可用。不保证所有手机可用。