今天思考的一个问题是如何让应用监控到自己被卸载,然后在卸载的时候执行一些操作,比如向服务器发送一个报告,或者引导用户到一个卸载反馈页上去,比如360手机助手那样。然后就在网上上看到了这篇文章android应用监控被卸载还有它的续集,github上有人根据他的方案做出了一个demo:https://github.com/sevenler/Uninstall_Statics。这个demo我测试过,是可用的。只要不是用户通过进程管理器把整个进程都kill掉,就能够在应用自身被卸载的时候调用c写的native代码,感觉上是利用了一个时间差吧。
blog上介绍了其原理,通过java进程调用native代码,然后fork出一个子进程,在子进程中利用linux内核的inotify机制来监听文件夹的状态,如果一旦被删除就认定为应用被卸载了,就触发后续的代码。这里的inotify机制的利用很有意思,避免了轮询带来对系统资源的消耗和浪费。下面是摘出来的代码:
//fork子进程,以执行轮询任务
pid_t pid = fork();
if (pid < 0)
{
//出错log
LOG_ERROR((*env)->GetStringUTFChars(env, tag, &b_IS_COPY)
, (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork failed !!!"), &b_IS_COPY));
}
else if (pid == 0)
{
//子进程注册"/data/data/pym.test.uninstalledobserver"目录监听器
int fileDescriptor = inotify_init();
if (fileDescriptor < 0)
{
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY)
, (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_init failed !!!"), &b_IS_COPY));
exit(1);
}
int watchDescriptor;
watchDescriptor = inotify_add_watch(fileDescriptor, "/data/data/pym.test.uninstalledobserver", IN_DELETE);
if (watchDescriptor < 0)
{
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY)
, (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &b_IS_COPY));
exit(1);
}
//分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL)
{
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY)
, (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &b_IS_COPY));
exit(1);
}
//开始监听
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY)
, (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start observer"), &b_IS_COPY));
size_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));
//read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
free(p_buf);
inotify_rm_watch(fileDescriptor, IN_DELETE);
//目录不存在log
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY)
, (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "uninstalled"), &b_IS_COPY));
//执行命令am start -a android.intent.action.VIEW -d http://shouji.360.cn/web/uninstall/uninstall.html
execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://shouji.360.cn/web/uninstall/uninstall.html", (char *)NULL);
在手机上查看进程,可以看到一个主进程,一个fork出来的进程,如下图:
结束主进程之后,子进程可以依然存在,并完成预定的任务。