應用監聽自身卸載,彈出用戶反饋調查(下)——使用Inotify監聽安裝目錄

silejiuok 8年前發布 | 13K 次閱讀 Android開發 移動開發

來自: 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 實例:

  1. int fd = inotify_init ();  
每一個 inotify 實例對應一個獨立的排序的隊列。

        文件系統的變化事件被稱做 watches 的一個對象管理,每一個 watch 是一個二元組(目標,事件掩碼),目標可以是文件或目錄,事件掩碼表示應用希望關注的 inotify 事件,每一個位對應一個 inotify 事件。Watch 對象通過 watch描述符引用,watches 通過文件或目錄的路徑名來添加。目錄 watches 將返回在該目錄下的所有文件上面發生的事件。
下面函數用于添加一個 watch:

  1. int wd = inotify_add_watch (fd, path, mask);  
         fd 是 inotify_init() 返回的文件描述符,path 是被監視的目標的路徑名(即文件名或目錄名),mask 是事件掩碼, 在頭文件 linux/inotify.h 中定義了每一位代表的事件。可以使用同樣的方式來修改事件掩碼,即改變希望被通知的inotify 事件。Wd 是 watch 描述符。

        下面是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:

  1. int ret = inotify_rm_watch (fd, wd);  
 fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函數的返回值。
文件事件用一個 inotify_event 結構表示,它通過由 inotify_init() 返回的文件描述符使用通常文件讀取函數 read 來獲得:

  1. struct inotify_event {  
  2.         __s32           wd;             /* watch descriptor */  
  3.         __u32           mask;           /* watch mask */  
  4.         __u32           cookie;         /* cookie to synchronize two events */  
  5.         __u32           len;            /* length (including nulls) of name */  
  6.         char            name[0];        /* stub for possible name */  
  7. };  
         結構中的 wd 為被監視目標的 watch 描述符,mask 為事件掩碼,len 為 name字符串的長度,name 為被監視目標的路徑名,該結構的 name 字段為一個樁,它只是為了用戶方面引用文件名,文件名是變長的,它實際緊跟在該結構的后面,文件名將被 0 填充以使下一個事件結構能夠 4 字節對齊。注意,len 也把填充字節數統計在內。
通過 read 調用可以一次獲得多個事件,只要提供的 buf 足夠大。

  1. size_t len = read (fd, buf, BUF_LEN);  
         buf 是一個 inotify_event 結構的數組指針,BUF_LEN 指定要讀取的總長度,buf 大小至少要不小于 BUF_LEN,該調用返回的事件數取決于 BUF_LEN 以及事件中文件名的長度。Len 為實際讀去的字節數,即獲得的事件的總長度。
可以在函數 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 來得到當前隊列的長度。close(fd)將刪除所有添加到 fd 中的 watch 并做必要的清理。

  1. int inotify_init (void);  
  2. int inotify_add_watch (int fd, const char *path, __u32 mask);  
  3. int inotify_rm_watch (int fd, __u32 mask);  
注意:上述資料參考了 IBM Developerworks ,IBM Developerworks是個非常非常優秀的技術學習,我們可以在上面找到很多牛逼的資料來學習,如果你想更深入的了解Inotify機制,請點擊 http://www.ibm.com/developerworks/cn/linux/l-inotifynew/


代碼實現:

        關于代碼的編寫,大部分可以參考上篇博客的案例代碼,因為大部分代碼和配置文件以及編譯步驟在上篇博客中以及寫的比較詳盡了,這里就不重復編寫了,唯一需要改的部分就是,將C代碼中的while(true)死循環部分刪掉,改成用Inotify機制去監聽應用目錄的變化:

編譯的頭文件:

  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_example_appuninstalldemo_MainActivity */  
  4.   
  5. #ifndef _Included_com_example_appuninstalldemo_MainActivity  
  6. #define _Included_com_example_appuninstalldemo_MainActivity  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     com_example_appuninstalldemo_MainActivity 
  12.  * Method:    uninstall 
  13.  * Signature: (Ljava/lang/String;I)V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_com_example_appuninstalldemo_MainActivity_uninstall  
  16.   (JNIEnv *, jobject, jstring, jint);  
  17.   
  18. #ifdef __cplusplus  
  19. }  
  20. #endif  
  21. #endif  
C的代碼實現:

  1. #include <stdio.h>  
  2. #include <jni.h>  
  3. #include <malloc.h>  
  4. #include <string.h>  
  5. #include <strings.h>  
  6. #include <stdlib.h>  
  7. #include <unistd.h>  
  8. #include <sys/inotify.h>  
  9. #include <fcntl.h>  
  10. #include <stdint.h>  
  11. #include "com_example_appuninstalldemo_MainActivity.h"  
  12. #include <android/log.h>  
  13. #define LOG_TAG "System.out.c"  
  14. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
  15. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
  16.   
  17. /** 
  18.  * 返回值 char* 這個代表char數組的首地址 
  19.  * Jstring2CStr 把java中的jstring的類型轉化成一個c語言中的char 字符串 
  20.  */  
  21. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
  22.     char* rtn = NULL;  
  23.     jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String  
  24.     jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一個java字符串 "GB2312"  
  25.     jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
  26.             "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");  
  27.     jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,  
  28.             strencode); // String .getByte("GB2312");  
  29.     jsize alen = (*env)->GetArrayLength(env, barr); // byte數組的長度  
  30.     jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
  31.     if (alen > 0) {  
  32.         rtn = (char*) malloc(alen + 1); //"\0"  
  33.         memcpy(rtn, ba, alen);  
  34.         rtn[alen] = 0;  
  35.     }  
  36.     (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //  
  37.     return rtn;  
  38. }  
  39.   
  40. JNIEXPORT void JNICALL Java_com_example_appuninstalldemo_MainActivity_uninstall(  
  41.         JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion) {  
  42.     // 1,將傳遞過來的java的包名轉為c的字符串  
  43.     char * pd = Jstring2CStr(env, packageDir);  
  44.   
  45.     // 2,創建當前進程的克隆進程  
  46.     pid_t pid = fork();  
  47.   
  48.     // 3,根據返回值的不同做不同的操作,<0,>0,=0  
  49.     if (pid < 0) {  
  50.         // 說明克隆進程失敗  
  51.         LOGD("current crate process failure");  
  52.     } else if (pid > 0) {  
  53.         // 說明克隆進程成功,而且該代碼運行在父進程中  
  54.         LOGD("crate process success,current parent pid = %d", pid);  
  55.     } else {  
  56.         // 說明克隆進程成功,而且代碼運行在子進程中  
  57.         LOGD("crate process success,current child pid = %d", pid);  
  58.   
  59.         // 4,在子進程中監視/data/data/包名這個目錄  
  60.         //初始化inotify進程  
  61.         int fd = inotify_init();  
  62.         if (fd < 0) {  
  63.             LOGD("inotify_init failed !!!");  
  64.             exit(1);  
  65.         }  
  66.   
  67.         //添加inotify監聽器  
  68.         int wd = inotify_add_watch(fd, pd, IN_DELETE);  
  69.         if (wd < 0) {  
  70.             LOGD("inotify_add_watch failed !!!");  
  71.             exit(1);  
  72.         }  
  73.   
  74.         //分配緩存,以便讀取event,緩存大小=一個struct inotify_event的大小,這樣一次處理一個event  
  75.         void *p_buf = malloc(sizeof(struct inotify_event));  
  76.         if (p_buf == NULL) {  
  77.             LOGD("malloc failed !!!");  
  78.             exit(1);  
  79.         }  
  80.   
  81.         //開始監聽  
  82.         LOGD("start observer");  
  83.         ssize_t readBytes = read(fd, p_buf,sizeof(struct inotify_event));  
  84.   
  85.         //read會阻塞進程,走到這里說明收到目錄被刪除的事件,注銷監聽器  
  86.         free(p_buf);  
  87.         inotify_rm_watch(fd, IN_DELETE);  
  88.   
  89.         // 應用被卸載了,通知系統打開用戶反饋的網頁  
  90.         LOGD("app uninstall,current sdkversion = %d", sdkVersion);  
  91.         if (sdkVersion >= 17) {  
  92.             // Android4.2系統之后支持多用戶操作,所以得指定用戶  
  93.             execlp("am""am""start""--user""0""-a",  
  94.                     "android.intent.action.VIEW""-d""http://www.baidu.com",  
  95.                     (char*) NULL);  
  96.         } else {  
  97.             // Android4.2以前的版本無需指定用戶  
  98.             execlp("am""am""start""-a""android.intent.action.VIEW",  
  99.                     "-d""http://www.baidu.com", (char*) NULL);  
  100.         }  
  101.   
  102.     }  
  103. }  
      代碼如上所示,大家可以根據上面的Inotify介紹和代碼中注釋來看,實現的代碼基本上不難,但是了解實現原理還得好好理解一下Inotify的實現機制,這樣才能事半功倍啊。

Java層代碼很簡單,直接看:

[java]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
  1. public class MainActivity extends Activity {  
  2.   
  3.     static {  
  4.         System.loadLibrary("uninstall");  
  5.     }  
  6.   
  7.     public native void uninstall(String packageDir, int sdkVersion);  
  8.   
  9.     @Override  
  10.     protected void onCreate(Bundle savedInstanceState) {  
  11.         super.onCreate(savedInstanceState);  
  12.         setContentView(R.layout.activity_main);  
  13.   
  14.         String packageDir = "/data/data/" + getPackageName();  
  15.         int sdkVersion = android.os.Build.VERSION.SDK_INT;  
  16.         uninstall(packageDir, sdkVersion);  
  17.     }  
  18.   
  19. }  

運行之后卸載應用程序,效果如下:



源碼請在這里下載

 本文由用戶 silejiuok 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!