應用監聽自身卸載,彈出用戶反饋調查(下)——使用Inotify監聽安裝目錄
來自: http://blog.csdn.net//guijiaoba/article/details/50161449
轉載出處:http://blog.csdn.net/allen315410/article/details/42555415
在上一篇博客中,我們講了一個小小的案例,用NDK監聽應用程序自身卸載,并且打開內置瀏覽器加載用戶調用頁面。關于監聽應用程序自身卸載的原理和實現方案可以在上篇博客中找到,地址是:http://blog.csdn.net/allen315410/article/details/42521251,這里就不再復述了。
值得注意的是,在上篇博客中我也已經引述了一個案例中存在的問題,就是在監聽應用程序安裝目錄是否被刪除時,使用了while(true)這種死循環,讓C代碼每隔1秒鐘去自動執行一次檢查應用程序安裝目錄是否還存在,這樣做效果是完全可以實現的,但是弊端也是顯而易見的,由于使用了死循環,這樣代碼是不環保的,不可避免的重復執行,重復打印LOG,占用cpu計算資源,這是一種不太好的解決方案。
在這里,我來介紹一個比較好的解決方案,就是使用Linux系統的一個內核特性——Inotify,來監聽應用程序安裝目錄的變化,inotify 是一種文件系統的變化通知機制,如文件增加、刪除等事件可以立刻讓用戶態得知,這種機制是為了彌補Linux系統在桌面領域的不足而產生,在Linux2,.6內核中被添加,值得慶幸的是我們偉大的Android系統就是構建在Linux2.6內核的基礎上的,所以Android里也就包含了Inotify機制。
關于Inotify:
在用戶態,inotify 通過三個系統調用和在返回的文件描述符上的文件 I/O 操作來使用,使用 inotify 的第一步是創建 inotify 實例:
- int fd = inotify_init ();
文件系統的變化事件被稱做 watches 的一個對象管理,每一個 watch 是一個二元組(目標,事件掩碼),目標可以是文件或目錄,事件掩碼表示應用希望關注的 inotify 事件,每一個位對應一個 inotify 事件。Watch 對象通過 watch描述符引用,watches 通過文件或目錄的路徑名來添加。目錄 watches 將返回在該目錄下的所有文件上面發生的事件。
下面函數用于添加一個 watch:
- int wd = inotify_add_watch (fd, path, mask);
下面是mask事件掩碼的可選值:
IN_ACCESS,即文件被訪問
IN_MODIFY,文件被 write
IN_ATTRIB,文件屬性被修改,如 chmod、chown、touch 等
IN_CLOSE_WRITE,可寫文件被 close
IN_CLOSE_NOWRITE,不可寫文件被 close
IN_OPEN,文件被 open
IN_MOVED_FROM,文件被移走,如 mv
IN_MOVED_TO,文件被移來,如 mv、cp
IN_CREATE,創建新文件
IN_DELETE,文件被刪除,如 rm
IN_DELETE_SELF,自刪除,即一個可執行文件在執行時刪除自己
IN_MOVE_SELF,自移動,即一個可執行文件在執行時移動自己
IN_UNMOUNT,宿主文件系統被 umount
IN_CLOSE,文件被關閉,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
IN_MOVE,文件被移動,等同于(IN_MOVED_FROM | IN_MOVED_TO)
注:上面所說的文件也包括目錄。
下面的函數用于刪除一個 watch:
- int ret = inotify_rm_watch (fd, wd);
文件事件用一個 inotify_event 結構表示,它通過由 inotify_init() 返回的文件描述符使用通常文件讀取函數 read 來獲得:
- struct inotify_event {
- __s32 wd; /* watch descriptor */
- __u32 mask; /* watch mask */
- __u32 cookie; /* cookie to synchronize two events */
- __u32 len; /* length (including nulls) of name */
- char name[0]; /* stub for possible name */
- };
通過 read 調用可以一次獲得多個事件,只要提供的 buf 足夠大。
- size_t len = read (fd, buf, BUF_LEN);
可以在函數 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 來得到當前隊列的長度。close(fd)將刪除所有添加到 fd 中的 watch 并做必要的清理。
- int inotify_init (void);
- int inotify_add_watch (int fd, const char *path, __u32 mask);
- int inotify_rm_watch (int fd, __u32 mask);
代碼實現:
關于代碼的編寫,大部分可以參考上篇博客的案例代碼,因為大部分代碼和配置文件以及編譯步驟在上篇博客中以及寫的比較詳盡了,這里就不重復編寫了,唯一需要改的部分就是,將C代碼中的while(true)死循環部分刪掉,改成用Inotify機制去監聽應用目錄的變化:
編譯的頭文件:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class com_example_appuninstalldemo_MainActivity */
- #ifndef _Included_com_example_appuninstalldemo_MainActivity
- #define _Included_com_example_appuninstalldemo_MainActivity
- #ifdef __cplusplus
- extern "C" {
- #endif
- /*
- * Class: com_example_appuninstalldemo_MainActivity
- * Method: uninstall
- * Signature: (Ljava/lang/String;I)V
- */
- JNIEXPORT void JNICALL Java_com_example_appuninstalldemo_MainActivity_uninstall
- (JNIEnv *, jobject, jstring, jint);
- #ifdef __cplusplus
- }
- #endif
- #endif
- #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 <fcntl.h>
- #include <stdint.h>
- #include "com_example_appuninstalldemo_MainActivity.h"
- #include <android/log.h>
- #define LOG_TAG "System.out.c"
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
- /**
- * 返回值 char* 這個代表char數組的首地址
- * Jstring2CStr 把java中的jstring的類型轉化成一個c語言中的char 字符串
- */
- char* Jstring2CStr(JNIEnv* env, jstring jstr) {
- char* rtn = NULL;
- jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
- jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一個java字符串 "GB2312"
- jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
- "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
- jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
- strencode); // String .getByte("GB2312");
- jsize alen = (*env)->GetArrayLength(env, barr); // byte數組的長度
- jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
- if (alen > 0) {
- rtn = (char*) malloc(alen + 1); //"\0"
- memcpy(rtn, ba, alen);
- rtn[alen] = 0;
- }
- (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //
- return rtn;
- }
- JNIEXPORT void JNICALL Java_com_example_appuninstalldemo_MainActivity_uninstall(
- JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion) {
- // 1,將傳遞過來的java的包名轉為c的字符串
- char * pd = Jstring2CStr(env, packageDir);
- // 2,創建當前進程的克隆進程
- pid_t pid = fork();
- // 3,根據返回值的不同做不同的操作,<0,>0,=0
- if (pid < 0) {
- // 說明克隆進程失敗
- LOGD("current crate process failure");
- } else if (pid > 0) {
- // 說明克隆進程成功,而且該代碼運行在父進程中
- LOGD("crate process success,current parent pid = %d", pid);
- } else {
- // 說明克隆進程成功,而且代碼運行在子進程中
- LOGD("crate process success,current child pid = %d", pid);
- // 4,在子進程中監視/data/data/包名這個目錄
- //初始化inotify進程
- int fd = inotify_init();
- if (fd < 0) {
- LOGD("inotify_init failed !!!");
- exit(1);
- }
- //添加inotify監聽器
- int wd = inotify_add_watch(fd, pd, IN_DELETE);
- if (wd < 0) {
- LOGD("inotify_add_watch failed !!!");
- exit(1);
- }
- //分配緩存,以便讀取event,緩存大小=一個struct inotify_event的大小,這樣一次處理一個event
- void *p_buf = malloc(sizeof(struct inotify_event));
- if (p_buf == NULL) {
- LOGD("malloc failed !!!");
- exit(1);
- }
- //開始監聽
- LOGD("start observer");
- ssize_t readBytes = read(fd, p_buf,sizeof(struct inotify_event));
- //read會阻塞進程,走到這里說明收到目錄被刪除的事件,注銷監聽器
- free(p_buf);
- inotify_rm_watch(fd, IN_DELETE);
- // 應用被卸載了,通知系統打開用戶反饋的網頁
- LOGD("app uninstall,current sdkversion = %d", sdkVersion);
- if (sdkVersion >= 17) {
- // Android4.2系統之后支持多用戶操作,所以得指定用戶
- execlp("am", "am", "start", "--user", "0", "-a",
- "android.intent.action.VIEW", "-d", "http://www.baidu.com",
- (char*) NULL);
- } else {
- // Android4.2以前的版本無需指定用戶
- execlp("am", "am", "start", "-a", "android.intent.action.VIEW",
- "-d", "http://www.baidu.com", (char*) NULL);
- }
- }
- }
Java層代碼很簡單,直接看:
- public class MainActivity extends Activity {
- static {
- System.loadLibrary("uninstall");
- }
- public native void uninstall(String packageDir, int sdkVersion);
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- String packageDir = "/data/data/" + getPackageName();
- int sdkVersion = android.os.Build.VERSION.SDK_INT;
- uninstall(packageDir, sdkVersion);
- }
- }
運行之后卸載應用程序,效果如下: