精华内容
下载资源
问答
  • 想要实现应用的卸载反馈看似可行的方案有很多,比如监听Android的系统广播一类,完全可以监听到其他软件的卸载事件,但是如果想要在卸载后执行某些操作,java层的方法可能无能为力,因为随着应用的卸载,这些方法也...

    想要实现应用的卸载反馈看似可行的方案有很多,比如监听Android的系统广播一类,完全可以监听到其他软件的卸载事件,但是如果想要在卸载后执行某些操作,java层的方法可能无能为力,因为随着应用的卸载,这些方法也会随着Activity一个个被干掉而失效,于是在网上查了一下,有大神提出可以用基于C的子进程机制解决这一问题,因为在Linux中,一个进程在fork了一个子进程之后如果被干掉,子进程会被托管而不是马上被干掉,因此在应用卸载后仍可以在这个子进程中进行一些操作,这也是我这个实现卸载后反馈的原理。

    ——首先介绍下NDK插件的安装,可以去官网下载一个NDK插件安装包,是个exe,双击就可以运行啦,会在指定目录下生成一个NDK的根目录,在Eclipse中:

    Windows->pref->Android->NDK 设置一下NDK的根目录

    ——下载一个和eclipse版本对应的CDT插件,这个插件可以让Eclipse能够编译C代码,如果不用Eclipse编译的话也可以使用独立的编译工具Cygwin,这样可以在Eclipse外部编译生成SO文件,在copy到项目中,效果是一样的;


    ——安装了CDT之后,如果要编译C代码,就可以在Eclipse项目中右键-->Android-Tools-->添加native方法,写一个方法名,之后会自动在工程中生成一个JNI文件夹,其中会有一个刚才指定过名称的.cpp文件和一个Android.mk文件,如果是C代码,可以把.cpp改为.c,然后C代码就可以写在这里了, .mk文件类似于一个配置文件,在有多个c方法或者引入外部so文件时可以修改这个文件;


    ——在java代码中声明一个native方法:public native int uninstall(String userSerial, int sdkVersion);

    之后用cmd在工程的src目录下执行javah -jni 包名+类名(MainActivity),成功后会在src目录下生成一个.h文件,复制到jni目录中,打开.h文件,里面会有一个名字长长的方法,这个是根据我们刚才声明的native方法所生成的:

    JNIEXPORT jint JNICALL Java_com_example_uninstalldemo_MainActivity_uninstall(JNIEnv *, jobject, jstring, jint);

    ——在C文件中先引入这个.h文件,之后就可以实现我们所声明的native方法啦:

    /* 头文件begin */
    #include <stdio.h>
    #include <jni.h>
    #include <malloc.h>
    #include <string.h>
    #include <strings.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/inotify.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdint.h>
    #include "com_example_uninstalldemo_MainActivity.h"
    #include <android/log.h>
    
    
    /* 头文件end */
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    /* 内全局变量begin */
    static char TAG[] = "MainActivity.uninstall";
    static jboolean isCopy = JNI_TRUE;
    
    static const char APP_DIR[] = "/data/data/com.example.uninstalldemo/lib/libuninstall.so";
    static const char APP_FILES_DIR[] = "/data/data/com.example.uninstalldemo/files<span style="font-family: Arial, Helvetica, sans-serif;">";</span>
    static const char APP_OBSERVED_FILE[] = "/data/data/com.example.uninstalldemo/files/observedFile";
    static const char APP_LOCK_FILE[] = "/data/data/com.example.uninstalldemo/files/lockFile";
    /* 内全局变量 */
    
    JNIEXPORT int JNICALL <span style="white-space: pre;">Java_com_</span>example<span style="white-space: pre;">_</span>uninstalldemo<span style="white-space: pre;">_MainActivity_uninstall</span>(JNIEnv *env, jobject obj, jstring userSerial,jint sdkVersion)
    {
        jstring tag = (*env)->NewStringUTF(env, TAG);
    
    
        // fork子进程,以执行轮询任务
        pid_t pid = fork();
        if (pid < 0)
        {
    
            exit(1);
        }
        else if (pid == 0)
        {
            // 若监听文件所在文件夹不存在,创建
            FILE *p_filesDir = fopen(APP_FILES_DIR, "r");
            if (p_filesDir == NULL)
            {
            	fclose(p_filesDir);
            	int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH);
                if (filesDirRet == -1)
                {
                    exit(1);
                }
            }
    
            // 若被监听文件不存在,创建文件
            FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
            if (p_observedFile == NULL)
            {
                p_observedFile = fopen(APP_OBSERVED_FILE, "w");
            }
            fclose(p_observedFile);
    
            // 创建锁文件,通过检测加锁状态来保证只有一个卸载监听进程
            int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY);
            if (lockFileDescriptor == -1)
            {
                lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT);
            }
            int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB);
            if (lockRet == -1)
            {
    
                exit(0);
            }
    
            // 分配空间,以便读取event
            void *p_buf = malloc(sizeof(struct inotify_event));
            if (p_buf == NULL)
            {
    
                exit(1);
            }
            // 分配空间,以便打印mask
            int maskStrLength = 7 + 10 + 1;// mask=0x占7字节,32位整形数最大为10位,转换为字符串占10字节,'\0'占1字节
            char *p_maskStr = malloc(maskStrLength);
            if (p_maskStr == NULL)
            {
                free(p_buf);
    
                exit(1);
            }
    
            // 开始监听
    
            // 初始化
            int fileDescriptor = inotify_init();
            if (fileDescriptor < 0)
            {
                free(p_buf);
                free(p_maskStr);
    
                exit(1);
            }
    
            // 添加被监听文件到监听列表
            int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_DELETE_SELF);//IN_ALL_EVENTS);
            if (watchDescriptor < 0)
            {
                free(p_buf);
                free(p_maskStr);
                exit(1);
            }
    
    <span style="white-space:pre">	</span>while(1){
                    // read会阻塞进程
                    size_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));
    
    
                    // 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在
                    if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask)
                     {
                         //FILE *p_appDir = fopen(APP_DIR, "r");
                         // 未卸载,可能用户执行了"清除数据"
                         if ( access(APP_DIR,0) == 0){
    
                        <span style="white-space:pre">	</span> // 重新创建被监听文件,并重新监听
                        <span style="white-space:pre">	</span> // 若被监听文件不存在,创建文件
                        <span style="white-space:pre">	</span> inotify_rm_watch(fileDescriptor, watchDescriptor);
                        <span style="white-space:pre">	</span> FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
                        <span style="white-space:pre">	</span> if (p_observedFile == NULL)
                        <span style="white-space:pre">	</span> {
                        <span style="white-space:pre">	</span>      p_observedFile = fopen(APP_OBSERVED_FILE, "w");
                        <span style="white-space:pre">	</span> }
                        <span style="white-space:pre">	</span> fclose(p_observedFile);
    
    
                        <span style="white-space:pre">	</span> //LOGE(LOG_TAG,"开始监听");
    
                        <span style="white-space:pre">	</span> int fileDescriptor =inotify_init();
                        <span style="white-space:pre">	</span> if (fileDescriptor < 0)
                        <span style="white-space:pre">	</span> {
                        <span style="white-space:pre">	</span>      free(p_buf);
                        <span style="white-space:pre">	</span>      free(p_maskStr);
                        <span style="white-space:pre">	</span>      exit(1);
                        <span style="white-space:pre">	</span> }
    
    
                        <span style="white-space:pre">	</span> int watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_DELETE_SELF);
                        <span style="white-space:pre">	</span> if (watchDescriptor < 0){
                        <span style="white-space:pre">	</span>       free(p_buf);
                        <span style="white-space:pre">	</span>       free(p_maskStr);
                        <span style="white-space:pre">	</span>       exit(1);
                        <span style="white-space:pre">	</span> }
                         }else if(access(APP_DIR,0) == -1)// 确认已卸载
                         {
                        <span style="white-space:pre">	</span> inotify_rm_watch(fileDescriptor, watchDescriptor);
                        <span style="white-space:pre">	</span> break;
                         }
                 }
            }
            // 释放资源
            free(p_buf);
            free(p_maskStr);
    
            // 停止监听
    
            if (userSerial == NULL)
            {
                // 执行命令am start -a android.intent.action.VIEW -d $(url)
                execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *)NULL);
            }
            else
            {
                // 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)
                execlp("am", "am", "start", "--user", (*env)->GetStringUTFChars(env, userSerial, &isCopy), "-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *)NULL);
            }
        }
        else
        {
            // 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pid
            return pid;
        }
    }
    
    #ifdef __cplusplus
    }
    #endif
    我们通过在C代码中fork一个子进程,在这里我们通过inotify机制监听我们指定的文件是否被删除(原来本想监听data/data/包名 这个文件的IN_DELETE_SELF事件的,但是应用新旧版覆盖安装时会出现问题,有大神知道的还望帮忙解答),这个文件是我们新建专门用来监听应用卸载事件的,我们的应用启动后就会在data/data中新建了这个文件,应用卸载它就会被删除,此时会触发我们的卸载事件,这里是用默认浏览器打开百度页面;


    ——在Eclipse中的C代码会有一些问题,会显示好多红线,但不影响编译,我们就工程右键->Properties->C/C++ General ->Code Analysis ->Use project settings 把Method和Symbol这两个勾掉就可以了;


    ——在java代码中调用我们的方法,需要先声明一个static代码块,加载我们的lib:

    static {
    		System.loadLibrary("uninstall");
    	}

    ——随后直接调用就可以了,由于API17以后打开浏览器的方式和之前不一样,所以在main中调用时要获取userSerial并传入uninstall方法;


    ——编译,会在libs目录生成.so文件,由于.so是要分平台的,如果我们可以再新建一个Application.mk,在其中设置我们要生成那些平台的.so(x86 armeabi armeabi-v7a mips):APP_ABI := x86 armeabi

    展开全文
  • 监听自身卸载 弹出用户反馈调查

    千次阅读 2017-02-22 11:53:51
    监听卸载情景和原理分析1,情景分析在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我将讲述一下...

    监听卸载情景和原理分析

    1,情景分析

    在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我将讲述一下一个各大应用中很常见的一个功能,同样也是基于JNI开发的Android应用小Demo,看完这个之后,不仅可以加深对NDK开发的理解,而且该Demo也可以使用在实际的开发中。不知道大家在使用一个Android应用的时候,当我们卸载这个应用后,设备上会弹出一个“用户反馈调查”的网页出来,也许很多人没有留意过或者直接忽视了,那么从现在开始请留意,大家不妨下载一下“豌豆荚”“360”之类的应用装上,然后卸载,看看设备上有没有弹出浏览器,浏览器上打开的“XXX用户反馈”?上面写了一些HTML表单,问我们“你为毛要卸载我们这么好的应用啊?”“我们哪里得罪你了?”“卸载之后,你丫的还装不?”,呵呵,开个玩笑,实际效果如下图:

    好了,上面的图片是感觉似曾显示啊?那么这样的一个小功能是怎么实现的呢?我们先从Java层以我们有的Android基础分析一下:

    1,监听系统的卸载广播,但是这个只能监听其他应用的卸载广播的动作,通过卸载广播监听自己是监听不到的:失败
    2,系统配置文件,做一个标记应用是否卸载,判断标记来show用户反馈,显然这也是不合理的,因为应用卸载之后,配置文件也没有了。
    3,静默安装另一个程序,监听自己的应用被卸载的动作。前提是要root,才能实现。但是市场绝大多数手机都是默认没有root权限的。
    4,服务检测,只能是自己开启,当自身被卸载了,服务也一并被干掉了。

    以上几点看起来都无法实现这个功能,确实如此啊,单纯的从Java层是做不到这一点的。

    2,原理分析

    上面情景分析后表明Java实现不了这样的一个功能,是否该考虑一下使用JNI了,用C在底层为我们实现这样一个打开内置浏览器加载用户反馈网页即可,在知道这个方法之前,我们有必要了解以下几个知识点。

    1.通过c语言,c进程监视。

    既然Java做不到的话,我们试着使用C语言在底层实现好了,让C语言调用Android adb的命令去打开内置浏览器。

    判断自己是否被卸载
    andoird程序在被安装的时候会在/data/data/目录下生成一个以为包名为文件名的目录/data/data/包名
    监听该目录是否还存在,如果不存在,就证明应用被卸载了。

    2.c代码可以复制一个当前的进程作为自己的儿子,父进程销毁的时候,子进程还存在。

    fork()函数:

    fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,两个进程可以做相同的事,相当于自己生了个儿子,如果初始参数或者传入的参数不一样,两个进程做的事情也不一样。当前进程调用fork函数之后,系统先给当前进程分配资源,然后再将当前进程的所有变量的值复制到新进程中(只有少数值不一样),相当于克隆了一个自己。

    pid_t fpid = fork()被调用前,就一个进程执行该段代码,这条语句执行之后,就将有两个进程执行代码,两个进程执行没有固定先后顺序,主要看系统调度策略,fork函数的特别之处在于调用一次,但是却可以返回两次,甚至是三种的结果
    (1)在父进程中返回子进程的进程id(pid)
    (2)在子进程中返回0
    (3)出现错误,返回小于0的负值
    出现错误原因:(1)进程数已经达到系统规定 (2)内存不足,此时返回

    3.在c代码的子进程中监视父进程是否被卸载,如果被卸载,通知Android系统打开一个url,卸载调查的网页。

    AM命令

    Android系统提供的adb工具,在adb的基础上执行adb shell就可以直接对android系统执行shell命令
    am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令。
    am命令的源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。
    am命令可以用start子命令,和带指定的参数,start是子命令,不是参数
    常见参数:-a:表示动作,-d:表示携带的数据,-t:表示传入的类型,-n:指定的组件名

    例如,我们现在在命令行模式下进入adb shell下,使用这个命令去打开一个网页

    类似的命令还有这些:

    拨打电话
    命令:am start -a android.intent.action.CALL -d tel:电话号码
    示例:am start -a android.intent.action.CALL -d tel:10086

    打开一个网页
    命令:am start -a android.intent.action.VIEW -d 网址
    示例:am start -a android.intent.action.VIEW -d http://www.baidu.com

    启动一个服务

    命令:am startservice <服务名称>
    示例:am startservice -n com.android.music/com.android.music.MediaPlaybackService

    execlp()函数

    execlp函数简单的来说就是C语言中执行系统命令的函数
    execlp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0], argv[1], ..., 最后一个参数必须用空指针(NULL)作结束.
    android开发中,execlp函数对应android的path路径为system/bin/目录下

    调用格式:

    execlp("am","am","start","--user","0","-a","android.intent.action.VIEW","-d","http://shouji.360.cn/web/uninstall/uninstall.html",(char*)NULL);

    =================================================================================================

    编写代码实现

    1,Java层定义native方法

    在Java层定义一个native方法,提供在Java端和C端调用

    1. public native void uninstall(String packageDir, int sdkVersion);

    该方法需要传递应用的安装目录和当前设备的版本号,在Java代码中获取,传递给C代码处理。

    2,使用javah命令生成方法签名头文件

    01. /* DO NOT EDIT THIS FILE - it is machine generated */
    02. #include <jni.h>
    03. /* Header for class com_example_appuninstall_MainActivity */
    04.  
    05. #ifndef _Included_com_example_appuninstall_MainActivity
    06. #define _Included_com_example_appuninstall_MainActivity
    07. #ifdef __cplusplus
    08. extern "C" {
    09. #endif
    10. /*
    11. * Class:     com_example_appuninstall_MainActivity
    12. * Method:    uninstall
    13. * Signature: (Ljava/lang/String;)V
    14. */
    15. JNIEXPORT void JNICALL Java_com_example_appuninstall_MainActivity_uninstall
    16. (JNIEnv *, jobject, jstring);
    17.  
    18. #ifdef __cplusplus
    19. }
    20. #endif
    21. #endif

    方法签名生成好之后,工程上右键 --> Android Tools --> Add Native Support,在弹出的对话框中输入编辑的C/C++的文件名,确定之后,在工程的自动生成的jni目录下找到cpp后缀名的文件修改为.c后缀名的文件,因为本案例是基于C语言上实现的,然后同样修改Android.mk文件中的LOCAL_SRC_FILES为.c的C文件,最后将上面生成好的.h方法签名文件拷贝到jni目录下。

    3,编写C语言代码

    正如上面原理分析的那样,我们在实现这样一个功能的时候用Java是无法实现的,只能在C中克隆出一个当前App的子进程,让这个子进程去监听应用本身的卸载。那么实现这样的功能我们需要哪些步骤呢?下面就是编写代码的思路:

    1,将传递过来的java的包名转为c的字符串
    2,创建当前进程的克隆进程
    3,根据返回值的不同做不同的操作
    4,在子进程中监视/data/data/包名这个目录
    5,目录被删除,说明被卸载,执行打开用户反馈的页面

    01. #include <stdio.h>
    02. #include <jni.h>
    03. #include <malloc.h>
    04. #include <string.h>
    05. #include <strings.h>
    06. #include <stdlib.h>
    07. #include <unistd.h>
    08. #include "com_example_appuninstall_MainActivity.h"
    09. #include <android/log.h>
    10. #define LOG_TAG "System.out.c"
    11. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    12. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
    13.  
    14. /**
    15. * 返回值 char* 这个代表char数组的首地址
    16. * Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串
    17. */
    18. char* Jstring2CStr(JNIEnv* env, jstring jstr) {
    19. char* rtn = NULL;
    20. jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
    21. jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一个java字符串 "GB2312"
    22. jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
    23. "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
    24. jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
    25. strencode); // String .getByte("GB2312");
    26. jsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度
    27. jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    28. if (alen > 0) {
    29. rtn = (char*) malloc(alen + 1); //""
    30. memcpy(rtn, ba, alen);
    31. rtn[alen] = 0;
    32. }
    33. (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //
    34. return rtn;
    35. }
    36.  
    37. JNIEXPORT void JNICALL Java_com_example_appuninstall_MainActivity_uninstall(
    38. JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion) {
    39. // 1,将传递过来的java的包名转为c的字符串
    40. char * pd = Jstring2CStr(env, packageDir);
    41.  
    42. // 2,创建当前进程的克隆进程
    43. pid_t pid = fork();
    44.  
    45. // 3,根据返回值的不同做不同的操作,<0,>0,=0
    46. if (pid < 0) {
    47. // 说明克隆进程失败
    48. LOGD("current crate process failure");
    49. else if (pid > 0) {
    50. // 说明克隆进程成功,而且该代码运行在父进程中
    51. LOGD("crate process success,current parent pid = %d", pid);
    52. else {
    53. // 说明克隆进程成功,而且代码运行在子进程中
    54. LOGD("crate process success,current child pid = %d", pid);
    55.  
    56. // 4,在子进程中监视/data/data/包名这个目录
    57. while (JNI_TRUE) {
    58. FILE* file = fopen(pd, "rt");
    59.  
    60. if (file == NULL) {
    61. // 应用被卸载了,通知系统打开用户反馈的网页
    62. LOGD("app uninstall,current sdkversion = %d", sdkVersion);
    63. if (sdkVersion >= 17) {
    64. // Android4.2系统之后支持多用户操作,所以得指定用户
    65. execlp("am""am""start""--user""0""-a",
    66. "android.intent.action.VIEW""-d",
    67. "http://www.baidu.com", (char*) NULL);
    68. else {
    69. // Android4.2以前的版本无需指定用户
    70. execlp("am""am""start""-a",
    71. "android.intent.action.VIEW""-d",
    72. "http://www.baidu.com", (char*) NULL);
    73. }
    74. else {
    75. // 应用没有被卸载
    76. LOGD("app run normal");
    77. }
    78. sleep(1);
    79. }
    80. }
    81.  
    82. }
    上述代码就如上述的步骤一样,用C代码实现了,首先注意的一点就是Android的版本问题,众所周知,Android是基于Linux的非常优秀的操作系统,而且在Android4.2版本以后支持多用户操作,但是这也给我们这个小小的项目中带来了不便之处,因为在多用户情况下执行am命令的时候强制指定一个用户和一个编号,在Android4.2之前的版本这些参数是没有必要的,所以我们在编写C代码的时候需要区别Android系统版本,分别执行相应的am命令,关于获取Android系统版本可以在Java层实现,然后将其作为参数传递给C代码中,C代码根据Android版本为判断条件执行am命令。

    注意:为了简便起见,我在C代码监视应用是否被卸载的时候,使用了一个While(true)的死循环,并且是每隔1毫秒执行一次监视检测,这样写的代码是“不环保的”,想想这样的结果是程序被不停的执行,LOG被不停的打印,造成cpu计算资源浪费和耗电是难免的。最好的解决方案是,使用Android给我们提供的FileObserve文件观察者,FileObserve使用到的是Linux系统下的inotify进程,用来监视文件目录的变化的,本实例中如果需要优化就需要使用这个API,但是需要的知识就更加多了,我现在为了简单的演示起见,暂时用了while(true)死循环,关于后期的优化版本,等我写出来,再一起公布一下!

    4,编译.so动态库

    正如上篇博客写的那样,我们编写好了C源码之后,就需要使用ndk-build命令来编译成.so文件了,具体编译的过程也是非常简单的,在Eclipse中切换到C/C++编辑的手下,找到“小锤子”按钮,点击一下就开始编译了,如果代码没有出现错误的情况,编译之后的结果是这样的:

    5,编写Java代码,传递数据 ,加载链接库

    上面的工作做好了,剩下的就是在Java中加载这个链接库,和调用这个本地方法了。首先,要获取本应用安装的目录/data/data/包名,然后获取当前设备的版本号,一起传给本地方法中,最后调用这个方法。

    01. public class MainActivity extends Activity {
    02.  
    03. static {
    04. System.loadLibrary("uninstall");
    05. }
    06.  
    07. public native void uninstall(String packageDir, int sdkVersion);
    08.  
    09. @Override
    10. protected void onCreate(Bundle savedInstanceState) {
    11. super.onCreate(savedInstanceState);
    12. setContentView(R.layout.activity_main);
    13.  
    14. String packageDir = "/data/data/" + getPackageName();
    15. int sdkVersion = android.os.Build.VERSION.SDK_INT;
    16. uninstall(packageDir, sdkVersion);
    17. }
    18.  
    19. }

    6,测试

    好了,应用是做完了,我们clean一下工程,然后启动一个基于ARM的模拟器,运行这个程序,回到桌面,点击应用图片——卸载掉这个应用,看看效果:

    好了,大家看看效果吧,实际上打开的网页应该是用户反馈调查页面,由于我暂时没有服务器,所以将网址定向到了百度首页了,大家在开发的时候,可以将execlp函数里的参数网址改成自己的服务器网址,这样就大功告成了。检查一下Log日志的输出:

    看到了,LOG输入日志跟代码流程是一致的,好了,源码在下面的链接下,有兴趣的朋友可以下载研究,欢迎你给我提出宝贵意见,大家一起学习一起进步!


    出处:http://blog.csdn.net/allen315410/article/details/42521251

    展开全文
  • 不知道大家在使用一个Android应用的时候,当我们卸载这个应用后,设备上会弹出一个“用户反馈调查”的网页出来,也许很多人没有留意过或者直接忽视了,那么从现在开始请留意,大家不妨下载一下“豌豆荚”“360”之类...

    1,情景分析

            在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我将讲述一下一个各大应用中很常见的一个功能,同样也是基于JNI开发的Android应用小Demo,看完这个之后,不仅可以加深对NDK开发的理解,而且该Demo也可以使用在实际的开发中。不知道大家在使用一个Android应用的时候,当我们卸载这个应用后,设备上会弹出一个“用户反馈调查”的网页出来,也许很多人没有留意过或者直接忽视了,那么从现在开始请留意,大家不妨下载一下“豌豆荚”“360”之类的应用装上,然后卸载,看看设备上有没有弹出浏览器,浏览器上打开的“XXX用户反馈”?上面写了一些HTML表单,问我们“你为毛要卸载我们这么好的应用啊?”“我们哪里得罪你了?”“卸载之后,你丫的还装不?”,呵呵,开个玩笑,实际效果如下图:


           好了,上面的图片是感觉似曾显示啊?那么这样的一个小功能是怎么实现的呢?我们先从Java层以我们有的Android基础分析一下:

    1,监听系统的卸载广播,但是这个只能监听其他应用的卸载广播的动作,通过卸载广播监听自己是监听不到的:失败
    2,系统配置文件,做一个标记应用是否卸载,判断标记来show用户反馈,显然这也是不合理的,因为应用卸载之后,配置文件也没有了。
    3,静默安装另一个程序,监听自己的应用被卸载的动作。前提是要root,才能实现。但是市场绝大多数手机都是默认没有root权限的。
    4,服务检测,只能是自己开启,当自身被卸载了,服务也一并被干掉了。

    以上几点看起来都无法实现这个功能,确实如此啊,单纯的从Java层是做不到这一点的。


    2,原理分析

           上面情景分析后表明Java实现不了这样的一个功能,是否该考虑一下使用JNI了,用C在底层为我们实现这样一个打开内置浏览器加载用户反馈网页即可,在知道这个方法之前,我们有必要了解以下几个知识点。

    1.通过c语言,c进程监视。

        既然Java做不到的话,我们试着使用C语言在底层实现好了,让C语言调用Android adb的命令去打开内置浏览器。

    判断自己是否被卸载
    andoird程序在被安装的时候会在/data/data/目录下生成一个以为包名为文件名的目录/data/data/包名
    监听该目录是否还存在,如果不存在,就证明应用被卸载了。


    2.c代码可以复制一个当前的进程作为自己的儿子,父进程销毁的时候,子进程还存在。

    fork()函数:

            fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,两个进程可以做相同的事,相当于自己生了个儿子,如果初始参数或者传入的参数不一样,两个进程做的事情也不一样。当前进程调用fork函数之后,系统先给当前进程分配资源,然后再将当前进程的所有变量的值复制到新进程中(只有少数值不一样),相当于克隆了一个自己。

           pid_t fpid = fork()被调用前,就一个进程执行该段代码,这条语句执行之后,就将有两个进程执行代码,两个进程执行没有固定先后顺序,主要看系统调度策略,fork函数的特别之处在于调用一次,但是却可以返回两次,甚至是三种的结果
    (1)在父进程中返回子进程的进程id(pid)
    (2)在子进程中返回0
    (3)出现错误,返回小于0的负值
    出现错误原因:(1)进程数已经达到系统规定 (2)内存不足,此时返回


    3.在c代码的子进程中监视父进程是否被卸载,如果被卸载,通知Android系统打开一个url,卸载调查的网页。

    AM命令

            Android系统提供的adb工具,在adb的基础上执行adb shell就可以直接对android系统执行shell命令
            am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令。
            am命令的源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。
            am命令可以用start子命令,和带指定的参数,start是子命令,不是参数
    常见参数:-a:表示动作,-d:表示携带的数据,-t:表示传入的类型,-n:指定的组件名

    例如,我们现在在命令行模式下进入adb shell下,使用这个命令去打开一个网页


    类似的命令还有这些:

    拨打电话
    命令:am start -a android.intent.action.CALL -d tel:电话号码
    示例:am start -a android.intent.action.CALL -d tel:10086

    打开一个网页
    命令:am start -a android.intent.action.VIEW -d  网址
    示例:am start -a android.intent.action.VIEW -d  http://www.baidu.com 

    启动一个服务

    命令:am startservice <服务名称>
    示例:am startservice -n com.android.music/com.android.music.MediaPlaybackService


    execlp()函数

              execlp函数简单的来说就是C语言中执行系统命令的函数
              execlp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个以后的参数当做该文件的argv[0], argv[1], ..., 最后一个参数必须用空指针(NULL)作结束.
              android开发中,execlp函数对应android的path路径为system/bin/目录下

    调用格式:

    execlp("am","am","start","--user","0","-a","android.intent.action.VIEW","-d","http://shouji.360.cn/web/uninstall/uninstall.html",(char*)NULL);

    ===================================================================================================================


    编写代码实现

    1,Java层定义native方法

           在Java层定义一个native方法,提供在Java端和C端调用

    [java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
    1. public native void uninstall(String packageDir, int sdkVersion);  

    该方法需要传递应用的安装目录和当前设备的版本号,在Java代码中获取,传递给C代码处理。


    2,使用javah命令生成方法签名头文件

    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class com_example_appuninstall_MainActivity */  
    4.   
    5. #ifndef _Included_com_example_appuninstall_MainActivity  
    6. #define _Included_com_example_appuninstall_MainActivity  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     com_example_appuninstall_MainActivity 
    12.  * Method:    uninstall 
    13.  * Signature: (Ljava/lang/String;)V 
    14.  */  
    15. JNIEXPORT void JNICALL Java_com_example_appuninstall_MainActivity_uninstall  
    16.   (JNIEnv *, jobject, jstring);  
    17.   
    18. #ifdef __cplusplus  
    19. }  
    20. #endif  
    21. #endif  

           方法签名生成好之后,工程上右键 --> Android Tools --> Add Native Support,在弹出的对话框中输入编辑的C/C++的文件名,确定之后,在工程的自动生成的jni目录下找到cpp后缀名的文件修改为.c后缀名的文件,因为本案例是基于C语言上实现的,然后同样修改Android.mk文件中的LOCAL_SRC_FILES为.c的C文件,最后将上面生成好的.h方法签名文件拷贝到jni目录下。


    3,编写C语言代码

            正如上面原理分析的那样,我们在实现这样一个功能的时候用Java是无法实现的,只能在C中克隆出一个当前App的子进程,让这个子进程去监听应用本身的卸载。那么实现这样的功能我们需要哪些步骤呢?下面就是编写代码的思路:

    1,将传递过来的java的包名转为c的字符串
    2,创建当前进程的克隆进程
    3,根据返回值的不同做不同的操作
    4,在子进程中监视/data/data/包名这个目录
    5,目录被删除,说明被卸载,执行打开用户反馈的页面

    1. #include <stdio.h>  
    2. #include <jni.h>  
    3. #include <malloc.h>  
    4. #include <string.h>  
    5. #include <strings.h>  
    6. #include <stdlib.h>  
    7. #include <unistd.h>  
    8. #include "com_example_appuninstall_MainActivity.h"  
    9. #include <android/log.h>  
    10. #define LOG_TAG "System.out.c"  
    11. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    12. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
    13.   
    14. /** 
    15.  * 返回值 char* 这个代表char数组的首地址 
    16.  * Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串 
    17.  */  
    18. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
    19.     char* rtn = NULL;  
    20.     jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String  
    21.     jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一个java字符串 "GB2312"  
    22.     jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
    23.             "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");  
    24.     jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,  
    25.             strencode); // String .getByte("GB2312");  
    26.     jsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度  
    27.     jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
    28.     if (alen > 0) {  
    29.         rtn = (char*) malloc(alen + 1); //"\0"  
    30.         memcpy(rtn, ba, alen);  
    31.         rtn[alen] = 0;  
    32.     }  
    33.     (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //  
    34.     return rtn;  
    35. }  
    36.   
    37. JNIEXPORT void JNICALL Java_com_example_appuninstall_MainActivity_uninstall(  
    38.         JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion) {  
    39.     // 1,将传递过来的java的包名转为c的字符串  
    40.     char * pd = Jstring2CStr(env, packageDir);  
    41.   
    42.     // 2,创建当前进程的克隆进程  
    43.     pid_t pid = fork();  
    44.   
    45.     // 3,根据返回值的不同做不同的操作,<0,>0,=0  
    46.     if (pid < 0) {  
    47.         // 说明克隆进程失败  
    48.         LOGD("current crate process failure");  
    49.     } else if (pid > 0) {  
    50.         // 说明克隆进程成功,而且该代码运行在父进程中  
    51.         LOGD("crate process success,current parent pid = %d", pid);  
    52.     } else {  
    53.         // 说明克隆进程成功,而且代码运行在子进程中  
    54.         LOGD("crate process success,current child pid = %d", pid);  
    55.   
    56.         // 4,在子进程中监视/data/data/包名这个目录  
    57.         while (JNI_TRUE) {  
    58.             FILE* file = fopen(pd, "rt");  
    59.   
    60.             if (file == NULL) {  
    61.                 // 应用被卸载了,通知系统打开用户反馈的网页  
    62.                 LOGD("app uninstall,current sdkversion = %d", sdkVersion);  
    63.                 if (sdkVersion >= 17) {  
    64.                     // Android4.2系统之后支持多用户操作,所以得指定用户  
    65.                     execlp("am""am""start""--user""0""-a",  
    66.                             "android.intent.action.VIEW""-d",  
    67.                             "http://www.baidu.com", (char*) NULL);  
    68.                 } else {  
    69.                     // Android4.2以前的版本无需指定用户  
    70.                     execlp("am""am""start""-a",  
    71.                             "android.intent.action.VIEW""-d",  
    72.                             "http://www.baidu.com", (char*) NULL);  
    73.                 }  
    74.             } else {  
    75.                 // 应用没有被卸载  
    76.                 LOGD("app run normal");  
    77.             }  
    78.             sleep(1);  
    79.         }  
    80.     }  
    81.   
    82. }  
            上述代码就如上述的步骤一样,用C代码实现了,首先注意的一点就是Android的版本问题,众所周知,Android是基于Linux的非常优秀的操作系统,而且在Android4.2版本以后支持多用户操作,但是这也给我们这个小小的项目中带来了不便之处,因为在多用户情况下执行am命令的时候强制指定一个用户和一个编号,在Android4.2之前的版本这些参数是没有必要的,所以我们在编写C代码的时候需要区别Android系统版本,分别执行相应的am命令,关于获取Android系统版本可以在Java层实现,然后将其作为参数传递给C代码中,C代码根据Android版本为判断条件执行am命令。

            注意:为了简便起见,我在C代码监视应用是否被卸载的时候,使用了一个While(true)的死循环,并且是每隔1毫秒执行一次监视检测,这样写的代码是“不环保的”,想想这样的结果是程序被不停的执行,LOG被不停的打印,造成cpu计算资源浪费和耗电是难免的。最好的解决方案是,使用Android给我们提供的FileObserve文件观察者,FileObserve使用到的是Linux系统下的inotify进程,用来监视文件目录的变化的,本实例中如果需要优化就需要使用这个API,但是需要的知识就更加多了,我现在为了简单的演示起见,暂时用了while(true)死循环,关于后期的优化版本,等我写出来,再一起公布一下!


    4,编译.so动态库

           正如上篇博客写的那样,我们编写好了C源码之后,就需要使用ndk-build命令来编译成.so文件了,具体编译的过程也是非常简单的,在Eclipse中切换到C/C++编辑的手下,找到“小锤子”按钮,点击一下就开始编译了,如果代码没有出现错误的情况,编译之后的结果是这样的:



    5,编写Java代码,传递数据 ,加载链接库

            上面的工作做好了,剩下的就是在Java中加载这个链接库,和调用这个本地方法了。首先,要获取本应用安装的目录/data/data/包名,然后获取当前设备的版本号,一起传给本地方法中,最后调用这个方法。

    [java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
    1. public class MainActivity extends Activity {  
    2.   
    3.     static {  
    4.         System.loadLibrary("uninstall");  
    5.     }  
    6.   
    7.     public native void uninstall(String packageDir, int sdkVersion);  
    8.   
    9.     @Override  
    10.     protected void onCreate(Bundle savedInstanceState) {  
    11.         super.onCreate(savedInstanceState);  
    12.         setContentView(R.layout.activity_main);  
    13.   
    14.         String packageDir = "/data/data/" + getPackageName();  
    15.         int sdkVersion = android.os.Build.VERSION.SDK_INT;  
    16.         uninstall(packageDir, sdkVersion);  
    17.     }  
    18.   
    19. }  

    6,测试

           好了,应用是做完了,我们clean一下工程,然后启动一个基于ARM的模拟器,运行这个程序,回到桌面,点击应用图片——卸载掉这个应用,看看效果:


    好了,大家看看效果吧,实际上打开的网页应该是用户反馈调查页面,由于我暂时没有服务器,所以将网址定向到了百度首页了,大家在开发的时候,可以将execlp函数里的参数网址改成自己的服务器网址,这样就大功告成了。检查一下Log日志的输出:


    看到了,LOG输入日志跟代码流程是一致的,好了,源码在下面的链接下,有兴趣的朋友可以下载研究,欢迎你给我提出宝贵意见,大家一起学习一起进步!

    经过查询资料,我已经了解不使用while(true)轮询方式,改用Linux的Inotify机制监听应用安装目录的实现方法了,关于最新优化版本的案例已经做完,请点击这里查看实现原理和代码:Android NDK开发(九)——应用监听自身卸载升级版,使用Inotify监听安装目录


    源码请在这里下载

    展开全文
  • 两个月前发了两篇有关监听自己是否被卸载和卸载反馈功能实现的博客,第二版的地址如下:http://www.cnblogs.com/zealotrouge/p/3159772.html,感谢@whiletrue_童鞋发现了我的代码在4.2.2系统上无法实现卸载反馈,...

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

    注:推荐到原文查看文章

    展开全文
  • 监听卸载情景和原理分析 1,情景分析  在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我将...
  • 测试开发笔记

    万次阅读 多人点赞 2019-11-14 17:11:58
    提高产品的适用范围和使用质量标准 1、明确进行α测试的版本 2、邀请潜在用户进行使用体验 3、针对用户提出的问题进行修复或改进 β测试 潜在用户: 1、安装软件并使用 客服人员: 记录并反馈用户的问题 提前占领...
  • 完美可用-DirectX修复工具增强版DirectX Repair

    万次阅读 热门讨论 2018-12-06 11:19:34
    如在常规模式下无法成功修复c++,可直接在本程序的选项界面内开启强力修复功能,可大幅提高修复成功率。此功能为试验性功能,请仅在常规修复无效时再使用。  新增API Sets强力修复功能。参照DirectX相关文件的精细...
  • APP卸载量统计方案

    2021-10-21 00:05:48
    需要对这两个情况进行判断 当判断App的确被卸载之后,可以直接在C层执行 “am” 命令弹出卸载反馈页面,例如 “am start -a android.intent.action.VIEW -d 卸载反馈的url” 下面是可执行Bin文件C代码, 编译完后命名...
  • 卸载 金山毒霸 的方法

    千次阅读 2019-02-19 10:02:37
    我们在使用电脑管家等其它软件卸载金山毒霸时就会弹出如图这样类似的反馈。步骤阅读2所以想要卸载金山毒霸就得使用电脑系统默认的卸载方式才可以,进入系统卸载程序的页面入口是通过控制面板进入,首先我们打开电脑...
  • // 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在   if   ( IN_DELETE_SELF  ==   (( struct  inotify_event  *)  p_buf )-> mask )   {  FILE  * p_appDir  =   fopen ( APP_DIR...
  • 从前慢-各种工具的安装与卸载

    千次阅读 2021-01-22 20:00:03
    vue-cli的卸载与安装 vue-cli的卸载与安装 安装 1.x或2.x npm install vue-cli -g 3.x以上 npm install -g @vue/cli 卸载 前提条件 自己电脑已经安装node.js和npm 卸载vue-cli(1.x或2.x) npm uninstall vue-cli ...
  • 监听卸载情景和原理分析 1,情景分析  在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我将...
  • 不知道大家在使用一个Android应用的时候,当我们卸载这个应用后,设备上会弹出一个“用户反馈调查”的网页出来,也许很多人没有留意过或者直接忽视了,那么从现在开始请留意,大家不妨下载一下“豌豆荚”“360”之类...
  • 重新安装了window7 旗舰版系统,但是安装Visual Studio时总是安装程序都运行失败,经过百度,可能是.Net Framwork的原因,于是卸载,使用百度上的方法总是卸载不掉.很幸运,看到了一遍这篇文章.重新安装.Net Framwork 4.6....
  • 手机模拟大师卸载

    千次阅读 2020-10-22 10:12:16
    手机模拟大师卸载 前两天为了下载一个软件的汉化包在一个非官方网站上下了一个文件,谁知道下载完之后安装显示什么P2P下载方式,就察觉不对了,桌面上多了一堆应用图标,最近添加里也多了一些不知名的东西,重点是弹...
  • Oracle 11g 服务器端卸载教程 提示:本文章是仅用于卸载oracle11g服务器教程,学会了此教程你未来在重新安装oracle有所帮助 一:断开连接: 注:要断开服务中oracle的所有服务链接 1.1 右击电脑的管理打开->服务...
  • 监听卸载情景和原理分析 1,情景分析 在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我将...
  • Python和Pycharm安装与卸载教程

    千次阅读 2020-10-26 22:12:44
    登录系统,我申请了学生免费授权,所以采用账号密码直接登录 激活成功 卸载 Python卸载 找到Python的安装目录,双击.exe文件 点击【Uninstall】卸载 Pycharm卸载 找到Pycharm安装目录下的bin文件夹,找到...
  • WPF开发教程

    万次阅读 多人点赞 2019-07-02 23:13:20
    ------WPF开发教程 目录 WPF基础入门....... 1. WPF基础之体系结构......2. WPF基础之XAML....3. WPF基础之基元素......4. WPF基础之属性系统......5. WPF基础之路由事件......6. WPF基础之布局系统......7. WPF基础之样式设置和模板...
  • .NET4.6.2无法卸载或.NET安装失败

    万次阅读 热门讨论 2018-07-16 17:09:45
    最近发现很多人按照我这个方法处理不了,这8种方案是需要您自己根据现场情况自行判断那种更合适,您...公司的产品是依赖.NET环境的,所以很多客户都无法使用产品,收到很多反馈。 一开始技术支持部门并没有在意,就...
  • 1)卸载vscode umake ide visual-studio-code --remove 3、方式二:直接官网下载与安装 注意:有的时候通过方式一安装vscode之后发现无法正常使用,表现为命令行中无code命令,也无法搜索到安装好的vscode,该问题...
  • 查看系统的挂载点信息 df -h 查看数据盘是否挂载成功,挂载成功就继续下一步! 6.设置开机自动挂载数据盘 echo'/dev/disk/by-id/数据盘名称 /挂载点 ext4 defaults,nofail,discard 0 0' | sudo tee -a /...
  • cad2020卸载工具This sponsored article was created by our content partner, Mekanism. Thank you for supporting the partners who make SitePoint possible. 这篇赞助文章是由我们的内容合作伙伴Mekanism创建的...
  • 无法卸载 Office 2003 \2007

    热门讨论 2011-10-19 17:42:57
    当您尝试再次安装该程序时,将不会成功。 •此时无法删除以前安装的程序,因为安装文件已损坏。 •如果以前尝试安装 Office 2010 时遇到问题。 回到顶端 解决方案 解决该问题的步骤 验证是否无法使用“添加或...
  • 监听卸载情景和原理分析 1,情景分析  在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底。这篇博客,我...
  • Anaconda详细安装及使用教程(带图文)

    万次阅读 多人点赞 2018-08-15 17:48:52
    用命令“conda list”查看已安装的包,从这些库中我们可以发现NumPy,SciPy,Matplotlib,Pandas,说明已经安装成功了!   还可以使用conda命令进行一些包的安装和更新 conda list:列出所有的已安装的...
  • Linux软件包安装与卸载

    千次阅读 2018-05-28 22:44:04
    [root@zyshanlinux-01 Packages]# rpm -e zsh ##卸载成功 [root@zyshanlinux-01 Packages]# [root@zyshanlinux-01 Packages]# [root@zyshanlinux-01 Packages]# rpm -e xz ##卸载失败,需要先卸载其他依赖...
  • Android P 9 实现静默安装静默卸载

    千次阅读 热门讨论 2019-10-24 09:43:53
    Log.d(TAG, "onFinished() 安装成功"); } else { Log.d(TAG, "onFinished() 安装失败"); } } } }   进行安装的代码   /** * 适配android9的安装方法。 * 全部替换安装 */ public void ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,394
精华内容 3,357
关键字:

卸载反馈成功