Android App 不死之路
如何让你的app一直在运行状态呢?默认情况下,不做任何跨进程部署配置的话,每个android app运行在单独一个虚拟机上,每个虚拟机对应一个进程。当app被系统回收或者是被用户主动杀掉(通过app管理软件),进程就彻底退出了。在有些场景,app所在的进程退出了,我们希望还能做一些操作。比如,app被卸载后(卸载会先退出运行),我们希望跳转浏览器做一些卸载原因的调查问卷;或者为了优化体验,提高ap
如何让你的app一直在运行状态呢?
默认情况下,不做任何跨进程部署配置的话,每个android app运行在单独一个虚拟机上,每个虚拟机对应一个进程。当app被系统回收或者是被用户主动杀掉(通过app管理软件),进程就彻底退出了。
在有些场景,app所在的进程退出了,我们希望还能做一些操作。比如,app被卸载后(卸载会先退出运行),我们希望跳转浏览器做一些卸载原因的调查问卷;或者为了优化体验,提高app热启动速度,再比如,有些监控的app,我们希望一直在运行,否则可能数据不准确,这就需要app所在进程退出后能自我启动。
为了做到app停止运行状态的监控和执行回调,典型的解决方案就是多进程相互守护。
去你的手机上瞧瞧,设置-应用管理-运行中 ,有没有发现,支付宝,QQ,微信等等App同时都有两个进程在运行,有木有。
multiprocess
双进程相互守护
双进程A和B相互守护,当A进程检测到B进程退出时,A进程重新启动B进程,同理当A进程退出时,B重新启动A进程。进程的运行状态的检测是实现进程守护的关键点,比较容易想到的方案有:
通过轮训的方式查询远端进程的状态
通过发送心跳包来看是否能接受应答,无应答,远端进程可能已经退出
在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
1.
int
daemon (
int
nochdir,
int
noclose);
2.
将程序将运行在后台,成为一个daemon程序,而linux下大多的服务都是以此方式运行的
1.
int
getopt_long(
int
argc,
char
*
const
argv[],
const
char
*optstring,
2.
const
struct option *longopts,
int
*longindex);
3.
参数解析的帮助函数,当命令参数多个而且有些课选参数时,如果只按顺序接受参数容易混乱,如果按参数名来对应则便利很多,可以参考一下
1.
FILE popen(
const
char
command ,
const
char
*type );
2.
popen()创建一个管道,fork一个新的进程执行shell命令,popen()的返回值是个标准I/O流,必须由pclose来终止。前面提到这个流是单向的(只能用于读或写)。向这个流写内容相当于写入该命令的标准输入,命令的标准输出和调用popen()的进程相同;与之相反的,从流中读数据相当于读取命令的标准输出,命令的标准输入和调用popen()的进程相同.
1.
int
system(
const
char
* string);
2.
函数说明
3.
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
4.
返回值
守护流程图
守护流程图
守护的程序代码
01.
int
main(
int
argc,
char
* argv[] )
02.
{
03.
signal(SIGTERM, SIG_IGN);
04.
05.
const
char
*process_name = NULL;
06.
const
char
*package_name = NULL;
07.
const
char
*activity_name = NULL;
08.
int
interval_sec =
30
;
09.
10.
struct option options[] =
11.
{
12.
{
"process_name"
, required_argument,
0
,
'p'
},
13.
{
"package_name"
, required_argument,
0
,
'a'
},
14.
{
"activity_name"
, required_argument,
0
,
'c'
},
15.
{
"interval_sec"
, required_argument,
0
,
'i'
},
16.
{
0
,
0
,
0
,
0
}
17.
};
18.
19.
int
c;
20.
21.
for
(;;)
22.
{
23.
c = getopt_long(argc, argv,
"p:a:c:i:"
, options, NULL);
24.
if
(c == -
1
)
25.
{
26.
break
;
27.
}
28.
switch
(c)
29.
{
30.
case
'p'
:
31.
process_name = optarg;
32.
break
;
33.
case
'a'
:
34.
package_name = optarg;
35.
break
;
36.
case
'c'
:
37.
activity_name = optarg;
38.
break
;
39.
case
'i'
:
40.
interval_sec = atoi(optarg);
41.
break
;
42.
default
:
43.
exit(EXIT_FAILURE);
44.
}
45.
}
46.
47.
if
(process_name == NULL || package_name == NULL || activity_name == NULL)
48.
exit(EXIT_FAILURE);
49.
50.
daemon(
1
,
1
);
51.
52.
run_service(process_name, package_name, activity_name,
10
);
53.
54.
return
0
;
55.
}
run_service的实现
01.
int
chk_process(
const
char
*process_name)
02.
{
03.
FILE *stream;
04.
char
*line = NULL;
05.
size_t len =
0
;
06.
ssize_t read_len;
07.
08.
stream = popen(
"ps"
,
"r"
);
09.
if
(stream == NULL)
10.
return
-
1
;
11.
12.
int
exists =
0
;
13.
while
( (read_len = getline(&line, &len, stream)) != -
1
)
14.
{
15.
int
len = strlen(line);
16.
char
*cmd = line + len;
17.
while
( len >
0
)
18.
{
19.
len--;
20.
if
( *cmd ==
' '
)
21.
{
22.
cmd++;
23.
break
;
24.
}
25.
26.
cmd--;
27.
}
28.
29.
if
( strncmp(cmd, process_name, strlen(process_name)) ==
0
)
30.
{
31.
exists =
1
;
32.
break
;
33.
}
34.
}
35.
36.
pclose( stream );
37.
if
( line != NULL )
38.
free(line);
39.
40.
return
exists;
41.
}
42.
43.
void
run_service(
const
char
*process_name,
const
char
*package_name,
const
char
*activity_name,
int
interval_sec)
44.
{
45.
while
(
1
)
46.
{
47.
if
( chk_process(process_name) ==
0
)
48.
{
49.
char
*pkg_activity_name = NULL;
50.
// 格式化命令
51.
asprintf(&pkg_activity_name,
"/system/bin/am start --user 0 -n %s/%s"
, package_name, activity_name);
52.
system(pkg_activity_name);
// 执行命令启动app
53.
free(pkg_activity_name);
54.
}
55.
// sleep 指定时间间隔
56.
sleep(interval_sec);
57.
}
58.
59.
return
;
60.
}
编译成功后生成xxx,重命名为xxx.so,把文件拷贝到libs下,这样安装后该文件会被同动态库一起拷贝到data/data/app_package目录下,编写拷贝和chmod相关逻辑的代码,大概流程如下
path = "/data/data/" +packageName; // 安装后的app路径
执行shell命令:dd if= path+lib/xxx.so of=path/xxx ;//拷贝到app路径下,重命名为xxx
赋可执行权限 chmod 777 path/xxx;
运行可执行文件 path/xxx -p process_name -a pkgname ..(别的参数)
需要注意的一点:
这里的操作都是通过执行shell来完成的,需要先cd到app 路径下,才会有读写权限。
01.
public
static
boolean
execCommand(String command, String packageName) {
02.
Process process =
null
;
03.
try
{
04.
process = Runtime.getRuntime().exec(
"sh"
);
//获得shell.
05.
DataInputStream inputStream =
new
DataInputStream(process.getInputStream());
06.
DataOutputStream outputStream =
new
DataOutputStream(process.getOutputStream());
07.
08.
//保证在command在自己的数据目录里执行,才有权限写文件到当前目录
09.
outputStream.writeBytes(
"cd /data/data/"
+ packageName +
"\n"
);
10.
11.
outputStream.writeBytes(command +
" \n"
);
12.
outputStream.writeBytes(
"exit\n"
);
13.
outputStream.flush();
14.
process.waitFor();
15.
16.
byte
[] buffer =
new
byte
[inputStream.available()];
17.
inputStream.read(buffer);
18.
String s =
new
String(buffer);
19.
}
catch
(Exception e) {
20.
return
false
;
21.
}
22.
return
true
;
23.
}
编好代码打包测试时,通过app管理界面停止app的运行,看看app是否会被重新启动。
参考
http://www.cnblogs.com/caosiyang/archive/2012/06/25/2560976.html
更多推荐
所有评论(0)