NDK-实现监听应用卸载弹出网页

/ 0评 / 0

前言

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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注