很多应用在卸载后都会弹出一个网页做用户卸载反馈,这就需要监听 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) { 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);
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(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
| void open_browser(char *url) { if (url == NULL || strlen(url) < 4) { return; }
char value[8] = ""; __system_property_get("ro.build.version.sdk", value);
int version = atoi(value); 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 一样由于系统原因不能适配所有手机,请注意。