前言
在我们使用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