Android NDK初探:应用卸载反馈实现

想要实现应用的卸载反馈看似可行的方案有很多,比如监听Android的系统广播一类,完全可以监听到其他软件的卸载事件,但是如果想要在卸载后执行某些操作,java层的方法可能无能为力,因为随着应用的卸载,这些方法也会随着Activity一个个被干掉而失效,于是在网上查了一下,有大神提出可以用基于C的子进程机制解决这一问题,因为在Linux中,一个进程在fork了一个子进程之后如果被干掉,子进程会被托管而不是马上被干掉,因此在应用卸载后仍可以在这个子进程中进行一些操作,这也是我这个实现卸载后反馈的原理。

——首先介绍下NDK插件的安装,可以去官网下载一个NDK插件安装包,是个exe,双击就可以运行啦,会在指定目录下生成一个NDK的根目录,在Eclipse中:

Windows->pref->Android->NDK 设置一下NDK的根目录

——下载一个和eclipse版本对应的CDT插件,这个插件可以让Eclipse能够编译C代码,如果不用Eclipse编译的话也可以使用独立的编译工具Cygwin,这样可以在Eclipse外部编译生成SO文件,在copy到项目中,效果是一样的;


——安装了CDT之后,如果要编译C代码,就可以在Eclipse项目中右键-->Android-Tools-->添加native方法,写一个方法名,之后会自动在工程中生成一个JNI文件夹,其中会有一个刚才指定过名称的.cpp文件和一个Android.mk文件,如果是C代码,可以把.cpp改为.c,然后C代码就可以写在这里了, .mk文件类似于一个配置文件,在有多个c方法或者引入外部so文件时可以修改这个文件;


——在java代码中声明一个native方法:public native int uninstall(String userSerial, int sdkVersion);

之后用cmd在工程的src目录下执行javah -jni 包名+类名(MainActivity),成功后会在src目录下生成一个.h文件,复制到jni目录中,打开.h文件,里面会有一个名字长长的方法,这个是根据我们刚才声明的native方法所生成的:

JNIEXPORT jint JNICALL Java_com_example_uninstalldemo_MainActivity_uninstall(JNIEnv *, jobject, jstring, jint);

——在C文件中先引入这个.h文件,之后就可以实现我们所声明的native方法啦:

/* 头文件begin */
#include <stdio.h>
#include <jni.h>
#include <malloc.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include "com_example_uninstalldemo_MainActivity.h"
#include <android/log.h>


/* 头文件end */

#ifdef __cplusplus
extern "C"
{
#endif

/* 内全局变量begin */
static char TAG[] = "MainActivity.uninstall";
static jboolean isCopy = JNI_TRUE;

static const char APP_DIR[] = "/data/data/com.example.uninstalldemo/lib/libuninstall.so";
static const char APP_FILES_DIR[] = "/data/data/com.example.uninstalldemo/files<span style="font-family: Arial, Helvetica, sans-serif;">";</span>
static const char APP_OBSERVED_FILE[] = "/data/data/com.example.uninstalldemo/files/observedFile";
static const char APP_LOCK_FILE[] = "/data/data/com.example.uninstalldemo/files/lockFile";
/* 内全局变量 */

JNIEXPORT int JNICALL <span style="white-space: pre;">Java_com_</span>example<span style="white-space: pre;">_</span>uninstalldemo<span style="white-space: pre;">_MainActivity_uninstall</span>(JNIEnv *env, jobject obj, jstring userSerial,jint sdkVersion)
{
    jstring tag = (*env)->NewStringUTF(env, TAG);


    // fork子进程,以执行轮询任务
    pid_t pid = fork();
    if (pid < 0)
    {

        exit(1);
    }
    else if (pid == 0)
    {
        // 若监听文件所在文件夹不存在,创建
        FILE *p_filesDir = fopen(APP_FILES_DIR, "r");
        if (p_filesDir == NULL)
        {
        	fclose(p_filesDir);
        	int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH);
            if (filesDirRet == -1)
            {
                exit(1);
            }
        }

        // 若被监听文件不存在,创建文件
        FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
        if (p_observedFile == NULL)
        {
            p_observedFile = fopen(APP_OBSERVED_FILE, "w");
        }
        fclose(p_observedFile);

        // 创建锁文件,通过检测加锁状态来保证只有一个卸载监听进程
        int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY);
        if (lockFileDescriptor == -1)
        {
            lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT);
        }
        int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB);
        if (lockRet == -1)
        {

            exit(0);
        }

        // 分配空间,以便读取event
        void *p_buf = malloc(sizeof(struct inotify_event));
        if (p_buf == NULL)
        {

            exit(1);
        }
        // 分配空间,以便打印mask
        int maskStrLength = 7 + 10 + 1;// mask=0x占7字节,32位整形数最大为10位,转换为字符串占10字节,'\0'占1字节
        char *p_maskStr = malloc(maskStrLength);
        if (p_maskStr == NULL)
        {
            free(p_buf);

            exit(1);
        }

        // 开始监听

        // 初始化
        int fileDescriptor = inotify_init();
        if (fileDescriptor < 0)
        {
            free(p_buf);
            free(p_maskStr);

            exit(1);
        }

        // 添加被监听文件到监听列表
        int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_DELETE_SELF);//IN_ALL_EVENTS);
        if (watchDescriptor < 0)
        {
            free(p_buf);
            free(p_maskStr);
            exit(1);
        }

<span style="white-space:pre">	</span>while(1){
                // read会阻塞进程
                size_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));


                // 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在
                if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask)
                 {
                     //FILE *p_appDir = fopen(APP_DIR, "r");
                     // 未卸载,可能用户执行了"清除数据"
                     if ( access(APP_DIR,0) == 0){

                    <span style="white-space:pre">	</span> // 重新创建被监听文件,并重新监听
                    <span style="white-space:pre">	</span> // 若被监听文件不存在,创建文件
                    <span style="white-space:pre">	</span> inotify_rm_watch(fileDescriptor, watchDescriptor);
                    <span style="white-space:pre">	</span> FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
                    <span style="white-space:pre">	</span> if (p_observedFile == NULL)
                    <span style="white-space:pre">	</span> {
                    <span style="white-space:pre">	</span>      p_observedFile = fopen(APP_OBSERVED_FILE, "w");
                    <span style="white-space:pre">	</span> }
                    <span style="white-space:pre">	</span> fclose(p_observedFile);


                    <span style="white-space:pre">	</span> //LOGE(LOG_TAG,"开始监听");

                    <span style="white-space:pre">	</span> int fileDescriptor =inotify_init();
                    <span style="white-space:pre">	</span> if (fileDescriptor < 0)
                    <span style="white-space:pre">	</span> {
                    <span style="white-space:pre">	</span>      free(p_buf);
                    <span style="white-space:pre">	</span>      free(p_maskStr);
                    <span style="white-space:pre">	</span>      exit(1);
                    <span style="white-space:pre">	</span> }


                    <span style="white-space:pre">	</span> int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_DELETE_SELF);
                    <span style="white-space:pre">	</span> if (watchDescriptor < 0){
                    <span style="white-space:pre">	</span>       free(p_buf);
                    <span style="white-space:pre">	</span>       free(p_maskStr);
                    <span style="white-space:pre">	</span>       exit(1);
                    <span style="white-space:pre">	</span> }
                     }else if(access(APP_DIR,0) == -1)// 确认已卸载
                     {
                    <span style="white-space:pre">	</span> inotify_rm_watch(fileDescriptor, watchDescriptor);
                    <span style="white-space:pre">	</span> break;
                     }
             }
        }
        // 释放资源
        free(p_buf);
        free(p_maskStr);

        // 停止监听

        if (userSerial == NULL)
        {
            // 执行命令am start -a android.intent.action.VIEW -d $(url)
            execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *)NULL);
        }
        else
        {
            // 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)
            execlp("am", "am", "start", "--user", (*env)->GetStringUTFChars(env, userSerial, &isCopy), "-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *)NULL);
        }
    }
    else
    {
        // 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pid
        return pid;
    }
}

#ifdef __cplusplus
}
#endif
我们通过在C代码中fork一个子进程,在这里我们通过inotify机制监听我们指定的文件是否被删除(原来本想监听data/data/包名 这个文件的IN_DELETE_SELF事件的,但是应用新旧版覆盖安装时会出现问题,有大神知道的还望帮忙解答),这个文件是我们新建专门用来监听应用卸载事件的,我们的应用启动后就会在data/data中新建了这个文件,应用卸载它就会被删除,此时会触发我们的卸载事件,这里是用默认浏览器打开百度页面;


——在Eclipse中的C代码会有一些问题,会显示好多红线,但不影响编译,我们就工程右键->Properties->C/C++ General ->Code Analysis ->Use project settings 把Method和Symbol这两个勾掉就可以了;


——在java代码中调用我们的方法,需要先声明一个static代码块,加载我们的lib:

static {
		System.loadLibrary("uninstall");
	}

——随后直接调用就可以了,由于API17以后打开浏览器的方式和之前不一样,所以在main中调用时要获取userSerial并传入uninstall方法;


——编译,会在libs目录生成.so文件,由于.so是要分平台的,如果我们可以再新建一个Application.mk,在其中设置我们要生成那些平台的.so(x86 armeabi armeabi-v7a mips):APP_ABI := x86 armeabi

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值