前言
在我们使用360手机助手的时候,当卸载360以后,会打开一个反馈的网页,虽然猜想大多数人和我一样直接关了,但是其实现却是一个很有意思的东西,应用都被卸载完了,居然还能打开网页,下面就来介绍下其的实现(Android 5.0以下有效)。
实现
通过效果图我们可以看到,当我们开启卸载监听以后,在最近程序列表里面杀死了我们的应用,可是当卸载的时候还是能打开我们指定的网页。
其实是我通过在native层fork了一个进程,在Android 5.0之前,通过任务栏只能杀死应用的进程,native进程还是能够继续运行。
那么当什么时候我们的native进程知道应用被卸载了呢?其实应用安装就是解压apk的过程,应用数据都放在/data/data/package-name/目录下面,如果这个目录没了,那么就说明apk被卸载了,我们可以通过while(1)来循环监听,不过linux给我们提供了一个更好的方法,使用inotify监控文件动作。具体用法可以参考:江淼的Blog
通过上面的分析我们就清楚了大致步骤了,开启一个native进程通过inotify监听/data/data/package-name/目录是否存在,当目录被删除的时候打开一个网页即可。具体实现如下,注释已经很清晰。
/*** * 思路参考链接: * http://blog.csdn.net/jiangwei0910410003/article/details/42177117 * 文件监听参考链接: * http://www.jiangmiao.org/blog/2179.html */ extern "C" { int sdk_int = 0; bool has_listener = false; void initTool(JNIEnv *env) { jclass version = env->FindClass("android/os/Build$VERSION"); jfieldID sdk_int_id = env->GetStaticFieldID(version, "SDK_INT", "I"); sdk_int = env->GetStaticIntField(version, sdk_int_id); LOGI("sdk version:%d", sdk_int); } JNIEXPORT void JNICALL Java_org_ndk_ndkfirst_UninstallTool_setUninstallAction(JNIEnv *env, jclass jcls, jstring path, jstring url) { initTool(env); LOGI("target:%s", env->GetStringUTFChars(path, NULL)); //5.0及以上native进程无法使用am等命令。 if (sdk_int >= 21 || has_listener) { LOGE("stop uninstall"); return; } pid_t pid = fork(); LOGF("pid = %d", pid); if (pid == -1) { LOGE("%s", "fork error!!!"); } else if (pid == 0) { //子进程 // 创建一个inotify实例 int inotify = inotify_init(); if (inotify < 0) { LOGE("%s", "inotify_init() error!!!"); _exit(-1); } //监听/data/data/package-name/是否被删除 int watch_id = inotify_add_watch(inotify, env->GetStringUTFChars(path, NULL), IN_DELETE); if (watch_id < 0) { LOGE("%s", "inotify_add_watch error!!!"); _exit(-1); } LOGI("begin listener ==》 %s", env->GetStringUTFChars(path, NULL)); has_listener = true; inotify_event event; //读取事件,这里会阻塞掉,一直等待 read(inotify, &event, sizeof(event)); //读取到了,说明被应用被卸载了 LOGI("%s", "package-uninstall"); //停止监听 inotify_rm_watch(inotify, watch_id); const char *targetUrl = env->GetStringUTFChars(url, NULL); //执行命令 if (sdk_int <= 16) { execlp( "am", "am", "start", "-a", "android.intent.action.VIEW", "-d", targetUrl, (char *) NULL); } else { //4.2以上的系统由于用户权限管理更严格,需要加上 --user 0 execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d", targetUrl, (char *) NULL); } } else { //父进程直接退出,使子进程被init进程领养,以避免子进程僵死 } } }
Demo下载地址:github
参考链接:生死看淡,不服就干! 、江淼的Blog