1

Android App 不死之路

 3 years ago
source link: http://www.androidchina.net/4992.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

如何让你的app一直在运行状态呢?

默认情况下,不做任何跨进程部署配置的话,每个android app运行在单独一个虚拟机上,每个虚拟机对应一个进程。当app被系统回收或者是被用户主动杀掉(通过app管理软件),进程就彻底退出了。

在有些场景,app所在的进程退出了,我们希望还能做一些操作。比如,app被卸载后(卸载会先退出运行),我们希望跳转浏览器做一些卸载原因的调查问卷;或者为了优化体验,提高app热启动速度,再比如,有些监控的app,我们希望一直在运行,否则可能数据不准确,这就需要app所在进程退出后能自我启动。

为了做到app停止运行状态的监控和执行回调,典型的解决方案就是多进程相互守护

去你的手机上瞧瞧,设置-应用管理-运行中 ,有没有发现,支付宝,QQ,微信等等App同时都有两个进程在运行,有木有。

1240

multiprocess

双进程相互守护

双进程A和B相互守护,当A进程检测到B进程退出时,A进程重新启动B进程,同理当A进程退出时,B重新启动A进程。进程的运行状态的检测是实现进程守护的关键点,比较容易想到的方案有:

  1. 通过轮训的方式查询远端进程的状态
  2. 通过发送心跳包来看是否能接受应答,无应答,远端进程可能已经退出
  3. 在Application生命周期终止时,也就是onTerminate方法里发送广播通知对方自己即将灭亡

通过轮训的方式,只要通过ps命令查询状态,无需A和B做进程间通信;心跳包应答检测的方式需要通过socket或者别的IPC机制来做通信。

守护进程可以是一个运行android app的进程(Dalvik进程),也可以是一个linux的原生进程,如果是Dalvik进程,当app被卸载时,进程会被退出,字节码被移除,无法再运行任何逻辑比如跳转浏览器页面。

如果守护进程是linux系统里fork一个新的进程,与app不在同一个进程空间,当app被关闭或者杀掉或者卸载的时候,不会影响守护的运行状态,也就是说守护还是处于运行状态,可以执行相应操作。因此守护可以监控app的运行状态,发现app停止运行时,可以发送命令启动app进程,保证app的生存,这对某些系统监控的app来说至关重要。而且linux上的进程,通过android上的PackageManger获取不到,也不会在app管理软件的运行软件之列,基本上不会被杀掉,守护本身可以相对可靠的生存。

本文介绍一下,如何在linux 上fork一个原生进程来守护Dalvik app的进程。

linux原生进程守护

介绍几个linux下的几个api

  • int daemon (int nochdir, int noclose);

将程序将运行在后台,成为一个daemon程序,而linux下大多的服务都是以此方式运行的


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

    参数解析的帮助函数,当命令参数多个而且有些课选参数时,如果只按顺序接受参数容易混乱,如果按参数名来对应则便利很多,可以参考一下


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

popen()创建一个管道,fork一个新的进程执行shell命令,popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同.


  • int system(const char * string);

函数说明
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
返回值

守护流程图

1240

守护流程图

守护的程序代码

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);
run_service(process_name, package_name, activity_name, 10);
return 0;
}

run_service的实现

int chk_process(const char *process_name)
{
FILE   *stream;
char   *line = NULL;
size_t len = 0;
ssize_t read_len;
stream = popen( "ps", "r" );
if (stream == NULL)
return -1;
int exists = 0;
while ( (read_len = 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 run_service(const char *process_name, const char *package_name, const char *activity_name, int interval_sec)
{
while (1)
{
if ( chk_process(process_name) == 0)
{
char *pkg_activity_name = NULL;
// 格式化命令
asprintf(&pkg_activity_name, "/system/bin/am start --user 0 -n %s/%s", package_name, activity_name);
system(pkg_activity_name);// 执行命令启动app
free(pkg_activity_name);
}
// sleep 指定时间间隔
sleep(interval_sec);
}
return;
}

编译成功后生成xxx,重命名为xxx.so,把文件拷贝到libs下,这样安装后该文件会被同动态库一起拷贝到data/data/app_package目录下,编写拷贝和chmod相关逻辑的代码,大概流程如下

  1. path = “/data/data/” +packageName; // 安装后的app路径
  2. 执行shell命令:dd if= path+lib/xxx.so of=path/xxx ;//拷贝到app路径下,重命名为xxx
  3. 赋可执行权限 chmod 777 path/xxx;
  4. 运行可执行文件 path/xxx -p process_name -a pkgname ..(别的参数)

需要注意的一点:
这里的操作都是通过执行shell来完成的,需要先cd到app 路径下,才会有读写权限。

public static boolean execCommand(String command, String packageName) {
Process process = null;
try {
process = Runtime.getRuntime().exec("sh");  //获得shell.
DataInputStream inputStream = new DataInputStream(process.getInputStream());
DataOutputStream outputStream = new DataOutputStream(process.getOutputStream());
//保证在command在自己的数据目录里执行,才有权限写文件到当前目录
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 s = new String(buffer);
} catch (Exception e) {
return false;
}
return true;
}

编好代码打包测试时,通过app管理界面停止app的运行,看看app是否会被重新启动。

http://www.cnblogs.com/caosiyang/archive/2012/06/25/2560976.html

http://blog.csdn.net/cashey1991/article/details/7942809

文/aimfaraway(简书作者)
原文链接:http://www.jianshu.com/p/a4a6222654a0
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

转载请注明:Android开发中文站 » Android App 不死之路


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK