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
相關文章
- Android掃碼槍監聽封裝Android封裝
- 如何解除安裝NTFS for Mac?ntfs for mac解除安裝詳細教程Mac
- Solaris中對tar.z進行安裝解除安裝教程詳解
- 【轉載】Linux下徹底解除安裝mysql詳解LinuxMySql
- 免root解除安裝Android預裝應用Android
- Linux(CentOS7)安裝與解除安裝MySQL8.0圖文詳解LinuxCentOSMySql
- Android截圖監聽Android
- MySQL解除安裝重灌解決方案MySql
- 2018.03.31、Android-ObjectBox-監聽AndroidObject
- Android開屏、鎖屏、解鎖監聽實現Android
- MySQL:mysql5.7解壓版安裝與解除安裝MySql
- Linux RPM包安裝、解除安裝、升級命令講解Linux
- Linux安裝解除安裝MySQLLinuxMySql
- Ubuntu解除安裝和安裝Ubuntu
- cocoapods安裝/解除安裝/使用
- JDK安裝和解除安裝JDK
- docker安裝及解除安裝Docker
- 超詳細maven的解除安裝、重新安裝與配置Maven
- Filter(過濾器)與Listener(監聽器)詳解Filter過濾器
- 監聽配置細節引數詳解兩則
- docker 解除安裝Docker
- 解除安裝 PythonPython
- WSL解除安裝
- 安裝npm 解除安裝npm 安裝apidocNPMAPI
- Android多程式之Binder解綁監聽的問題Android
- android 5.0 以後app自身解除安裝統計------求助AndroidAPP
- debian軟體解除安裝|deb包解除安裝|dpkg命令
- Mac Redis安裝與解除安裝MacRedis
- Linux 解除安裝openjdk 安裝oraclejdkLinuxJDKOracle
- debian安裝和解除安裝vmware
- JDK的安裝與解除安裝JDK
- linux 解除安裝jdk和安裝LinuxJDK
- [雲原生]Docker - 安裝&解除安裝Docker
- Ubuntu安裝和解除安裝mongodbUbuntuMongoDB
- Maven安裝詳解Maven
- window下安裝並使用nvm(含解除安裝node、解除安裝nvm、全域性安裝npm)NPM
- Android Studio 超詳細安裝教程Android
- 圖文詳解Prometheus監控+Grafana+Alertmanager告警安裝使用PrometheusGrafana
- mysql解除安裝不乾淨解決方法MySql