一、概述
最近公司项目为了版本升级方便,对公司的项目做了平台化管理,相应的也添加了新的功能。其中就包括卸载模块,要求卸载平台的时候附带着卸载手机上已经安装的与平台相关的应用。
那么要解决这个问题,我们就可能需要先来处理一下几个问题:
1. 通过什么样的方式来监听平台被卸载这个事件
2. 卸载平台的同时如何去卸载其他相关的应用
二、监听卸载事件
1. 直接在平台上注册一个广播来监听这个卸载事件,但是仔细想想就会发现这是不可行的,因为在系统发送卸载这个应用的广播之前,这个应用就已经杀死并被卸载了,所以根本不会再接收到这个广播了
2. 既然不能直接监听卸载这个广播,那么我们是不是可以在卸载平台之前做一些操作,之前有看过一个大神的帖子,发现在应用被卸载之前,系统会先删除data目录下对应的应用的文件夹。所以我准备通过FileObserver来监听这个文件的状态,如果是DELETE状态就表示删除。通过验证发现是可以监听到的,但是不知道是不是我的代码有问题,执行速度跟不上,还没来得级去卸载其他应用,平台就被卸载完了。所以我放弃了这种方法。
3. 在每个apk中都注册一个广播,来监听应用的卸载事件,然后根据包名来判断
三、具体实现
1. 在相关应用里面通过xml的形式注册一个常住型广播:
<receiver android:name=".broadcast.ListenerReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="960"> <action android:name="android.intent.action.PACKAGE_ADDED" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> <action android:name="android.intent.action.BOOT_COMPLETED"/> <data android:scheme="package" /> </intent-filter> </receiver>在这里,我添加了一个开机广播的action,为了防止手机重启之后,没有运行应用而无法监听到卸载的这种情况。并设置了priority属性,系统会根据这个属性来决定接收这个广播的先后顺序。
2. 创建一个BroadcaseReceiver来接收这个广播:
public class ListenerReceiver extends BroadcastReceiver { public ListenerReceiver() { } @Override public void onReceive(Context context, Intent intent) { //接收卸载广播 if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) { String packageName = intent.getDataString(); if(packageName.equals("平台包名") || packageName.equals("应用一的包名") || packageName.equals("应用二的包名") || packageName.equals("应用三的包名")){ ApkUtiles.hasInstall(context); abortBroadcast(); } } } }这里先监听卸载这个事件,然后根据包名来判断卸载的是哪个应用,然后再进行相关操作,这里我调用了absortBroadcast()这个方法来终止这个广播,为了防止多个应用重复对这个广播进行监听处理。
3. 判断手机上是否已经安装了我们的相关应用:
/** * 判断是否已经安装指定程序 */ public static void hasInstall(Context context){ PackageInfo packageInfo; String[] str = {"平台包名" ,"应用一包名" , "应用二包名", "应用三包名" }; for(int i = 0; i < str.length ; i++){ try { packageInfo = context.getPackageManager().getPackageInfo(str[i], 0); }catch (PackageManager.NameNotFoundException e) { packageInfo = null; e.printStackTrace(); } if(packageInfo ==null){ LogUtils.e("没有安装 :" + str[i]); }else{ if(i == 0){ //表名卸载的是相关应用(不是平台) return; } // LogUtils.e("安装了程序 :" + str[i]); uninstall(context , str[i]); return; } } }这里需要注意,str数组里面存放的都是我们所有应用的包名,最后一个元素必须是当前注册该广播的应用的包名,否则会出现卸载不干净的情况,因为广播里面有做absortBroadcast()处理。
4. 根据包名卸载指定的应用:
/** 卸载一个app */ public static void uninstall(Context context, String packageName) { //通过程序的包名创建URI Uri packageURI = Uri.parse("package:" + packageName); //创建Intent意图 Intent intent = new Intent(Intent.ACTION_DELETE, packageURI); //执行卸载程序 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); }这里有一点不是很清楚:如果没有添加intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),就不会执行弹出让用户确认卸载应用的activity。这个应该和任务栈有关,以后有空再补补相关知识。
这是本人第一次写博客,有写的不好的地方或者错误的地方,希望大家能提出来一起交流
补充:
安卓3.1之后,google考虑到一些安全问题,避免一些流氓软件、病毒什么的干坏事,所以对广播机制做了一些改变。给广播新添加了两个Flag,分别是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,分别表示是否允许被force stop的应用能够接收到这个广播,并且系统默认给系统类型的广播(例如:开机、卸载、短信等)添加了一个Intent.FLAG_EXCLUDE_STOPPED_PACKAGES,这也就意味着如果我们清理内存之后,我们的应用就会进入到force stop状态,也就不会再能接收到系统的广播了。所以这也导致了我上门通过广播的方式而无法监听到平台被卸载的bug。但是也没办法,这是系统广播的机制决定的,也只能接受这个现实了。