Android卸載監聽詳解

目前市場上比較多的應用在用戶卸載后會彈出意見反饋界面,比如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方法如下:

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, "<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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內容

  • 原創鏈接 (http://blog.csdn.net/stefanli1991/article/details/4...
    yoosir閱讀 4,788評論 1 0
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,692評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,807評論 18 139
  • 番茄工作法第四篇|內容總結 A.番茄工作法是什么? 番茄工作法是簡單易行的時間管理方法,是由弗朗西斯科?西里洛于1...
    小蔥鐵憨憨閱讀 224評論 2 0
  • 每次看完一本書,總會沉思很久,思緒漂浮,要經過很久的思索才會慢慢把那份不定的思緒沉淀下來,漸漸有了重量感。 過去了...
    傾耳傾聽16閱讀 386評論 2 1