想起360被卸载之后会跳转到指定的反馈页面,是怎么弄的?就百度了一下,果然网上似乎有相关的问题的解答,这里就将他们的步骤在细化一下了:

其实这个问题的核心就在于:应用被卸载了,如果能够做到后续的代码逻辑继续执行

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

一个应用被用户卸载肯定是有理由的,而开发者却未必能得知这一重要的理由,毕竟用户很少会主动反馈建议,多半就是用得不爽就卸,如果能在被卸载后获取到用户的一些反馈,那对开发者进一步改进应用是非常有利的。目前据我所知,国内的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端进程在应用被卸载后不会被销毁。

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

/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */  #include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <android/log.h>
#include <unistd.h>
#include <sys/inotify.h>  #include "com_example_uninstalldemos_NativeClass.h"  /* 宏定义begin */
//清0宏
#define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize)  #define LOG_TAG "onEvent"  //LOG宏定义
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)  JNIEXPORT jstring JNICALL Java_com_example_uninstalldemos_NativeClass_init(JNIEnv* env, jobject thiz) {  //初始化log  LOGD("init start...");  //fork子进程,以执行轮询任务  pid_t pid = fork();  if (pid < 0) {  //出错log  LOGD("fork failed...");  } else if (pid == 0) {  //子进程注册"/data/data/pym.test.uninstalledobserver"目录监听器  int fileDescriptor = inotify_init();  if (fileDescriptor < 0) {  LOGD("inotify_init failed...");  exit(1);  }  int watchDescriptor;  watchDescriptor = inotify_add_watch(fileDescriptor,"/data/data/com.example.uninstalldemos", IN_DELETE);  LOGD("watchDescriptor=%d",watchDescriptor);  if (watchDescriptor < 0) {  LOGD("inotify_add_watch failed...");  exit(1);  }  //分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event  void *p_buf = malloc(sizeof(struct inotify_event));  if (p_buf == NULL) {  LOGD("malloc failed...");  exit(1);  }  //开始监听  LOGD("start observer...");  size_t readBytes = read(fileDescriptor, p_buf,sizeof(struct inotify_event));  //read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器  free(p_buf);  inotify_rm_watch(fileDescriptor, IN_DELETE);  //目录不存在log  LOGD("uninstall");  //执行命令am start -a android.intent.action.VIEW -d http://shouji.360.cn/web/uninstall/uninstall.html  execlp(  "am", "am", "start", "-a", "android.intent.action.VIEW", "-d",   "http://shouji.360.cn/web/uninstall/uninstall.html", (char *)NULL);  //4.2以上的系统由于用户权限管理更严格,需要加上 --user 0  //execlp("am", "am", "start", "--user", "0", "-a",  //"android.intent.action.VIEW", "-d", "https://www.google.com",(char *) NULL);  } else {  //父进程直接退出,使子进程被init进程领养,以避免子进程僵死  }  return (*env)->NewStringUTF(env, "Hello from JNI !");
}  

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

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

Android应用程序代码:

MyActivity.java

package com.example.uninstalldemos;  import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;  public class MyActivity extends Activity {  @Override  public void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  Intent intent = new Intent(this, SDCardListenSer.class);  startService(intent);  NativeClass nativeObj = new NativeClass();  nativeObj.init();  }  static {  Log.d("onEvent", "load jni lib");  System.loadLibrary("hello-jni");  }
}  

SDCardListenSer.java

package com.example.uninstalldemos;  import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.FileObserver;
import android.os.IBinder;
import android.util.Log;
import java.io.File;
import java.io.IOException;  public class SDCardListenSer extends Service {  SDCardListener[] listenners;  @SuppressLint("SdCardPath")  @Override  public void onCreate() {  SDCardListener[] listenners = {   new SDCardListener("/data/data/com.example.uninstalldemos", this),  new SDCardListener(Environment.getExternalStorageDirectory() + File.separator + "1.txt", this) };  this.listenners = listenners;  Log.i("onEvent", "=========onCreate============");  for (SDCardListener listener : listenners) {  listener.startWatching();  }  File file = new File(Environment.getExternalStorageDirectory() + File.separator + "1.txt");  Log.i("onEvent", "dddddddddddddddddddddd nCreate============");  if (file.exists())  file.delete();  /*try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); }*/  }  @Override  public void onDestroy() {  for (SDCardListener listener : listenners) {  listener.stopWatching();  }  }  @Override  public IBinder onBind(Intent intent) {  return null;  }
}  class SDCardListener extends FileObserver {  private String mPath;  private final Context mContext;  public SDCardListener(String parentpath, Context ctx) {  super(parentpath);  this.mPath = parentpath;  this.mContext = ctx;  }  @Override  public void onEvent(int event, String path) {  int action = event & FileObserver.ALL_EVENTS;  switch (action) {  case FileObserver.DELETE:  Log.i("onEvent", "delete path: " + mPath + File.separator + path);  //openBrowser();  break;  case FileObserver.MODIFY:  Log.i("onEvent", "更改目录" + mPath + File.separator + path);  break;  case FileObserver.CREATE:  Log.i("onEvent", "创建文件" + mPath + File.separator + path);  break;  default:  break;  }  }  protected void openBrowser() {  Uri uri = Uri.parse("http://aoi.androidesk.com");  Intent intent = new Intent(Intent.ACTION_VIEW, uri);  mContext.startActivity(intent);  }  public void exeShell(String cmd) {  try {  Runtime.getRuntime().exec(cmd);  } catch (Throwable t) {  t.printStackTrace();  }  }  }  

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

运行:

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

adb logcat -s onEvent

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

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

总结:

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

PS: 上面的总结同样可以用于 Service 不死的做法,关于这方便的请看:http://blog.csdn.net/u012573920/article/details/49612955

JNI demo     Android Demo下载

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

  1. 谷歌浏览器卸载后点击安装程序没反应

    谷歌浏览器卸载后点击安装程序没反应 在桌面新建ch.txt文件,将下面内容复制进去,并更改后缀为.reg,如图所示 然后双击运行,如果你装有360这样的防护软件,会提示是否允许什么的,点允许,再重装c ...

  2. Oracle9i卸载后再次安装,设置的SID相同出现“指定的SID在本机上已经存在。请指定一个不同的SID。”...

    Oracle9.0.2卸载后再次安装,设置的SID相同出现"指定的SID在本机上已经存在.请指定一个不同的SID." 记得阿都跟我说过要全部清除注册表. 但是考虑到出现相同的SID ...

  3. Android进入商店并跳转到指定应用

    Android进入商店并跳转到指定应用 效果图 对话框 // 提示好评 Dialog dialog = new AlertDialog.Builder(this).setTitle("评价& ...

  4. 【愚公系列】2022年11月 微信小程序-优购电商项目-意见反馈页面

    文章目录 前言 一.意见反馈页面 1. 业务逻辑 2. 关键技术 二.意见反馈页面代码 1.图片上传组件 2.页面代码 3.效果 前言 对于需要收集产品上线后用户实际使用感受的,意见反馈绝对是一个很好 ...

  5. web网页浏览器唤起wpf,winform exe桌面程序,并跳转到指定页面

    背景案例 我们看到网页上打开百度网盘,下载的时候会通过浏览器唤起百度网盘桌面端,并进入到指定页面,我们要做的,就是达到类似的效果 实现流程 1.写注册表 在桌面软件首次启动的时候,像注册表中写入程序的 ...

  6. 服务器主板用360优化后崩溃,为什么卸载了360以后,电脑反而更流畅了?电脑高手这样解释...

    原标题:为什么卸载了360以后,电脑反而更流畅了?电脑高手这样解释 最近有个朋友对小编说了这么件事,说前几天他的电脑非常的卡,浏览个网页都会死机,然后他请教了一个比较懂电脑的朋友. 问他:我的电脑非常 ...

  7. android程序无法卸载,无法从Android卸载SoundHound应用程序

    我的HTC Desire S单元的SoundHound application出现严重问题. 通过输入"应用程序>管理应用程序",找到SoundHound并将其卸载,我应该能 ...

  8. 仿360手机卫士首页[android平台]

    学习android几个月了,对android的UI开发部分一直不是很熟悉.于是最近拿360手机卫士首页来练手,做了个小demo,想与各位多多交流.效果图如下: (1)首页 (2)transformat ...

  9. Android 仿王者荣耀广告弹窗,android仿王者荣耀对战资料之能力图

    TX的王者荣耀最近是火的不得了啊,小编也是最近入坑.玩了几天之后,发现对战资料之能力图,蛮有意思(其实我想说,我们的产品妹子是个农药迷,非要把农药的能力图搬到我们的app中),于是就有了下面的自定义v ...

最新文章

  1. 快速搭建samba服务
  2. python多线程内存越要越大_Python 面试:这9个问题你一定要掌握!
  3. java知识回顾_Java7 –回顾
  4. QT:基本知识(一);
  5. 【系列7】使用Dockerfile创建带mysql的Centos Docker镜像
  6. Java工作笔记-Map的基本用法
  7. aba问题mysql_面试题总结:可能是全网最好的MySQL重要知识点
  8. javascript常用tool.js
  9. base64原理与实现
  10. 关于计算机ps读后感,ps心得体会4篇
  11. 网站CNZZ数据统计的实现方式
  12. 浅析eTS的起源和演进
  13. 90后,为什么我建议你不要老是加班?
  14. python 分类型数据转化数值型
  15. 第八届蓝桥杯个人赛赛后总结
  16. VIEWGOOD(远古)供应校园网视频点播系统
  17. Grayscale一周增持9503个BTC,机构投资者“抢购”有望继续推高价格
  18. 【论文】优秀的论文记录
  19. UIM卡 PIN 码特点
  20. XCode打包静态库文件

热门文章

  1. Minus – Share simply.
  2. 磨刀不误砍柴工 —— 自己动手写操作系统 入门导引
  3. vs的新项目建立(基础)
  4. Linux ❉ tmpfs详解
  5. 浅析Lambda表达式
  6. 炫界 (302) -(查动简)_Miniforms:以极简主义之名玩弄色彩于家居美学之中
  7. 跑过风景,跑过你 - 我的首个全程马拉松
  8. 小满未满第三期|小满是什么季节,这些小满海报来告诉你
  9. 交换机如何设置控制IP地址冲突故障
  10. 世界杯将至,体育类加密项目迎来春天?