(转)Android应用如何监听自己是否被卸载及卸载反馈功能的实现(第三版)

转载 2013年12月04日 16:49:19

  两个月前发了两篇有关监听自己是否被卸载和卸载反馈功能实现的博客,第二版的地址如下: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即可。


//以下为原文评论

#1楼 2013-10-04 17:17 | 南海菜园  
楼主,如果是android客户端多次调用C层的方法,在卸载的时候就会发送多次http请求,有没有什么办法可以阻止多次调用在C层,我的QQ:494159721,如果可以的话,还请联系下我,麻烦楼主了
  
#2楼[楼主2013-10-09 17:29 | 热气球  
@南海菜园
怎么会多次调用C层的方法?卸载之前inotify接口是阻塞状态
  
#3楼 2013-10-10 11:28 | jasonguo0606  
楼主,使用你的方法,如果覆盖安装貌似也会弹出网页,有没有好的办法解决?
  
#4楼[楼主2013-10-10 11:43 | 热气球  
@jasonguo0606
应该是因为监听的是整个apk在/data/data下的文件夹,而且这个监听是“递归”的,所以文件夹下子目录或者子文件发生变化,也会被误以为是卸载;由于覆盖安装不会删除用户数据,所以你可以试试监听那些在覆盖安装时不会被删除的文件,比如记录SharedPref这种文件
  
#5楼 2013-10-10 15:09 | jasonguo0606  
@热气球
监控sharepfef确实可以了,谢谢~
  
#6楼 2013-10-10 19:06 | 南海菜园  
博主,我在用的时候,流程是这样的,装上软件----》注册卸载统计方法-----》拔掉USB数据线---------》客户端自动关屏-----然后再打开手机,发现弹出了浏览器,也就说执行了弹出浏览器的命令,博主你测试下是不是也是这样的,我的QQ:494159721
mail:lhjtianji@gmail.com
  
#7楼[楼主2013-10-10 19:36 | 热气球  
@南海菜园
除非你用PC端某些市场类软件卸载掉了,不然不可能。而且我按照你的操作步骤也没有复现
  
#8楼 2013-10-10 22:10 | 南海菜园  
@热气球
就是手动操作,装上软件之后,拔掉USB,然后自动等待手机锁屏,然后再开屏,就会执行卸载的命令,方便QQ联系mee
  
#9楼 2013-10-10 22:22 | 南海菜园  
前提是拔掉USB数据线,以及自动等待手机锁屏及黑屏,然后再开屏,就会出现这样的情况,明天我再换个测试机试试的,这是三星I9100
  
#10楼 2013-10-10 22:28 | 南海菜园  
由于版本将要上线,还请博主有时间及时联系下我
  
#11楼 2013-10-11 10:04 | 南海菜园  
博主,测试过了,又用了公司几个其他的手机都弹出浏览器了,操作流程是拔掉USB数据线,等待手机锁屏与黑屏,在打开手机,就会执行卸载的命令了,还请博主及时联系我
  
#12楼[楼主2013-10-11 11:31 | 热气球  
@南海菜园
我在Nexus7上没有复现这个问题,我自己的I9158上倒是有这个问题,我今天不忙的时候来看看这个问题
  
#13楼 2013-10-11 11:33 | 南海菜园  
@热气球
好的,麻烦博主了
  
#14楼[楼主2013-10-11 20:14 | 热气球  
@南海菜园
没搞定,我还有些不明白为什么插拔USB线也会收到event,明天继续研究
  
#15楼 2013-10-12 10:38 | 南海菜园  
@热气球
好的,如果博主搞定了,能否给我发封邮件,通知下的,比较急的
  
#16楼[楼主2013-10-12 17:29 | 热气球  
@jasonguo0606
公司不让上QQ啊。。。
  
#17楼[楼主2013-10-12 17:33 | 热气球  
@南海菜园
先回答你之前的关于多次初始化监听进程的问题,很简单,自己用SharedPref存一个boolean值,仅调用init方法一次即可
  
#18楼[楼主2013-10-12 17:35 | 热气球  
@南海菜园
再回答你关于插拔USB线的问题,插拔时发送了一堆mask为随机数的event导致误判,你可以这么监听:
1,将inotify_add_watch里mask参数改为IN_ALL_EVENTS
2,在while(1)中调用read方法,如果IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask,说明确实是被卸载了,即可break出来
  
#19楼 2013-10-12 17:40 | 南海菜园  
@热气球
博主,能否将你改好的代码发一份给我的,我不懂C,只会编译的,我的mail:lhjtianji@gmail.com
  
#20楼 2013-10-14 09:35 | 南海菜园  
博主,能否将修改好的给我一份的,我不懂C,不懂怎么修改,还请博主发一份给我,lhjtianji@gmail.com,比较急的
  
#21楼[楼主2013-10-14 10:18 | 热气球  
@南海菜园
NO!
1,代码都是在project里面,只有写博客的时候才会抽取,我没有精力和义务帮你做这件事情。
2,这是一个讨论技术的平台,请不要让别人帮你写代码,我又没收你的钱。
  
#22楼 2013-10-14 12:01 | 南海菜园  
博主,如果mask参数改为IN_ALL_EVENTS,这个参数会有很大的扩展性,可能是一些无关卸载的操作都会执行while循环,并且我这边测试没有执行while中的if语句,会不会是IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask这句代码有问题
  
#23楼[楼主2013-10-14 12:11 | 热气球  
@南海菜园
看4楼的回复,监听不应该针对整个文件夹,而是某个文件
  
#24楼 2013-10-14 13:42 | jasonguo0606  
之前说解决覆盖安装检测,后来觉得还是不太好,把事件改成IN_DELETE_SELF在我的nexus4上没问题了,结果发现2.3的手机接不到这个事件,能接到IN_DELETE的事件,不知道楼主遇到过没
  
#25楼 2013-10-22 12:58 | AlanJaver  
感谢楼主无私分享。我提两个问题,大家一起讨论下:1、用户手动移动app到SD中时,会弹出网页;2、设置中清除数据时,会弹出。 对于第二个问题,只要监听不会被清除的目录就行,第一个问题,好没有想到好的办法。

另外多次监听的问题的,还是执行ps去看看是不是已经有存在同名进程比较好;程序内存储一个值的方式不可取,1、清除数据后再启动程序会多次运行;2、关机重启后再也起不来。
  
#26楼 2013-10-23 16:35 | 死鱼哥  
@AlanJaver
关于多次监听的问题,ps命令能判断是监听进程是否已存在吗?我试过ps命令,结果同名的进程有好几个,名字都是我的应用包名。所以我是用kill()函数实现的:每次成功启动一个监听进程,则把pid存起来(可以考虑存在SD卡中,而不是sharePreferences),以后init之前,先用kill判断之前的进程是否存在,若存在,则不再开启。

此外,监听目录的选择确实是个问题。
如果监听sharePreferences,则清空数据时会误判
如果监听其他目录,则升级时会误判。
大家有什么好的办法吗?
  
#27楼 2013-10-23 16:50 | AlanJaver  
@死鱼哥
觉得会有风险,如果sd文件被删,或sd卡临时不可用,还是会启动多个子进程。而且你通过ps发现同名进程有很多个,估计已经存在了多个监控进程了。可以ps找到同名进程的pid,kill掉多余的,再启用子进程。

我现在最头痛的问题是,程序被移动到sd卡上,这个过程中,会误判。还没想到太好的办法解决。
  
#28楼 2013-10-24 10:18 | 南海菜园  
@死鱼哥
关于多次监听的问题,我的处理方案是第一次运行存储下来,如果关机的话,开启关机广播,存储下次启动的时候需要监听,这样可以实现我的目的了
  
#29楼 2013-10-24 18:31 | AlanJaver  
@南海菜园
用户手动清除数据怎么破。死机拆电池怎么破。 :)
  
#30楼 2013-10-26 09:13 | 南海菜园  
@AlanJaver
这个估计只有乔布斯能破
  
#31楼 2013-10-27 19:40 | Tentacle  
关于多次注册监听和误触发的问题,其实解决起来都很简单
具体代码我不会贴的(都是标准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一下就搞定了。

以上办法都是调试通过的,如果你感到不明觉厉我也只能表示残念⋯⋯
  
#32楼 2013-10-28 15:57 | Tentacle  
#31楼 自己纠正下 
下次注册的时候先把文件里的句柄拿出来解除监听
这个貌似是不行的,因为每次fork都会启动新进程,没法跨进程操作
  
#33楼 2013-10-29 11:01 | AlanJaver  
@Tentacle
和你的解决方案类似,但还是监听根目录,只要触发后判断一把根目录是否存在即可,神马覆盖安装神马清除数据都能解决。
解决多次注册,我用的是ps|grep或直接ps(部分rom不支持grep),看到已经有同名进程,就不开启监听,或者你不爽了kill掉再重开个也一样。
  
#34楼 2013-10-29 11:13 | Tentacle  
@AlanJaver

"ps|grep或直接ps(部分rom不支持grep),看到已经有同名进程,就不开启监听"
这个在我这里不好这样弄- =因为后台会比较希望你激活更新的那个监听而不是更旧的。越新的数据越有价值这样。
kill我估计有效,但是是不是需要考虑父进程子进程的问题,不是同一个父进程会不会杀不掉啥的?
  
#35楼 2013-10-30 11:16 | AlanJaver  
@Tentacle
根据我试的结果,只要跟你名字一样的进程,都可以kill掉(用android.os.Process.KillProcess(..)),不确定是否通用。主进程结束掉后,监听用的子进程是挂在init进程名下的,就是说子进程的父进程是pid为1的init进程,所以可能很难通过父进程子进程的方式判断。
  
#36楼 2013-11-13 11:09 | fd1207  
NewStringUTF 后没释放,,泄露啊,,
请问安装在sd卡上的问题解决了没呢?
  
#37楼 2013-11-13 16:45 | 死鱼哥  
@AlanJaver
请教一下,如何用ps命令查看是否已存在某个进程。是用execlp执行吗?执行之后如何获取结果呢?
  
#38楼[楼主2013-11-14 15:23 | 热气球  
@Tentacle @AlanJaver @死鱼哥
最近很忙没时间看博客,看来已经积累不少问题,可以看看我更新后的代码,我自测已经解决了你们的问题
  
#39楼[楼主2013-11-14 15:27 | 热气球  
@fd1207
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这个函数来删除引用的,这么做可行,但是没有必要。
  
#40楼 2013-11-18 15:23 | 南海菜园  
如果说应用被频繁的安装卸载,会不会导致耗电量比较高呢?
通过我的测试,我发现当手机满电量的时候,三个小时就会没有电了,经过测试功耗,应用耗电达到80%
  
#41楼[楼主2013-11-18 15:28 | 热气球  
@南海菜园
伪需求,只有测试才会频繁安装卸载
  
#42楼 2013-11-18 15:30 | 南海菜园  
对了,我用的是PowerTutor进行测试的,就算应用卸载过以后,这个应用的耗电量还会增加,可能是因为卸载频繁引起的,大家遇到过这种情况么
  
#43楼[楼主2013-11-18 15:33 | 热气球  
@南海菜园
卸载完后监听进程已退出,建议你先ps看看监听进程还在不在再测试吧
  
#44楼 2013-11-19 17:13 | lllman  
楼主有没有试过这样的情况,进入activity以后,通过kill命令直接杀死主进程,通过PS命令查看,子进程已经被init接收,但是主进程的界面会出现卡死的情况,除非子进程被KILL掉。
尝试过注册信号量的方式监听主进程结束进行判断,但是调试的时候没有问题,打包APK以后发言注册的信号量无效。
楼主可以试试这样的情况。
  
#45楼[楼主2013-11-25 10:56 | 热气球  
@lllman
首先不知道你说的信号量是什么意思?其次什么叫做发言注册的信号量?最后kill杀死了主进程为什么其界面还会卡死,真的杀死主进程了么?
对了,如果是这种场景就算出了问题,那也木有办法,建议不要花精力在这个方面。
  
#46楼 2013-11-28 11:57 | Tentacle  
补充:对于有时候fork进程被系统杀死的场合,请在JAVA端,以反复注册监听,配合KILL掉上一次进程,这样来解决


转自:http://www.cnblogs.com/zealotrouge/p/3182617.html

注:推荐到原文查看文章

相关文章推荐

Android 监听安装和卸载

Android 应用程序的安装和卸载事件,是由系统进行监听并全局广播的,支持1.5(android 3)以上因此,如果想要监听获取应用的安装和卸载事件,只需要自定义一个BroadcastReceive...

Android开发,卸载应用的时候删除文件或文件夹

Android与IOS一个比较大的不同就是文件夹部分,IOS在卸载应用的时候会同时删除应用所创建的所有文件及文件夹,Android不会。 以下是执行这个操作的方法。 首先写一个广播接收器即Broadc...

Android 4.2+ 应用如何监听自己是否被卸载及卸载反馈功能的实现(第三版)

两个月前发了两篇有关监听自己是否被卸载和卸载反馈功能实现的博客,第二版的地址如下: http://www.cnblogs.com/zealotrouge/p/3159772.html ,感谢@whil...

Android应用如何监听自己是否被卸载及卸载反馈功能的实现(第二版)

转载:http://www.cnblogs.com/zealotrouge/p/3159772.html  昨天发了一篇有关监听自己是否被卸载和卸载反馈功能实现的博客,地址如下:http://w...

Android应用如何监听自己是否被卸载及卸载反馈功能的实现

一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的。目前...

Android应用如何监听自己是否被卸载及卸载反馈功能的实现

一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的。目前...

Android应用如何监听自己是否被卸载及卸载反馈功能的实现

一方案:  1,注册BroadcastReceiver,监听"android.intent.action.PACKAGE_REMOVED"系统广播   结果:NO。未写代码,直接分析,卸载的第一步...
  • gvvbn
  • gvvbn
  • 2014年07月21日 12:56
  • 436

Android应用如何监听自己是否被卸载及卸载反馈功能的实现

一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的。目前...

Android应用如何监听自己是否被卸载及卸载反馈功能的实现

转载自:http://www.cnblogs.com/zealotrouge/p/3157126.html 一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反...

Android应用如何监听自己是否被卸载及卸载反馈功能的实现

一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的。目前...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:(转)Android应用如何监听自己是否被卸载及卸载反馈功能的实现(第三版)
举报原因:
原因补充:

(最多只允许输入30个字)