两个月前发了两篇有关监听自己是否被卸载和卸载反馈功能实现的博客,第二版的地址如下:http://www.cnblogs.com/zealotrouge/p/3159772.html,感谢@whiletrue_童鞋发现了我的代码在4.2.2系统上无法实现卸载反馈,经过调试,有了问题的解决方案,但是由于发完博客后即处于闭关开发阶段,没时间打理博客,所以解决方案迟迟没有与大家见面,最近空闲下来,将解决思路及方案发出来给大家看看还有没有问题。
调试发现,监听依然没有问题,毕竟是Linux Kernel中的接口,Framework层再怎么改也改不到那儿去,那么问题出在哪呢?阻塞结束后,通过调用exec函数发出am命令调起浏览器访问网页,在API16(Android 4.1.x)的设备上尚可正常访问网页,而API17(Android 4.2.x)的设备上连浏览器也不能调起。
通过分析log,发现了一条线索,如下面的log的所示:
W/ActivityManager( 387): Permission Denial: startActivity asks to run as user -2 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL
log中直接给出提示,需要加一个权限INTERACT_ACROSS_USERS_FULL,这个权限时API17新引入的,目的在于允许不同用户的应用之间可以产生交互。可是加上去之后发现,还不是无法调起浏览器,而且log依然提示需要权限INTERACT_ACROSS_USERS_FULL,很是奇怪,于是继续分析。
首先说明一下Linux中的pid和uid,以及android扩展的userSerialNumber。pid是Process的标识,用于系统对进程的控制,从API层面看就是用于Process.killProcess()和Process.sendSignal();uid在Linux系统中是用来标识用户的,而在android将uid视为app的标识id,用于"sandbox"安全模型,即用于app权限控制;而对于API17引入的多用户支持(目前只支持平板),uid已经被占用,只好新引入userSerialNumber来标识用户。
回到刚才的问题,log中告知startActivity时运行用户标识为-2,而调用却是由用户标识0发起,导致拒绝执行。用这句话搜索,发现在Google开发者网站中有相关的issue,链接如下:https://code.google.com/p/android/issues/detail?id=39801(打不开可以把https改为http)。结合官方的回答,问题原因如下:由于被卸载,C端进程监听到目录被删除,立即执行am命令,此时将会默认以USER_CURRENT的身份执行,由于API17中ActivityManagerService.handleIncomingUser()会校验userSerialNumber,发现用户标识不匹配,导致权限校验失败——这也说明了权限的影响范围仅限于Java端的进程,对于fork()出来的C端进程来说,并不继承父进程在Android中声明的权限。
解决方案:增加处理分支,若API>=17,将userSerialNumber传递给C端进程,然后在am命令中带上参数--user userSerialNumber即可。
Java端代码如下:
1 package main.activity; 2 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 6 import pym.test.uninstalledobserver.R; 7 import android.app.Activity; 8 import android.os.Build; 9 import android.os.Bundle; 10 import android.util.Log; 11 12 /** 13 * @author pengyiming 14 * @note 监听此应用是否被卸载,若被卸载则弹出卸载反馈 15 * @note 由于API17加入多用户支持,原有命令在4.2及更高版本上执行时缺少userSerial参数,特此修改 16 * 17 */ 18 19 public class UninstalledObserverActivity extends Activity 20 { 21 /* 数据段begin */ 22 private static final String TAG = "UninstalledObserverActivity"; 23 24 // 监听进程pid 25 private int mObserverProcessPid = -1; 26 /* 数据段end */ 27 28 /* static */ 29 // 初始化监听进程 30 private native int init(String userSerial); 31 static 32 { 33 Log.d(TAG, "load lib --> uninstalled_observer"); 34 System.loadLibrary("uninstalled_observer"); 35 } 36 /* static */ 37 38 /* 函数段begin */ 39 @Override 40 public void onCreate(Bundle savedInstanceState) 41 { 42 super.onCreate(savedInstanceState); 43 44 setContentView(R.layout.uninstalled_observer_layout); 45 46 // API level小于17,不需要获取userSerialNumber 47 if (Build.VERSION.SDK_INT < 17) 48 { 49 mObserverProcessPid = init(null); 50 } 51 // 否则,需要获取userSerialNumber 52 else 53 { 54 mObserverProcessPid = init(getUserSerial()); 55 } 56 } 57 58 @Override 59 protected void onDestroy() 60 { 61 super.onDestroy(); 62 63 // 示例代码,用于结束监听进程 64 // if (mObserverProcessPid > 0) 65 // { 66 // android.os.Process.killProcess(mObserverProcessPid); 67 // } 68 } 69 70 // 由于targetSdkVersion低于17,只能通过反射获取 71 private String getUserSerial() 72 { 73 Object userManager = getSystemService("user"); 74 if (userManager == null) 75 { 76 Log.e(TAG, "userManager not exsit !!!"); 77 return null; 78 } 79 80 try 81 { 82 Method myUserHandleMethod = android.os.Process.class.getMethod("myUserHandle", (Class<?>[]) null); 83 Object myUserHandle = myUserHandleMethod.invoke(android.os.Process.class, (Object[]) null); 84 85 Method getSerialNumberForUser = userManager.getClass().getMethod("getSerialNumberForUser", myUserHandle.getClass()); 86 long userSerial = (Long) getSerialNumberForUser.invoke(userManager, myUserHandle); 87 return String.valueOf(userSerial); 88 } 89 catch (NoSuchMethodException e) 90 { 91 Log.e(TAG, "", e); 92 } 93 catch (IllegalArgumentException e) 94 { 95 Log.e(TAG, "", e); 96 } 97 catch (IllegalAccessException e) 98 { 99 Log.e(TAG, "", e); 100 } 101 catch (InvocationTargetException e) 102 { 103 Log.e(TAG, "", e); 104 } 105 106 return null; 107 } 108 /* 函数段end */ 109 }
核心——native方法头文件:
1 /* 头文件begin */ 2 #include <jni.h> 3 #include <stdlib.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include <unistd.h> 7 #include <fcntl.h> 8 #include <sys/inotify.h> 9 #include <sys/stat.h> 10 11 #include <android/log.h> 12 /* 头文件end */ 13 14 /* 宏定义begin */ 15 //清0宏 16 #define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize) 17 18 //LOG宏定义 19 #define LOG_INFO(tag, msg) __android_log_write(ANDROID_LOG_INFO, tag, msg) 20 #define LOG_DEBUG(tag, msg) __android_log_write(ANDROID_LOG_DEBUG, tag, msg) 21 #define LOG_WARN(tag, msg) __android_log_write(ANDROID_LOG_WARN, tag, msg) 22 #define LOG_ERROR(tag, msg) __android_log_write(ANDROID_LOG_ERROR, tag, msg) 23 /* 宏定义end */ 24 25 #ifndef _Included_main_activity_UninstalledObserverActivity 26 #define _Included_main_activity_UninstalledObserverActivity 27 #ifdef __cplusplus 28 extern "C" { 29 #endif 30 31 #undef main_activity_UninstalledObserverActivity_MODE_PRIVATE 32 #define main_activity_UninstalledObserverActivity_MODE_PRIVATE 0L 33 #undef main_activity_UninstalledObserverActivity_MODE_WORLD_READABLE 34 #define main_activity_UninstalledObserverActivity_MODE_WORLD_READABLE 1L 35 #undef main_activity_UninstalledObserverActivity_MODE_WORLD_WRITEABLE 36 #define main_activity_UninstalledObserverActivity_MODE_WORLD_WRITEABLE 2L 37 #undef main_activity_UninstalledObserverActivity_MODE_APPEND 38 #define main_activity_UninstalledObserverActivity_MODE_APPEND 32768L 39 #undef main_activity_UninstalledObserverActivity_MODE_MULTI_PROCESS 40 #define main_activity_UninstalledObserverActivity_MODE_MULTI_PROCESS 4L 41 #undef main_activity_UninstalledObserverActivity_BIND_AUTO_CREATE 42 #define main_activity_UninstalledObserverActivity_BIND_AUTO_CREATE 1L 43 #undef main_activity_UninstalledObserverActivity_BIND_DEBUG_UNBIND 44 #define main_activity_UninstalledObserverActivity_BIND_DEBUG_UNBIND 2L 45 #undef main_activity_UninstalledObserverActivity_BIND_NOT_FOREGROUND 46 #define main_activity_UninstalledObserverActivity_BIND_NOT_FOREGROUND 4L 47 #undef main_activity_UninstalledObserverActivity_BIND_ABOVE_CLIENT 48 #define main_activity_UninstalledObserverActivity_BIND_ABOVE_CLIENT 8L 49 #undef main_activity_UninstalledObserverActivity_BIND_ALLOW_OOM_MANAGEMENT 50 #define main_activity_UninstalledObserverActivity_BIND_ALLOW_OOM_MANAGEMENT 16L 51 #undef main_activity_UninstalledObserverActivity_BIND_WAIVE_PRIORITY 52 #define main_activity_UninstalledObserverActivity_BIND_WAIVE_PRIORITY 32L 53 #undef main_activity_UninstalledObserverActivity_BIND_IMPORTANT 54 #define main_activity_UninstalledObserverActivity_BIND_IMPORTANT 64L 55 #undef main_activity_UninstalledObserverActivity_BIND_ADJUST_WITH_ACTIVITY 56 #define main_activity_UninstalledObserverActivity_BIND_ADJUST_WITH_ACTIVITY 128L 57 #undef main_activity_UninstalledObserverActivity_CONTEXT_INCLUDE_CODE 58 #define main_activity_UninstalledObserverActivity_CONTEXT_INCLUDE_CODE 1L 59 #undef main_activity_UninstalledObserverActivity_CONTEXT_IGNORE_SECURITY 60 #define main_activity_UninstalledObserverActivity_CONTEXT_IGNORE_SECURITY 2L 61 #undef main_activity_UninstalledObserverActivity_CONTEXT_RESTRICTED 62 #define main_activity_UninstalledObserverActivity_CONTEXT_RESTRICTED 4L 63 #undef main_activity_UninstalledObserverActivity_RESULT_CANCELED 64 #define main_activity_UninstalledObserverActivity_RESULT_CANCELED 0L 65 #undef main_activity_UninstalledObserverActivity_RESULT_OK 66 #define main_activity_UninstalledObserverActivity_RESULT_OK -1L 67 #undef main_activity_UninstalledObserverActivity_RESULT_FIRST_USER 68 #define main_activity_UninstalledObserverActivity_RESULT_FIRST_USER 1L 69 #undef main_activity_UninstalledObserverActivity_DEFAULT_KEYS_DISABLE 70 #define main_activity_UninstalledObserverActivity_DEFAULT_KEYS_DISABLE 0L 71 #undef main_activity_UninstalledObserverActivity_DEFAULT_KEYS_DIALER 72 #define main_activity_UninstalledObserverActivity_DEFAULT_KEYS_DIALER 1L 73 #undef main_activity_UninstalledObserverActivity_DEFAULT_KEYS_SHORTCUT 74 #define main_activity_UninstalledObserverActivity_DEFAULT_KEYS_SHORTCUT 2L 75 #undef main_activity_UninstalledObserverActivity_DEFAULT_KEYS_SEARCH_LOCAL 76 #define main_activity_UninstalledObserverActivity_DEFAULT_KEYS_SEARCH_LOCAL 3L 77 #undef main_activity_UninstalledObserverActivity_DEFAULT_KEYS_SEARCH_GLOBAL 78 #define main_activity_UninstalledObserverActivity_DEFAULT_KEYS_SEARCH_GLOBAL 4L 79 80 /* 81 * Class: main_activity_UninstalledObserverActivity 82 * Method: init 83 * Signature: (Ljava/lang/String;)V 84 */ 85 JNIEXPORT int JNICALL Java_main_activity_UninstalledObserverActivity_init(JNIEnv *, jobject, jstring); 86 87 #ifdef __cplusplus 88 } 89 #endif 90 #endif
核心——native方法实现:
1 /* 头文件begin */ 2 #include "main_activity_UninstalledObserverActivity.h" 3 /* 头文件end */ 4 5 #ifdef __cplusplus 6 extern "C" 7 { 8 #endif 9 10 /* 内全局变量begin */ 11 static char TAG[] = "UninstalledObserverActivity.init"; 12 static jboolean isCopy = JNI_TRUE; 13 14 static const char APP_DIR[] = "/data/data/pym.test.uninstalledobserver"; 15 static const char APP_FILES_DIR[] = "/data/data/pym.test.uninstalledobserver/files"; 16 static const char APP_OBSERVED_FILE[] = "/data/data/pym.test.uninstalledobserver/files/observedFile"; 17 static const char APP_LOCK_FILE[] = "/data/data/pym.test.uninstalledobserver/files/lockFile"; 18 /* 内全局变量 */ 19 20 /* 21 * Class: main_activity_UninstalledObserverActivity 22 * Method: init 23 * Signature: ()V 24 * return: 子进程pid 25 */ 26 JNIEXPORT int JNICALL Java_main_activity_UninstalledObserverActivity_init(JNIEnv *env, jobject obj, jstring userSerial) 27 { 28 jstring tag = (*env)->NewStringUTF(env, TAG); 29 30 LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) 31 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "init observer"), &isCopy)); 32 33 // fork子进程,以执行轮询任务 34 pid_t pid = fork(); 35 if (pid < 0) 36 { 37 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 38 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork failed !!!"), &isCopy)); 39 40 exit(1); 41 } 42 else if (pid == 0) 43 { 44 // 若监听文件所在文件夹不存在,创建 45 FILE *p_filesDir = fopen(APP_FILES_DIR, "r"); 46 if (p_filesDir == NULL) 47 { 48 int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH); 49 if (filesDirRet == -1) 50 { 51 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 52 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "mkdir failed !!!"), &isCopy)); 53 54 exit(1); 55 } 56 } 57 58 // 若被监听文件不存在,创建文件 59 FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r"); 60 if (p_observedFile == NULL) 61 { 62 p_observedFile = fopen(APP_OBSERVED_FILE, "w"); 63 } 64 fclose(p_observedFile); 65 66 // 创建锁文件,通过检测加锁状态来保证只有一个卸载监听进程 67 int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY); 68 if (lockFileDescriptor == -1) 69 { 70 lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT); 71 } 72 int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB); 73 if (lockRet == -1) 74 { 75 LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) 76 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "observed by another process"), &isCopy)); 77 78 exit(0); 79 } 80 LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) 81 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "observed by child process"), &isCopy)); 82 83 // 分配空间,以便读取event 84 void *p_buf = malloc(sizeof(struct inotify_event)); 85 if (p_buf == NULL) 86 { 87 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 88 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &isCopy)); 89 90 exit(1); 91 } 92 // 分配空间,以便打印mask 93 int maskStrLength = 7 + 10 + 1;// mask=0x占7字节,32位整形数最大为10位,转换为字符串占10字节,'\0'占1字节 94 char *p_maskStr = malloc(maskStrLength); 95 if (p_maskStr == NULL) 96 { 97 free(p_buf); 98 99 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 100 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &isCopy)); 101 102 exit(1); 103 } 104 105 // 开始监听 106 LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) 107 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start observe"), &isCopy)); 108 109 // 初始化 110 int fileDescriptor = inotify_init(); 111 if (fileDescriptor < 0) 112 { 113 free(p_buf); 114 free(p_maskStr); 115 116 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 117 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_init failed !!!"), &isCopy)); 118 119 exit(1); 120 } 121 122 // 添加被监听文件到监听列表 123 int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS); 124 if (watchDescriptor < 0) 125 { 126 free(p_buf); 127 free(p_maskStr); 128 129 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 130 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &isCopy)); 131 132 exit(1); 133 } 134 135 while(1) 136 { 137 // read会阻塞进程 138 size_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event)); 139 140 // 打印mask 141 snprintf(p_maskStr, maskStrLength, "mask=0x%x\0", ((struct inotify_event *) p_buf)->mask); 142 LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) 143 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, p_maskStr), &isCopy)); 144 145 // 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在 146 if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask) 147 { 148 FILE *p_appDir = fopen(APP_DIR, "r"); 149 // 确认已卸载 150 if (p_appDir == NULL) 151 { 152 inotify_rm_watch(fileDescriptor, watchDescriptor); 153 154 break; 155 } 156 // 未卸载,可能用户执行了"清除数据" 157 else 158 { 159 fclose(p_appDir); 160 161 // 重新创建被监听文件,并重新监听 162 FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "w"); 163 fclose(p_observedFile); 164 165 int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS); 166 if (watchDescriptor < 0) 167 { 168 free(p_buf); 169 free(p_maskStr); 170 171 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 172 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &isCopy)); 173 174 exit(1); 175 } 176 } 177 } 178 } 179 180 // 释放资源 181 free(p_buf); 182 free(p_maskStr); 183 184 // 停止监听 185 LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy) 186 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "stop observe"), &isCopy)); 187 188 if (userSerial == NULL) 189 { 190 // 执行命令am start -a android.intent.action.VIEW -d $(url) 191 execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *)NULL); 192 } 193 else 194 { 195 // 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url) 196 execlp("am", "am", "start", "--user", (*env)->GetStringUTFChars(env, userSerial, &isCopy), "-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *)NULL); 197 } 198 199 // 执行命令失败log 200 LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy) 201 , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "exec AM command failed !!!"), &isCopy)); 202 } 203 else 204 { 205 // 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pid 206 return pid; 207 } 208 } 209 210 #ifdef __cplusplus 211 } 212 #endif
注一:此次代码修复了评论中提到的一些bug,比如清除数据、插拔USB线、覆盖安装等操作引起程序误判卸载。
注二:在同事指点下,针对任何情况导致重复监听的问题,都可以通过加文件锁来防止,这比ps并读取返回结果并过滤进程名的方法要好很多。
注三:安装在SD卡此卸载监听依然没有问题,但是如果用户将已在Internal SD卡安装好的应用移动到external SD卡,由于.c的161行未重新files文件夹和锁文件,应该会bug,代码都有,需要的自行修复此bug即可。
//以下为原文评论
具体代码我不会贴的(都是标准c代码,大学的时候大家都学过的)仅介绍大体思路
误触发:
不要监听“data/data/应用包”这个目录 而是监听“data/data/应用包/lib”。为啥呢,因为你这本身就是c代码 会编译出一个so库,自然系统会给你建一个data/data/应用包/lib目录。这个目录在“清除数据”这个动作时不会触发任何事件。
然后你会问要是覆盖安装触发呢,那就更简单了,触发的时候你再加一段判断代码,用标准文件命令查看一下“data/data/应用包” 文件是不是存在,如果还存在直接exit(1),比如fp=fopen("data/data/应用包")==NULL之类的就能搞定。
为什么要监听lib而不是share_prefs?很简单,因为清除数据的时候你的进程还在,不触发还能多留一会儿去等真正的卸载;覆盖安装呢?你的进程就算不触发也早晚被杀掉
哦 别试监听data/dalvik-cache/***了,我早试过了,没权限咪啪。
多次注册:
这个麻烦一点,有一个思路是把上次注册监听的句柄存在文件里,下次注册的时候先把文件里的句柄拿出来解除监听。
拿到句柄就解除监听是不行的。你什么时候产生了intify每次返句柄都一样的错觉?
之前某人在二版里提到想给网址带参数怎么办?
答案是自己加个jstring的参数从java里把参数传进来。请从jni基础学起。其实不需要调试代码,h文件和c文件的jni函数里各加个jstring,java native方法里加个String,gcc一下就搞定了。
以上办法都是调试通过的,如果你感到不明觉厉我也只能表示残念⋯⋯
JNI手册里的原话是这么说的:A local reference is valid only within the dynamic context of the native
method that creates it, and only within that one invocation of the native method.
All local references created during the execution of a native method will be freed
once the native method returns.
所以说没有内存泄露,也许你在网上搜到的代码里都是通过DeleteLocalRef这个函数来删除引用的,这么做可行,但是没有必要。
转自:http://www.cnblogs.com/zealotrouge/p/3182617.html
注:推荐到原文查看文章