Android App Uninstall Watcher

很多应用在卸载后都会弹出一个网页做用户卸载反馈,这就需要监听 App 的卸载,但是应用一旦卸载就不会再执行任何程序了,如何才能弹出网页,答案就是在应用开启时就 fork 出一个子进程来,在进程中对 App 进行卸载监听。在 linux 中有个东西叫 inotify,可以对指定的文件进行监听(包括修改,删除等等),基本的流程就是 inotify_init->inotify_add_watch->inotify_event,在 inotify_event 读取操作的时候是阻塞的,一直会等到指定的文件变动后才会往下执行。

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
int main(int argc, char *argv[])
{
char *app_dir = "/data/data/com.xx.yy";
char *watch_file_path = str_stitching(app_dir, "/uninstall.watch");

pid_t pid = fork();
if (pid < 0)
{
return;
}
else if (pid == 0)
{
/* inotify init */
int fd = inotify_init();
if (fd < 0)
{
exit(EXIT_FAILURE);
}

int w_fd = open(watch_file_path, O_RDWR | O_CREAT | O_TRUNC,
S_IRWXU | S_IRWXG | S_IRWXO);
if (w_fd < 0)
{
exit(EXIT_FAILURE);
}

close(w_fd);

/* add watch in inotify */
int watch_fd = inotify_add_watch(fd, watch_file_path, IN_DELETE);
if (watch_fd < 0)
{
exit(EXIT_FAILURE);
}

void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL)
{
LOGD(LOG_TAG, "malloc inotify event failed");
exit(EXIT_FAILURE);
}

/* read will block process */
read(fd, p_buf, sizeof(struct inotify_event));

free(p_buf);
inotify_rm_watch(fd, IN_DELETE);

open_browser(url);
}
}

App Daemon 一样,使用命令行的方式来调用 wathcer,所有这里也 int main(int argc, char *argv[]),最终将这个 c 文件编译成可执行文件。为了要监听某个文件的删除,首先在 /data/data// 下新建了一个 uninstall.watch 文件,在后面我们将对此文件进行监听,然后同样的,为了不妨碍主进程,这里 fork 出一个子进程,在子进程里面进行操作。接下来是关键的地方:调用 inotify_init() 初始化 inotify,接着将刚刚新建的 uninstall.watch 文件加入 inotify_add_watch(fd, watch_file_path, IN_DELETE) 中监听,这里是监听该文件的删除(第三个参数还可以是 IN_CREATE, IN_MODIFY, IN_MOVED_TO 等等,根据需求更改),接下来就是读取 read(fd, p_buf, sizeof(struct inotify_event)),这个函数会一直阻塞直到条件满足才会继续往下执行,最后释放并加入打开浏览器。打开浏览器也比较简单了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* open browser with specified url */
void open_browser(char *url)
{
/* the url cannot be null */
if (url == NULL || strlen(url) < 4) {
return;
}

/* get the sdk version */
char value[8] = "";
__system_property_get("ro.build.version.sdk", value);

int version = atoi(value);
/* is the version is greater than 17 */
if (version >= 17 || version == 0)
{
execlp("am", "am", "start", "--user", "0",
"-a", "android.intent.action.VIEW", "-d", url, (char *)NULL);
}
else
{
execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", url, (char *)NULL);
}
}

App Daemon 类似,也是使用 execlp 来调用 am 命令打开默认浏览器,最后就会弹出一个指定的网页了。
  以上是卸载监听的基本流程,但我在实际操作过程中遇到了比如调试应用,实际上是覆盖,但这时候也会弹出网页等等,所有在子进程中的操作有所改动和优化,并在打开浏览器前使用了 libcurl 请求服务器,以满足不需要打开网页的需求,具体请查看 wathcer.c

ps: 此方法和 App Daemon 一样由于系统原因不能适配所有手机,请注意。