应用的状态变化,包括安装、卸载、更新,是android系统上重要的事件。如何侦听到?有两种方法,一是通过侦听广播,一是实现PackageMonitor。

侦听广播

 
当Package状态发生变化时,系统会广播如下一些Action的Intent:

应用安装:
public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";

应用更新:
public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
应用的新版本替代旧版本被安装
public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
应用的新版本替代旧版本被安装,只发给被更新的应用自己
public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
应用被改变,譬如某些组件被disable/enable

应用卸载:
public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
应用被卸载时发出,正在被卸载的应用自身不会收到
public static final String ACTION_PACKAGE_FULLY_REMOVED = "android.intent.action.PACKAGE_FULLY_REMOVED";
应用被完全卸载时发出(数据被删除)

 
上述Intent都为保护型,只能够由系统发出。
 
针对上述定义,结合android源代码,研究几个问题。Android对于Package的管理主要逻辑在PackageManagerService(PMS)中,主要在这个类中研究上述问题:
 
(1)系统如何实现只发给某个应用?

ACTION_MY_PACKAGE_REPLACED是如何处理的?

private void sendSystemPackageUpdatedBroadcastsInternal() {
Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, removedAppId >= ? removedAppId : uid);
extras.putBoolean(Intent.EXTRA_REPLACING, true);
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, removedPackage,
extras, , null, null, null);
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, removedPackage,
extras, , null, null, null);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
null, , removedPackage, null, null);
} final void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds) {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
final IActivityManager am = ActivityManagerNative.getDefault();
if (am == null) return;
final int[] resolvedUserIds;
if (userIds == null) {
resolvedUserIds = am.getRunningUserIds();
} else {
resolvedUserIds = userIds;
}
for (int id : resolvedUserIds) {
final Intent intent = new Intent(action,
pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
if (extras != null) {
intent.putExtras(extras);
}
if (targetPkg != null) {
intent.setPackage(targetPkg);
}
// Modify the UID when posting to other users
int uid = intent.getIntExtra(Intent.EXTRA_UID, -);
if (uid > && UserHandle.getUserId(uid) != id) {
uid = UserHandle.getUid(id, UserHandle.getAppId(uid));
intent.putExtra(Intent.EXTRA_UID, uid);
}
intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
if (DEBUG_BROADCASTS) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.d(TAG, "Sending to user " + id + ": "
+ intent.toShortString(false, true, false, false)
+ " " + intent.getExtras(), here);
}
am.broadcastIntent(null, intent, null, finishedReceiver,
, null, null, null, android.app.AppOpsManager.OP_NONE,
null, finishedReceiver != null, false, id);
}
} catch (RemoteException ex) {
}
}
});
}
看到在广播ACTION_MY_PACKAGE_REPLACED的时候,是通过Intent.setPackage(String packageName)实现定向发送。
(2)ACTION_PACKAGE_CHANGED在什么场景下使用?

ACTION_PACKAGE_CHANGED在PMS中只有一处使用入口:

private void sendPackageChangedBroadcast(String packageName,
boolean killFlag, ArrayList<String> componentNames, int packageUid) {
if (DEBUG_INSTALL)
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
Bundle extras = new Bundle();
extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get());
String nameList[] = new String[componentNames.size()];
componentNames.toArray(nameList);
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
// If this is not reporting a change of the overall package, then only send it
// to registered receivers. We don't want to launch a swath of apps for every
// little component state change.
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : ;
sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
new int[] {UserHandle.getUserId(packageUid)});
}
追踪sendPackageChangedBroadcast()的调用,来自setComponentEnabledSetting()。

这是实现PackageManager的对外公开API,看一下PackageManager中对此的定义:

/**
* Set the enabled setting for a package component (activity, receiver, service, provider).
* This setting will override any enabled state which may have been set by the component in its
* manifest.
*
* @param componentName The component to enable
* @param newState The new enabled state for the component. The legal values for this state
* are:
* {@link #COMPONENT_ENABLED_STATE_ENABLED},
* {@link #COMPONENT_ENABLED_STATE_DISABLED}
* and
* {@link #COMPONENT_ENABLED_STATE_DEFAULT}
* The last one removes the setting, thereby restoring the component's state to
* whatever was set in it's manifest (or enabled, by default).
* @param flags Optional behavior flags: {@link #DONT_KILL_APP} or 0.
*/
public abstract void setComponentEnabledSetting(ComponentName componentName,
int newState, int flags);

这是PackageManager提供的修改四大控件enable/disable的API,当然调用是需要检查权限的,此处不展开介绍。

(3)ACTION_PACKAGE_REMOVED和ACTION_PACKAGE_FULLY_REMOVED的逻辑关系

查看这两个Action在PMS中的使用,发现只有一处:

private void sendPackageRemovedBroadcastInternal(boolean killApp) {
Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, removedAppId >= ? removedAppId : uid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
if (isUpdate || isRemovedPackageSystemUpdate) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
if (removedPackage != null) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
extras, , null, null, removedUsers);
if (dataRemoved && !isRemovedPackageSystemUpdate) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED,
removedPackage, extras, , null, null, removedUsers);
}
}
if (removedAppId >= ) {
sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, , null, null,
removedUsers);
}
}

看到Package removed发生时,会发ACTION_PACKAGE_REMOVED;并且只有在数据被删除且非删除系统更新的app时,才会发送ACTION_PACKAGE_FULLY_REMOVED。在PackageManager中有一个flag常量定义PackageManager.DELETE_KEEP_DATA,用以决定在删除app时是否保留数据。

Android 侦听应用(Package)变化的方法侦听广播的更多相关文章

  1. jQuery 监听元素内容变化的方法

    我们可以用onchange事件来完成元素值发生改变触发的监听.但是 onchange 比较适用于<input>.<textarea> 以及 <select> 元素. ...

  2. 前端组件化Polymer入门教程(6)——监听属性值变化

    监听属性值变化 如果需要监听属性值变化可以通过给observer赋值一个回调函数. <say-Hello></say-Hello> <dom-module id=&quo ...

  3. Android简易实战教程--第十九话《手把手教您监听EditText文本变化,实现抖动和震动的效果》

    昨晚写博客太仓促,代码结构有问题,早上测试发现没法监听文本变化!今日更改一下.真心见谅啦,哈哈!主活动的代码已经改好了,看截图这次的确实现了文本监听变化情况. 监听文本输入情况,仅仅限于土司略显low ...

  4. Android监听手机网络变化

    Android监听手机网络变化 手机网络状态发生变化会发送广播,利用广播接收者,监听手机网络变化 效果图 注册广播接收者 <?xml version="1.0" encodi ...

  5. 计算属性(computed)+侦听器(watch)+ 方法(methods)

    计算属性 computed 当数据改变时,方法的结果也会发生改变.如果多处地方调用计算属性里面的同一个方法时,该方法只会执行一次.如图,在控制台改变data里面的num值时,虽然在多处使用comput ...

  6. onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法(设置readonly后onchange不起作用的解决方案)

    转自:https://www.cnblogs.com/white0710/p/7338456.html 1. onchange事件监听input值变化的使用方法: <input id=" ...

  7. 关于微信小程序使用watch监听数据变化的方法

    众所周知,Vue中,可以使用监听属性 watch来观察和响应 Vue 实例上的数据变化,那么小程序能不能实现这一点呢? 监听器的原理,是将data中需监听的数据写在watch对象中,并给其提供一个方法 ...

  8. Android 关于ListView中按钮监听的优化问题(方法二)

    关于ListView中按钮监听的优化问题(方法一)地址: http://www.cnblogs.com/steffen/p/3951901.html 之前的方法一,虽然能够解决position的传递, ...

  9. android -- 小问题 关于ListView设置了OnScrollListener之后onScrollStateChanged()和onScroll方法监听不到的问题

    关于ListView设置了OnScrollListener之后onScrollStateChanged()和onScroll方法监听不到的问题: 原因: 首先OnScrollListener是焦点滚动 ...

随机推荐

  1. 七牛直播云-m3u8格式直播

    直播架构 业务服务器:负责协调直播类应用的业务逻辑 创建直播房间 返回直播房间播放地址列表 关闭直播房间 LiveNet 实时流网络:负责流媒体的分发.直播流的创建.查询等相关操作 采集端:负责采集和 ...

  2. Navicat premium连接Oracle报ORA-12545错误

    1:ORA-12545 原因: 这里填localhost,127.0.0.1,或者远程ip.

  3. controller 接口写法

    import org.apache.commons.io.IOUtils; import javax.servlet.ServletInputStream; @RequestMapping(" ...

  4. 关于单CPU,多CPU上的原子操作

    所谓原子操作,就是"不可中断的一个或一系列操作" . 硬件级的原子操作:在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是" 原子操作& ...

  5. 使用MySQLMigrationToolkit快速将Oracle数据导入MySQL

    使用MySQL Migration Toolkit快速将Oracle数据导入MySQL 上来先说点废话 本人最近在学习一些数据库方面的知识,之前接触过Oracle和MySQL,最近又很流行MongoD ...

  6. ZooKeeper常用命令行工具及使用(转)

    一.服务端 bin目录下常用的脚本解释 zkCleanup:清理Zookeeper历史数据,包括食物日志文件和快照数据文件 zkCli:Zookeeper的一个简易客户端 zkEnv:设置Zookee ...

  7. 找出二叉查找树中指定结点的”下一个&quot;结点(也即中序后继)

    设计一个算法.找出二叉查找树中指定结点的"下一个"结点(也即中序后继).能够假定每一个结点都含有指向父结点的连接. watermark/2/text/aHR0cDovL2Jsb2c ...

  8. RxJava资料整理

    RxJava资料整理 https://www.jianshu.com/p/e3c4280ce397 http://www.daidingkang.cc/2017/05/19/Rxjava/ http: ...

  9. Ambari-部署常见问题

    重新启动ambari-server端后调用install.start API后返回200 导致该问题的解决办法是server在启动后没有收到agent的心跳即没有与agent建立连接,在此时进行API ...

  10. [CSS3] Make a horizontal-scrolling Menu

    Our web app on desktop may use elements that use great deal of width. On many occasions we can’t sim ...