目前市场上比较多的应用在用户卸载后会弹出意见反馈界面,比如360手机卫士,腾讯手机管家,应用宝等等,虽然本人不太认同其交互方式,但是在技术实现上还是可以稍微研究下的。其实要实现这个功能,最主要的就是监听到自己被卸载,然后弹出一个网页,具体思路如下:
private String getUserSerial(Context context) {
Object userManager = context.getSystemService("user");
if (userManager == null) {
return null;
}
try {
Method myUserHandleMethod = android.os.Process.class.getMethod(
"myUserHandle", (Class>[]) null);
Object myUserHandle = myUserHandleMethod.invoke(
android.os.Process.class, (Object[]) null);
Method getSerialNumberForUser = userManager.getClass().getMethod(
"getSerialNumberForUser", myUserHandle.getClass());
long userSerial = (Long) getSerialNumberForUser.invoke(userManager,
myUserHandle);
return String.valueOf(userSerial);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
JNIEXPORT int JNICALL Java_com_uninstall_browser_sdk_UninstallBrowserSDK_init(
JNIEnv * env, jobject thiz, jstring arg0, jstring arg1,
jstring userSerial) {
const char *pkgName = (*env)->GetStringUTFChars(env, arg0, 0);
const char *url = (*env)->GetStringUTFChars(env, arg1, 0);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "init jni");
// fork子进程,以执行轮询任务
pid_t pid = fork();
if (pid < 0) {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "fork failed");
} else if (pid == 0) {
// 子进程注册目录监听器
int fileDescriptor = inotify_init();
if (fileDescriptor < 0) {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg",
"inotify_init failed");
exit(1);
}
int watchDescriptor;
watchDescriptor = inotify_add_watch(fileDescriptor,
get_watch_file(pkgName), IN_DELETE);
if (watchDescriptor < 0) {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg",
"inotify_add_watch failed");
exit(1);
}
// 分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL) {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "malloc failed");
exit(1);
}
// 开始监听
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "start observer");
while (1) {
size_t readBytes = read(fileDescriptor, p_buf,
sizeof(struct inotify_event));
// read会阻塞进程,走到这里说明收到监听文件被删除的事件,但监听文件被删除,可能是卸载了软件,也可能是清除了数据
FILE *p_appDir = fopen(pkgName, "r");
// 已经卸载
if (p_appDir == NULL) {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "uninstalled");
inotify_rm_watch(fileDescriptor, watchDescriptor);
break;
}
// 未卸载,可能用户执行了"清除数据",重新监听
else {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "clean data");
fclose(p_appDir);
int watchDescriptor = inotify_add_watch(fileDescriptor,
get_watch_file(pkgName), IN_DELETE);
if (watchDescriptor < 0) {
__android_log_print(ANDROID_LOG_INFO, "JNIMsg",
"inotify_add_watch failed");
free(p_buf);
exit(1);
}
}
}
free(p_buf);
if (userSerial == NULL) {
// 执行命令am start -a android.intent.action.VIEW -d $(url)
execlp("am", "am", "start", "-a", "android.intent.action.VIEW",
"-d", url, (char *) NULL);
} else {
// 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)
const char *userSerialNumber = (*env)->GetStringUTFChars(env,
userSerial, 0);
execlp("am", "am", "start", "--user", userSerialNumber, "-a",
"android.intent.action.VIEW", "-d", url, (char *) NULL);
(*env)->ReleaseStringUTFChars(env, userSerial, userSerialNumber);
}
execlp("am", "am", "start", "--user", "0", "-a",
"android.intent.action.VIEW", "-d", url, (char *) NULL);
(*env)->ReleaseStringUTFChars(env, arg0, pkgName);
(*env)->ReleaseStringUTFChars(env, arg1, url);
} else {
(*env)->ReleaseStringUTFChars(env, arg0, pkgName);
(*env)->ReleaseStringUTFChars(env, arg1, url);
return pid;
}
return -1;
}
/**
* 创建监听文件,避免覆盖安装被判断为卸载事件
*/
char* get_watch_file(const char* package) {
int len = strlen(package) + strlen("watch.tmp") + 1;
char* watchPath = (char*) malloc(sizeof(char) * len);
sprintf(watchPath, "%s/%s", package, "watch.tmp");
FILE* file = fopen(watchPath, "r");
if (file == NULL) {
file = fopen(watchPath, "w+");
chmod(watchPath, 0755);
}
fclose(file);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "创建文件目录 : %s", watchPath);
return watchPath;
}
/**
* 设置软件卸载时弹出网页的URL
*/
public void setUninstallWebUrl(Context context, String url) {
if (url == null || url.length() == 0) {
return;
}
int mMonitorPid = ConfigDao.getInstance(context).getMonitorPid();
if (mMonitorPid > 0 && !getNameByPid(mMonitorPid).equals("!")) {
Log.i("stefanli", "监控进程存在");
return;
} else {
int mPid = init("/data/data/" + context.getPackageName(), url, getUserSerial(context));
Log.i("stefanli", "监控进程ID:" + mPid);
Log.i("stefanli", "监控进程名称:" + getNameByPid(mPid));
ConfigDao.getInstance(context).setMonitorPid(mPid);
}
}
JNIEXPORT jstring JNICALL Java_com_uninstall_browser_sdk_UninstallBrowserSDK_getNameByPid(
JNIEnv * env, jobject thiz, jint pid) {
char task_name[100];
getPidName(pid, task_name);
jsize len = strlen(task_name);
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env, "GB2312");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "",
"([BLjava/lang/String;)V");
jbyteArray barr = (*env)->NewByteArray(env, len);
(*env)->SetByteArrayRegion(env, barr, 0, len, (jbyte*) task_name);
return (jstring) (*env)->NewObject(env, clsstring, mid, barr, strencode);
}
void getPidName(pid_t pid, char *task_name) {
char proc_pid_path[BUF_SIZE];
char buf[BUF_SIZE];
sprintf(proc_pid_path, "/proc/%d/status", pid);
FILE* fp = fopen(proc_pid_path, "r");
if (NULL != fp) {
if (fgets(buf, BUF_SIZE - 1, fp) == NULL) {
fclose(fp);
}
fclose(fp);
sscanf(buf, "%*s %s", task_name);
}
}
Demo下载地址: http://download.csdn.net/detail/a378881925/8373409