>>分享Android开发相关的技术 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 19088 个阅读者 刷新本主题
 * 贴子主题:  Android 如何监听自身应用被卸载 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2020-05-15 01:16:27     消息  查看  搜索  好友  邮件  复制  引用

前段时间有个同事问我android应用在卸载以后,如何能够通知一下服务器,让用户填写一下卸载的原因,以求为将来的应用修改积累数据。当时他是有段源代码的,但是有点小问题,我只是帮他定位一下了代码的问题,具体细节没有研究。又加上最近工作比较繁忙,所以就放下来了,今天稍微有点空,就自己做了一个应用demo,告知一下诸位在Android中如何做到监听自身应用被卸载了。

一  效果演示

打开应用效果图:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小
  
如下图:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

然后退出应用,卸载程序,会发现当应用被卸载以后,会弹出调用浏览器的提示,这里随便放了一个搜狐浏览页面,在自己的应用中应该调用的一般都是调查页面。如下图:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

ok,效果前面已经演示了,现在需要讨论一下其具体实现了。

首先,通过adb shell进入手机,然后第一次进入应用,像图1一样,不点击按钮,通过 ps | busybox grep ubuntu 看这个应用的进程信息,如下图:

点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

这个时候只有

     u0_a108   2953  124   490956 47792 ffffffff 40052a40 S com.example.ubuntuforandroid
    2953 这一个进程

   点击 卸载后提示 按钮再次,执行刚才执行的ps命令,发现已经有两个进程了如下图:

   点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

   其实新产生的进程是通过程序调用jni接口

   public static native int Reguninstall(String path,String url);

   这个接口fork了一个进程,而新fork的进程负责监听本应用是否被卸载了

      二  源码分析

    java层的代码如下,很简单,就是调用一下jni接口
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
                      
    setContentView(R.layout.activity_main);
                
    initInjectFunction("testfile");
                
    test = (TextView)this.findViewById(R.id.testview);
    test.setText("点击卸载后提示按钮,你的应用在卸载以后会调用浏览器,然后调用你需要的页面。");
                              
    btn = (Button)this.findViewById(R.id.testbtn);
    btn.setOnClickListener(new View.OnClickListener() {
                    
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            String directory = MainActivity.this.getFilesDir().getAbsolutePath();
            String url = "http://www.sohu.com/";
            JniExec.Reguninstall(directory,url);
            test.setText("现在可以退出应用,然后卸载应用,看看是否有效果");
        }
    });
}

一目了然,不用多言了

现在就分析

package com.example.ubuntuforandroid;
public class JniExec {
    static {
        System.loadLibrary("uninstall");
    }
      
    public static native int Reguninstall(String path,String url);
}

   Reguninstall 这个jni接口里面做了什么事情,能够达到监听本身应用卸载的效果。

             native代码分析

jint Java_com_example_ubuntuforandroid_JniExec_Reguninstall(JNIEnv* env, jobject thiz, jstring path,
        jstring url)
{
    LOGI("Java_com_example_ubuntuforandroid_JniExec_Reguninstall");
    char *listenpath = (char*) (*env)->GetStringUTFChars(env,path, 0);
    char *jumpurl = (char*) (*env)->GetStringUTFChars(env,url, 0);
    LOGI("notify path is  %s",listenpath);
    LOGI("jumpurl  is  %s",jumpurl);
    pid_t pid;
    pid = fork();
    if(pid == 0)
    {
        //子进程
        inotify_main(listenpath,jumpurl);
    }
    //父进程不阻塞调用 waitpid  ok 子进程变成了孤儿进程,被init进程收养了
    pid = waitpid(-1,0,1);
    LOGI("father bye bye");
    return 0;
}

          这个接口里面最关键的是调用了 inotify_main 这个函数。如果看这段代码比较费力的话,建议先弄清楚linux 下的fork机制,搞清楚 孤儿进程 僵尸进程这些如何产生的情况。

下面看   inotify_main 这个函数

void inotify_main(char *path,char *url){
    struct pollfd poll_list[2];
    poll_list[0].fd = inotify_init();
    poll_list[0].events = POLLIN;
    int wd = inotify_add_watch(poll_list[0].fd, path, IN_DELETE | IN_CREATE);
    if(wd < 0) {
        fprintf(stderr, "could not add watch for %s, %s
", path, strerror(errno));
        return ;
    }
    int retval;
    while(1)
    {
        retval = poll(poll_list,(unsigned long)1,-1);
        /* retval 总是大于0或为-1,因为我们在阻塞中工作 */
        LOGI("retval = %d
",retval);
        if(retval < 0)
        {
            fprintf(stderr,"poll错误: %s/n",strerror(errno));
            return;
        }
        if((poll_list[0].revents & POLLIN) == POLLIN)
        {
          LOGI("poll_list[0].revents&POLLIN
");
          inotify_handle(poll_list[0].fd,url);
        }
    }
     inotify_rm_watch(poll_list[0].fd,wd);
}

          这个函数,这里面用到了  inotify_init  inotify_add_watch  inotify_rm_watch 这几个linux接口,这几个接口主要的作用就是监听指定的目录,其配合poll函数,能够监听目录下的任何改动,当要监听的目录有任何改动的时候,会触发poll函数的 POLLIN有可读数据到来事件。

在本应用中,监听的是 /data/data/com.example.ubuntuforandroid/files/ 这个目录,因为在卸载的时候卸载程序会删除监听目录的,而fork出来的守护进程当发现自身应用的目录被卸载程序删除了也就是卸载了,这个时候调用 inotify_handle 这个函数,然后调用 am命令启动浏览器,调用自己需要调用界面。

当然,在调用am指令以后,记得自身守护进程的使命也完成了,需要exit退出一下。

inotify接口的用处很多,扩展一下,也可以用于守护进程,比如A进程和B进程是共生共死的,这里有一种实现的方式就是 A进程用inotify 监听 /proc/B进程id目录,当B进程结束的时候,A进程就能知道B进程不在了,从而结束自己。

----------------------------
原文链接:https://blog.51cto.com/sunzeduo/1376117

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 flybird 在 2020-06-06 15:44:29 重新编辑]
  Java面向对象编程-->集合(下)
  JavaWeb开发-->JSP技术详解(Ⅱ)
  JSP与Hibernate开发-->立即检索和延迟检索策略
  Java网络编程-->对象的序列化与反序列化
  精通Spring-->Vue组件开发基础
  Vue3开发-->虚拟DOM和render()函数
  Android面试题汇总
  Android定义的路径全局变量汇总
  Android开发实践:编译VLC
  Android Broadcast receiver 编程
  Android带有粘性头部的ScrollView-WelliJohn的博客
  android cts 相关
  Android 自定义Menu
  android实用测试方法之Monkey与MonkeyRunner
  Android网络编程之WebKit应用
  创建 和使用Android服务
  Android开发实践:Android.mk模板
  Android语音识别 android.speech 包分析
  Android性能优化技巧
  一款在Linux下运行Android应用的软件:xDroid
  Appbarlayout+Recycleview滑动效果颜色渐变
  更多...
 IPIP: 已设置保密
楼主      
1页 0条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。