想要实现应用的卸载反馈看似可行的方案有很多,比如监听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