DownOL 软件仓库– 软件下载,字节世界与新知

App检测自身被卸载

发表于:2024-04-29 作者:创始人
编辑最后更新 2024年04月29日,正常处理有以下5种方案,但由于局限性,我们考虑第五种监听系统卸载广播:只能监听到其他应用的卸载广播,无法监听到自己是否被卸载。读取系统log:第三方软件卸载无法得知。静默安装另一个程序,监听自己是否被

正常处理有以下5种方案,但由于局限性,我们考虑第五种

  1. 监听系统卸载广播:只能监听到其他应用的卸载广播,无法监听到自己是否被卸载。

  2. 读取系统log:第三方软件卸载无法得知。

  3. 静默安装另一个程序,监听自己是否被卸载:需要root许可权。

  4. Java线程轮询,监听/data/data/{package-name}目录是否存在:卸载app,进程退出,线程也被销毁。

  5. C进程轮询,监听/data/data/{package-name}目录是否存在:目前业界普遍采用的方案。

1,C代码

/** Copyright (C) 2016 [email protected]** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#include #include #include #include #include #include #include #include #include #include #include #define TAG "venshine"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)static const char APP_DIR[] = "/data/data/com.wx.appuninstall";static const char APP_FILES_DIR[] = "/data/data/com.wx.appuninstall/files";static const char APP_OBSERVED_FILE[] = "/data/data/com.wx.appuninstall/files/observedFile";static const char APP_LOCK_FILE[] = "/data/data/com.wx.appuninstall/files/lockFile";static const char *HOST_ADDR = "www.baidu.com";static const char *SERVER_ADDR = "http://www.baidu.com";static const int OK = 0;static const int ERROR = -1;int watchDescriptor;int fileDescriptor;pid_t observer = -1;#ifdef __cplusplusextern "C" {#endif/*** 获取SDK版本号*/int get_sdk_version();/*** Jstring转char**/char *JstringToCStr(JNIEnv *env, jstring jstr) ;/*** 上传统计数据*/int uploadStatData(char* versionName, jint versionCode);/*** 监听*/int startObserver(void *p_buf);/*** 判断是否进程活着*/int isProcessAlive(const char *pid);/*** 记录pid*/void writePidFile(const char *pid);/*** 监控程序*/jint Java_com_wx_appuninstall_Uninstall_watch(JNIEnv *env, jobject thiz, jobject upload_obj) {if (upload_obj == NULL) {exit(1);}// 获得UploadInfo类引用jclass upload_cls = env->GetObjectClass(upload_obj);if (upload_cls == NULL) {exit(1);}// 判断监听进程是否活着if (isProcessAlive(APP_OBSERVED_FILE) == OK) {LOGE("watch process already exists");return observer;}// 若被监听文件存在,删除FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");if (p_observedFile != NULL) {LOGD("delete observed file");remove(APP_OBSERVED_FILE);fclose(p_observedFile);}// 若被监听文件存在,删除FILE *p_LockedFile = fopen(APP_LOCK_FILE, "r");if (p_LockedFile != NULL) {LOGD("delete lock file");remove(APP_LOCK_FILE);fclose(p_LockedFile);}// 创建进程pid_t pid = fork();// prctl(PR_SET_NAME, "m.uninstall");// 根据返回值不同做不同操作if (pid < 0) { // 创建进程失败LOGE("fork process error!");} else if (pid == 0) { // 创建第一个子进程成功,代码运行在子进程中LOGD("fork first process succ pid = %d", getpid());setsid(); // 将进程和它当前的对话过程和进程组分离开,并且把它设置成一个新的对话过程的领头进程。umask(0); // 为文件赋予更多的许可权,因为继承来的文件可能某些许可权被屏蔽int pid = fork();// prctl(PR_SET_NAME, "j.k.l.uninstall");if (pid == 0) { // 第二个子进程// 保存监听进程idLOGD("fork second process succ pid = %d", getpid());// 分配缓存,以便读取event,缓存大小等于一个struct inotify_event的大小,这样一次处理一个eventvoid *p_buf = malloc(sizeof(struct inotify_event));if (p_buf == NULL) {LOGD("malloc failed !!!");exit(1);}// 通过linux中的inotify机制来监听应用的卸载。inotify是linux内核用于通知用户空间文件系统变化的机制,文件的添加或卸载等事件都能够及时捕获到。if (startObserver(p_buf) != 0) {return 0;}writePidFile(APP_OBSERVED_FILE);// 开始监听while (1) {LOGD("start watch");// 调用read函数开始监听,read会阻塞进程ssize_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));// 走到这里说明收到目录被删除的事件if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask) {LOGD("IN_DELETE_SELF");// 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在FILE *p_appDir = fopen(APP_DIR, "r");if (p_appDir != NULL) {// 应用主目录还在(可能还没有来得及清除),sleep一段时间后再判断sleep(5);p_appDir = fopen(APP_DIR, "r");}// 确认已卸载if (p_appDir == NULL) {LOGD("inotify rm watch");inotify_rm_watch(fileDescriptor, watchDescriptor);break;} else { // 未卸载,可能用户执行了"清除数据"LOGD("not uninstall");fclose(p_appDir);// 应用没有卸载,重新监听if (startObserver(p_buf) != 0) {return 0;}}} else {LOGD("NOT IN_DELETE_SELF");}}LOGD("end watch");remove(APP_OBSERVED_FILE);remove(APP_LOCK_FILE);free(p_buf);jfieldID nameFieldID = env->GetFieldID(upload_cls, "versionName", "Ljava/lang/String;"); // 获得属性IDjfieldID codeFieldID = env->GetFieldID(upload_cls, "versionCode", "I"); // 获得属性IDjfieldID browserFieldID = env->GetFieldID(upload_cls, "isBrowser", "Z"); // 获得属性IDjstring versionName = (jstring) env->GetObjectField(upload_obj, nameFieldID);// 获得属性值jint versionCode = env->GetIntField(upload_obj, codeFieldID); // 获得属性值jboolean isBrowser = env->GetBooleanField(upload_obj, browserFieldID); // 获得属性值char *vName = JstringToCStr(env, versionName);// 上传统计数据if (uploadStatData(vName, versionCode) == OK) {LOGD("upload data succ");}// 是否打开浏览器if (isBrowser) { // TODO 打开浏览器命令在有些手机上可能失效// 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d",SERVER_ADDR,(char *) NULL);}} else {exit(0);}} else {// 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pidLOGD("parent process exit");}return pid;}/*** 监听*/int startObserver(void *p_buf) {// 若监听文件所在文件夹不存在,创建文件夹FILE *p_filesDir = fopen(APP_FILES_DIR, "r");if (p_filesDir == NULL) {int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH);if (filesDirRet == -1) {LOGE("create app files dir failed");exit(1);}}// if (access(APP_FILES_DIR, F_OK) != 0) {// LOGD("folder not exists");// if (mkdir(APP_FILES_DIR, 0755) == -1) {// LOGE("mkdir failed!");// exit(1);// }// }// 若被监听文件不存在,创建监听文件FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");if (p_observedFile == NULL) {p_observedFile = fopen(APP_OBSERVED_FILE, "w");LOGD("create app observed file");}fclose(p_observedFile);// 创建锁文件,通过检测加锁状态来保证只有一个卸载监听进程int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY);if (lockFileDescriptor == -1) {lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT);LOGD("create app lock file");}int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB);if (lockRet == -1) {LOGE("watch by other process");return ERROR;}// 初始化inotify进程fileDescriptor = inotify_init();if (fileDescriptor < 0) {LOGE("inotify init failed");free(p_buf);exit(1);}// 添加inotify监听器,监听APP_OBSERVED_FILE文件watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS);if (watchDescriptor < 0) {LOGE("inotify watch failed");free(p_buf);exit(1);}return OK;}/*** 上传统计数据*/int uploadStatData(char* versionName, jint versionCode) {LOGD("upload stat data");struct sockaddr_in serv_addr;struct hostent *host;int sock = socket(AF_INET, SOCK_STREAM, 0);if ((host = gethostbyname(HOST_ADDR)) == NULL) {LOGE("host name is null.");return ERROR;}memset(&serv_addr, 0, sizeof(serv_addr)); // 每个字节都用0填充serv_addr.sin_family = AF_INET; // 使用IPv4地址// serv_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 具体的IP地址serv_addr.sin_addr = *((struct in_addr *) host->h_addr);serv_addr.sin_port = htons(80); //埠if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {LOGE("connect error");return ERROR;}LOGD("connect succ");int sdkVersion = get_sdk_version();char request[200];sprintf(request, "GET /web/index.html?versionName=%s&versionCode=%d&sdkVersion=%d", versionName,versionCode, sdkVersion);if (write(sock, request, strlen(request)) < 0) {LOGE("request failed");return ERROR;}LOGD("request success");// 关闭套接字close(sock);return OK;}/*** 判断是否进程活着*/int isProcessAlive(const char *pid) {FILE *pidFile;char observerPID[32];if ((pidFile = fopen(pid, "rb")) == NULL) {LOGE("can't open pid file");return ERROR;}// fread(&observerPID,sizeof(observerPID),1,pidFile);fscanf(pidFile, "%d", &observer);fclose(pidFile);if (observer > 1) {sprintf(observerPID, "%d/n", observer);LOGD("read saved pid");if (kill(observer, 0) == 0) {LOGD("process is alive");return OK;}LOGD("process is not alive");} else {LOGD("not read saved pid");return ERROR;}}/*** 记录pid*/void writePidFile(const char *pid) {char str[32];int pidFile = open(pid, O_WRONLY | O_TRUNC);if (pidFile < 0) {LOGE("pid is %d", pidFile);exit(1);}if (flock(pidFile, LOCK_EX | LOCK_NB) < 0) {LOGD("cann't lock pid file: %s", pid);fprintf(stderr, "can't lock pid file: %s", pid);exit(1);}sprintf(str, "%d/n", getpid());ssize_t len = strlen(str);ssize_t ret = write(pidFile, str, len);if (ret != len) {LOGE("can't write pid file: %s", pid);fprintf(stderr, "can't write pid file: %s", pid);exit(1);}close(pidFile);LOGD("write pid file success");}/*** 获取SDK版本号*/int get_sdk_version() {char value[8] = "";__system_property_get("ro.build.version.sdk", value);return atoi(value);}/*** Jstring转char**/char *JstringToCStr(JNIEnv *env, jstring jstr) {char *rtn = NULL;jclass clsstring = (*env).FindClass("java/lang/String"); //Stringjstring strencode = (*env).NewStringUTF("GB2312"); // 得到一个java字元串 "GB2312"jmethodID mid = (*env).GetMethodID(clsstring, "getBytes","(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");jbyteArray barr = (jbyteArray) (*env).CallObjectMethod(jstr, mid,strencode); // String .getByte("GB2312");jsize alen = (*env).GetArrayLength(barr); // byte数组的长度jbyte *ba = (*env).GetByteArrayElements(barr, JNI_FALSE);if (alen > 0) {rtn = (char *) malloc(alen + 1); //"\0"memcpy(rtn, ba, alen);rtn[alen] = 0;}(*env).ReleaseByteArrayElements(barr, ba, 0);return rtn;}#ifdef __cplusplus}#endif

2,jni中java代码

/** Copyright (C) 2016 [email protected]** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.wx.appuninstall;import android.content.Context;import com.wx.android.common.util.AppUtils;/*** 卸载** @author venshine*/public class Uninstall {private static Uninstall mUninstall = null;private Uninstall() {}/*** 调用Native程序** @param context* @return*/public static Uninstall getInstance(Context context) {if (mUninstall == null) {mUninstall = new Uninstall();watch(create(context));}return mUninstall;}/*** 构造上传统计数据** @param context* @return*/private static UploadInfo create(Context context) {UploadInfo uploadInfo = new UploadInfo();uploadInfo.setVersionCode(AppUtils.getVersionCode(context));uploadInfo.setVersionName(AppUtils.getVersionName(context));uploadInfo.setBrowser(true);return uploadInfo;}/*** 监控卸载** @param info* @return*/public native static int watch(UploadInfo info);// Used to load the 'uninstall' library on application startup.static {System.loadLibrary("uninstall");}}

2022-05-09 21:34:31
0