Android卸载程序之后跳转到指定的反馈页面

转载 2015年07月08日 11:47:11

我们再来仔细分析一下场景和流程

一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的。目前据我所知,国内的Android应用中实现这一功能的只有360手机卫士、360平板卫士,那么如何实现这一功能的?

我们可以把实现卸载反馈的问题转化为监听自己是否被卸载,只有得知自己被卸载,才可以设计相应的反馈处理流程。以下的列表是我在研究这一问题的思路:

1、注册BroadcastReceiver,监听"android.intent.action.PACKAGE_REMOVED"系统广播
结果:NO。未写代码,直接分析,卸载的第一步就是退出当前应用的主进程,而此广播是在已经卸载完成后才发出的,此时主进程都没有了,去哪onReceive()呢?

2、若能收到"将要卸载XX包"的系统广播,在主进程被退出之前就抢先进行反馈处理就好了,可惜没有这样的系统广播,不过经过调研,倒是发现了一个办法,读取系统log,当日志中包含"android.intent.action.DELETE"和自己的包名时,意味着自己将要被卸载。
结果:NO。调试时发现此方法有两个缺陷,(1)点击设置中的卸载按钮即发出此Intent,此时用户尚未在弹框中确认卸载;(2)pm命令卸载不出发此Intent,意味着被诸如手机安全管家,豌豆荚等软件卸载时,无法提前得知卸载意图。

3、由于时间点不容易把控,所以干脆不依赖系统广播或log,考虑到卸载过程会删除"/data/data/包名"目录,我们可以用线程直接轮询这个目录是否存在,以此为依据判断自己是否被卸载。
结果:NO。同方法1,主进程退出,相应的线程必定退出,线程还没等到判断目录是否存在就已经被销毁了。

4、改用C端进程轮询"/data/data/包名"目录是否存在
结果:YES。借助Java端进程fork出来的C端进程在应用被卸载后不会被销毁。


解决的方案确定了,下面来看一下代码吧:

  1. /* 
  2.  * Copyright (C) 2009 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  * 
  16.  */  
  17.   
  18. #include <jni.h>  
  19. #include <stdio.h>  
  20. #include <stdlib.h>  
  21. #include <string.h>  
  22. #include <android/log.h>  
  23. #include <unistd.h>  
  24. #include <sys/inotify.h>  
  25.   
  26. #include "com_example_uninstalldemos_NativeClass.h"  
  27.   
  28. /* 宏定义begin */  
  29. //清0宏  
  30. #define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize)  
  31.   
  32. #define LOG_TAG "onEvent"  
  33.   
  34. //LOG宏定义  
  35. #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)  
  36.   
  37. JNIEXPORT jstring JNICALL Java_com_example_uninstalldemos_NativeClass_init(JNIEnv* env, jobject thiz) {  
  38.   
  39.     //初始化log  
  40.     LOGD("init start...");  
  41.   
  42.     //fork子进程,以执行轮询任务  
  43.     pid_t pid = fork();  
  44.     if (pid < 0) {  
  45.         //出错log  
  46.         LOGD("fork failed...");  
  47.     } else if (pid == 0) {  
  48.         //子进程注册"/data/data/pym.test.uninstalledobserver"目录监听器  
  49.         int fileDescriptor = inotify_init();  
  50.         if (fileDescriptor < 0) {  
  51.             LOGD("inotify_init failed...");  
  52.             exit(1);  
  53.         }  
  54.   
  55.         int watchDescriptor;  
  56.         watchDescriptor = inotify_add_watch(fileDescriptor,"/data/data/com.example.uninstalldemos", IN_DELETE);  
  57.         LOGD("watchDescriptor=%d",watchDescriptor);  
  58.         if (watchDescriptor < 0) {  
  59.             LOGD("inotify_add_watch failed...");  
  60.             exit(1);  
  61.         }  
  62.   
  63.         //分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event  
  64.         void *p_buf = malloc(sizeof(struct inotify_event));  
  65.         if (p_buf == NULL) {  
  66.             LOGD("malloc failed...");  
  67.             exit(1);  
  68.         }  
  69.         //开始监听  
  70.         LOGD("start observer...");  
  71.         size_t readBytes = read(fileDescriptor, p_buf,sizeof(struct inotify_event));  
  72.   
  73.         //read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器  
  74.         free(p_buf);  
  75.         inotify_rm_watch(fileDescriptor, IN_DELETE);  
  76.   
  77.         //目录不存在log  
  78.         LOGD("uninstall");  
  79.   
  80.         //执行命令am start -a android.intent.action.VIEW -d http://shouji.360.cn/web/uninstall/uninstall.html  
  81.         execlp(  
  82.             "am""am""start""-a""android.intent.action.VIEW""-d",   
  83.             "http://shouji.360.cn/web/uninstall/uninstall.html", (char *)NULL);  
  84.         //4.2以上的系统由于用户权限管理更严格,需要加上 --user 0  
  85.         //execlp("am", "am", "start", "--user", "0", "-a",  
  86.         //"android.intent.action.VIEW", "-d", "https://www.google.com",(char *) NULL);  
  87.   
  88.     } else {  
  89.         //父进程直接退出,使子进程被init进程领养,以避免子进程僵死  
  90.     }  
  91.   
  92.     return (*env)->NewStringUTF(env, "Hello from JNI !");  
  93. }  


这里面主要是用到了Linux中的inotify,这个相关的内容可以自行百度一下~~

这里有一个很重要的知识,也是解决这个问题的关键所在,就是Linux中父进程死了,但是子进程不会死,而是被init进程领养。所以当我们应用(进程)卸载了,但是我们fork的子进程并不会销毁,所以我们上述的逻辑代码就可以放到这里来做了。(学习了)


Android应用程序代码:

MyActivity.java

  1. package com.example.uninstalldemos;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7.   
  8. public class MyActivity extends Activity {  
  9.   
  10.     @Override  
  11.     public void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.   
  15.         Intent intent = new Intent(this, SDCardListenSer.class);  
  16.         startService(intent);  
  17.         NativeClass nativeObj = new NativeClass();  
  18.         nativeObj.init();  
  19.     }  
  20.   
  21.     static {  
  22.         Log.d("onEvent""load jni lib");  
  23.         System.loadLibrary("hello-jni");  
  24.     }  
  25. }  
SDCardListenSer.java
[java] view plaincopy
  1. package com.example.uninstalldemos;  
  2.   
  3. import android.annotation.SuppressLint;  
  4. import android.app.Service;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.net.Uri;  
  8. import android.os.Environment;  
  9. import android.os.FileObserver;  
  10. import android.os.IBinder;  
  11. import android.util.Log;  
  12. import java.io.File;  
  13. import java.io.IOException;  
  14.   
  15. public class SDCardListenSer extends Service {  
  16.     SDCardListener[] listenners;  
  17.   
  18.     @SuppressLint("SdCardPath")  
  19.     @Override  
  20.     public void onCreate() {  
  21.         SDCardListener[] listenners = {   
  22.                 new SDCardListener("/data/data/com.example.uninstalldemos"this),  
  23.                 new SDCardListener(Environment.getExternalStorageDirectory() + File.separator + "1.txt"this) };  
  24.         this.listenners = listenners;  
  25.   
  26.         Log.i("onEvent""=========onCreate============");  
  27.         for (SDCardListener listener : listenners) {  
  28.             listener.startWatching();  
  29.         }  
  30.   
  31.         File file = new File(Environment.getExternalStorageDirectory() + File.separator + "1.txt");  
  32.         Log.i("onEvent""dddddddddddddddddddddd nCreate============");  
  33.         if (file.exists())  
  34.             file.delete();  
  35.         /*try { 
  36.             file.createNewFile(); 
  37.         } catch (IOException e) { 
  38.             e.printStackTrace(); 
  39.         }*/  
  40.     }  
  41.   
  42.     @Override  
  43.     public void onDestroy() {  
  44.         for (SDCardListener listener : listenners) {  
  45.             listener.stopWatching();  
  46.         }  
  47.     }  
  48.   
  49.     @Override  
  50.     public IBinder onBind(Intent intent) {  
  51.         return null;  
  52.     }  
  53. }  
  54.   
  55. class SDCardListener extends FileObserver {  
  56.     private String mPath;  
  57.     private final Context mContext;  
  58.   
  59.     public SDCardListener(String parentpath, Context ctx) {  
  60.         super(parentpath);  
  61.         this.mPath = parentpath;  
  62.         this.mContext = ctx;  
  63.     }  
  64.   
  65.     @Override  
  66.     public void onEvent(int event, String path) {  
  67.         int action = event & FileObserver.ALL_EVENTS;  
  68.         switch (action) {  
  69.   
  70.         case FileObserver.DELETE:  
  71.             Log.i("onEvent""delete path: " + mPath + File.separator + path);  
  72.             //openBrowser();  
  73.             break;  
  74.   
  75.         case FileObserver.MODIFY:  
  76.             Log.i("onEvent""更改目录" + mPath + File.separator + path);  
  77.             break;  
  78.   
  79.         case FileObserver.CREATE:  
  80.             Log.i("onEvent""创建文件" + mPath + File.separator + path);  
  81.             break;  
  82.   
  83.         default:  
  84.             break;  
  85.         }  
  86.     }  
  87.   
  88.     protected void openBrowser() {  
  89.         Uri uri = Uri.parse("http://aoi.androidesk.com");  
  90.         Intent intent = new Intent(Intent.ACTION_VIEW, uri);  
  91.         mContext.startActivity(intent);  
  92.     }  
  93.   
  94.     public void exeShell(String cmd) {  
  95.         try {  
  96.             Runtime.getRuntime().exec(cmd);  
  97.         } catch (Throwable t) {  
  98.             t.printStackTrace();  
  99.         }  
  100.     }  
  101.   
  102. }  


开启一个服务,在这个服务中我们可以看到,用到了一个很重要的一个类FileObserver,也是用来监听文件的变更的,这个和上面的inotify功能差不多。关于这个类的具体用法和介绍,可以自行百度呀~~


运行:

我们将应用安装之后,打开log进行检测日志:

adb logcat -s onEvent



当我们从设置中卸载应用的时候,会弹出如下界面:


注:这里我特定说了是从设置界面中去卸载应用,因为当我使用小米手机自带的那种快捷卸载应用的时候并不会跳转。这个具体的原因还有待解决(当然360的这个问题也没有解决掉。。)


总结:

我写这篇文章的目的以及我从这个过程中唯一学习到的一个知识点就是当父进程消亡了,子进程并不会消亡,所以我们可以记住这个知识点,以后遇到像应用被卸载之后的一些逻辑操作都可以采用这种方式去解决。

相关文章推荐

(仿360卸载后弹窗)Android卸载程序之后跳转到指定的反馈页面

想起360被卸载之后会跳转到指定的反馈页面,是怎么弄的?就百度了一下,果然网上似乎有相关的问题的解答,这里就将他们的步骤在细化一下了: 其实这个问题的核心就在于:应用被卸载了,如果能够做到后续...

Android不被kill的Service与卸载之后跳转出反馈页面

转载:http://blog.csdn.net/jimmylopez/article/details/41015337#comments 最近看到很多android应用卸载之后,都会弹出一个...

Android笔记:实现加载的progressDialog之后跳转到程序主界面

因为考虑到有些程序加载时间相对较长,所以在启动主界面之前,实现一个加载的progressDialog对话框来延缓,相对来说可以提高用户体验度。如果是直接进入主界面,即activity,界面因为加载时间...

android 开发零起步学习笔记(二十九):Android笔记:实现加载的progressDialog之后跳转到程序主界面

因为考虑到有些程序加载时间相对较长,所以在启动主界面之前,实现一个加载的progressDialog对话框来延缓,相对来说可以提高用户体验度。 如果是直接进入主界面,即activity,界面因为...

Android单击通知栏后返回正在运行的程序并跳转到指定fragment页面

今天写通知栏的时候遇到了这样的一个问题:在fragment中发送通知,点击通知后跳转到指定的fragment。第一思路就是创建一个通知,点击通知后启动activity,在activity接受传入过来的...

android应用程序跳转到系统的各个设置页面

在android SDK文档中有这样一个类,android.provider.Settings类提供android系统各个页面的跳转常量: 使用实例例:startActivity(new Int...

android应用程序跳转到系统的各个设置页面

走在前面的人有饭吃,走在后面的人没饭吃,技术先人一步,你就是赢家,欢迎到未来新技术手机操作系统firefox os 5狐论坛网"www.5fox.cn" 在android SDK文档中有这样一个...

从android应用程序跳转到系统的各个设置页面

在android SDK文档中有这样一个类,android.provider.Settings类提供android系统各个页面的跳转常量: 使用实例例: startActivity(n...

android应用程序跳转到系统的各个设置页面

在开发过程中,当我们的程序检测到某个功能项没打开或者没设置的时候,需要我们在程序中跳转设置页面供用户设置后返回我们的程序才能用我们程序的某一功能,这样,我们就有必要去了解以下内容:   在andro...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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