Android卸载监听详解

来源:互联网 发布:手机淘宝5.8.0旧版本 编辑:程序博客网 时间:2024/05/17 21:50

目前市场上比较多的应用在用户卸载后会弹出意见反馈界面,比如360手机卫士,腾讯手机管家,应用宝等等,虽然本人不太认同其交互方式,但是在技术实现上还是可以稍微研究下的。其实要实现这个功能,最主要的就是监听到自己被卸载,然后弹出一个网页,具体思路如下:

1. fork 监听进程

虽然应用程序被卸载的时候会有系统广播,但是作为被卸载的应用,挂都挂掉了,这个广播也就没有意义了,所幸的是,我们可以通过当前进程调用fork函数去创建一个子进程来监听卸载。fork函数一次调用会返回两个值,子进程返回0,父进程返回子进程ID,出错则返回-1,函数原型:pid_t fork(void)

2. 创建监听文件

android应用是基于linux的,我们可以通过linux中的inotify机制来监听应用的卸载。inotify是linux内核用于通知用户空间文件系统变化的机制,文件的添加或卸载等事件都能够及时捕获到,要监听文件卸载一般三个步骤:

  • 创建inotify实例:int fileDescriptor = inotify_init();
  • 注册监听事件:int watchDescriptor = inotify_add_watch(fileDescriptor,path, IN_DELETE); 这个函数包含三个参数,分别是inotify实例,监听文件路径,以及事件掩码,在这里我们关注的是删除事件,所以用IN_DELETE;
  • 调用read函数开始监听:size_t len = read(int, void *, size_t); read函数也有三个参数,分别是inotify实例,inotify_event 结构的数组指针,以及要读取的事件的总长度。

关于inotify这部分的内容,可以参考这篇博客:http://blog.csdn.net/myarrow/article/details/7096460

3. 打开网页

打开网页很简单,直接调用execlp(“am”, “am”, “start”, “—user”, userSerialNumber, “-a”,”android.intent.action.VIEW”, “-d”, url, (char *) NULL);唯一要注意的是userSerialNumber,android API 17 引入了多用户支持,所以需要userSerialNumber来标识用户。获取userSerialNumber方法如下:

1234567891011121314151617181920
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;  }

以上内容基本解决了卸载监听的问题,但这肯定是不够的,还有很多细节需要考虑,先上代码,再来慢慢分析:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
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;  }

问题一:监听哪个文件?

其实这个问题在于,如何判断应用是被卸载,还是覆盖安装或只是清除了数据,很显然,如果是监听应用所在目录,那当应用被覆盖安装时,马上就会监听到卸载事件,弹出网页,这个情况肯定是需要避免的。我们知道,应用程序被覆盖安装时,数据文件是不会被删掉的,那是否就可以监听这个目录?当然也是不行的,因为一旦用户执行了清除数据操作,也会弹出网页。所以,最好的办法是自己创建一个监听文件,当用户清除数据时,判断应用所在目录存不存在,若存在则说明是清除数据操作,然后重新监听,如果用户是覆盖安装,则不会触发此监听事件。

12345678910111213141516
/**  * 创建监听文件,避免覆盖安装被判断为卸载事件*/  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;  }

问题二:如何判断监听进程是否存在?

要实现监听功能,我们必须在合适的时间点去创建监听进程,一般可以选在应用第一次开启以及监听到开机广播的时候,那么问题来了,如果用户每次打开软件的时候都去创建监听进程,这显然是不科学的,所以我们应该在创建进程前先判断该监听进程是否存在,如果不存在才创建:

123456789101112131415161718
/**  * 设置软件卸载时弹出网页的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);      }  }
1234567891011121314151617181920212223242526
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, "<init>", "([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

0 0
原创粉丝点击