曾经遇到过一个问题,app里注册了MY_PACKAGE_REPLACED,用来识别app的更新行为,但是
静态注册的MY_PACKAGE_REPLACED,没有找到receiver
01-04 09:47:53.798 1000 2007 2379 V ActivityManager: Broadcast: Intent { act=android.intent.action.MY_PACKAGE_REPLACED flg=0x4000010 pkg=com.example.testapp (has extras) } ordered=false userid=0
01-04 09:47:53.800 1000 2007 2379 I ActivityManager: test====== collectReceiverComponents
01-04 09:47:53.800 1000 2007 2379 I PackageManager: test=====, comp == null
01-04 09:47:53.800 1000 2007 2379 I PackageManager: test=====, pms,pkgName=com.example.testapp
01-04 09:47:53.800 1000 2007 2379 I ActivityManager: test======,end, receivers is null
01-04 09:47:53.800 1000 2007 2379 V ActivityManager: Enqueueing broadcast: android.intent.action.MY_PACKAGE_REPLACED replacePending=false
可能是什么地方屏蔽了。
这里来学习一下MY_PACKAGE_REPLACED广播
MY_PACKAGE_REPLACED 是 Android 系统中一个非常有用但常被忽略的隐式广播(Implicit Broadcast) ,用于通知应用 "自身已被更新(替换)"。下面从原理、使用场景、限制和最佳实践全面讲解。
📡 一、什么是 MY_PACKAGE_REPLACED?
-
Action 名称:
java
编辑
Intent.ACTION_MY_PACKAGE_REPLACED // 值为 "android.intent.action.MY_PACKAGE_REPLACED" -
触发时机 :
当 当前应用被升级(覆盖安装) 时,系统会向该应用发送此广播。
-
关键特点:
- 只有 被更新的应用自己 能收到
- 不会被其他应用收到(安全设计)
- 在 应用的新版本代码启动前 发送
✅ 与
PACKAGE_REPLACED的区别:
PACKAGE_REPLACED:所有应用都能监听任意包的更新(需权限 + Android 8.0+ 受限)MY_PACKAGE_REPLACED:仅自己能监听自己的更新 ,无需额外权限,Android 8.0+ 仍可用
🛠 二、如何注册接收?
方式 1:动态注册(推荐)
java
编辑
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver updateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
Log.d("Update", "App has been updated!");
// 执行更新后逻辑
handleAppUpdate();
}
}
};
@Override
protected void onResume() {
super.onResume();
registerReceiver(updateReceiver, new IntentFilter(Intent.ACTION_MY_PACKAGE_REPLACED));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(updateReceiver);
}
}
方式 2:静态注册(AndroidManifest.xml)
xml
编辑
<receiver android:name=".MyUpdateReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
✅ 强烈建议使用静态注册 !
因为
MY_PACKAGE_REPLACED广播在应用首次启动前就可能发出,动态注册可能错过。
🎯 三、典型使用场景
场景 1:数据库版本迁移
- 新版本修改了数据库结构
- 在
onReceive()中检查旧版本号,执行升级脚本
场景 2:清除过期缓存或临时文件
java
编辑
public void onReceive(Context context, Intent intent) {
// 删除旧版缓存(路径可能已变更)
context.getCacheDir().deleteRecursively();
}
场景 3:重置 SharedPreferences 配置
- 如果新版本改变了配置项含义,可选择重置
java
编辑
context.getSharedPreferences("config", Context.MODE_PRIVATE)
.edit().clear().apply();
场景 4:重新注册推送 Token
- 某些推送 SDK 要求每次更新后重新绑定设备
场景 5:显示"更新日志"弹窗
- 首次启动新版本时,提示用户新功能
💡 注意:不要在此广播中做耗时操作(运行在主线程)!
⚠️ 四、重要限制与注意事项
1. 仅在"覆盖安装"时触发
- ✅ 触发:
adb install -r app.apk、Google Play 更新 - ❌ 不触发:
- 首次安装(无旧版本)
- 卸载后重新安装
- 通过
pm install -r但包名不同
2. Android 8.0+(API 26)隐式广播限制
- 大多数隐式广播(如
PACKAGE_REPLACED)被禁止静态注册 - 但
MY_PACKAGE_REPLACED是例外!
Google 明确将其列入 豁免列表
✅ 所以即使 targetSdkVersion >= 26,静态注册依然有效
3. 广播在新版本上下文中运行
- 收到广播时,运行的是新 APK 的代码
- 可安全访问新版本的资源、类、SharedPreferences
4. 不要启动 Activity(可能被系统拦截)
-
Android 10+ 限制后台启动 Activity
-
如需引导用户,建议发通知:
java
编辑
NotificationManager nm = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); nm.notify(1, buildUpdateNotification());
🔍 五、与其他更新广播对比
| 广播 Action | 谁能接收 | 是否需要权限 | Android 8.0+ 静态注册 |
|---|---|---|---|
MY_PACKAGE_REPLACED |
仅自己 | ❌ 不需要 | ✅ 允许 |
PACKAGE_REPLACED |
所有应用 | ❌ 不需要 | ❌ 禁止(除非动态注册) |
PACKAGE_ADDED |
所有应用 | ❌ 不需要 | ❌ 禁止 |
PACKAGE_REMOVED |
所有应用 | ❌ 不需要 | ❌ 禁止 |
✅ 结论:监听自身更新,请永远用
MY_PACKAGE_REPLACED
🧪 六、调试技巧
1. 模拟应用更新
bash
编辑
# 先安装 v1
adb install old_version.apk
# 再覆盖安装 v2
adb install -r new_version.apk
观察 logcat 是否收到广播。
2. 查看广播是否注册成功
bash
编辑
adb shell dumpsys package com.your.package | grep -A 5 MY_PACKAGE_REPLACED
3. 日志验证
java
编辑
Log.d("Broadcast", "Received MY_PACKAGE_REPLACED at " + System.currentTimeMillis());
✅ 七、最佳实践总结
| 建议 | 说明 |
|---|---|
| 使用静态注册 | 确保不丢失首次更新广播 |
| 避免耗时操作 | 广播在主线程,用 Handler 或 WorkManager 延迟处理 |
| 不要启动 Activity | 改用通知或下次启动时检测版本 |
| 结合版本号判断 | 在 SharedPreferences 中记录上次版本,避免重复执行 |
| 测试覆盖安装流程 | 确保升级逻辑可靠 |
示例:带版本检查的更新处理
java
编辑
public class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String currentVersion = BuildConfig.VERSION_NAME;
SharedPreferences prefs = context.getSharedPreferences("meta", Context.MODE_PRIVATE);
String lastVersion = prefs.getString("last_version", "");
if (!currentVersion.equals(lastVersion)) {
// 执行一次性更新逻辑
migrateData(context);
prefs.edit().putString("last_version", currentVersion).apply();
}
}
}
💡 总结
MY_PACKAGE_REPLACED是 监听自身应用更新的最安全、最可靠方式- 无需权限,Android 8.0+ 仍支持静态注册
- 适用于:数据迁移、缓存清理、配置重置、更新提示等场景
- 记住:它只在"覆盖安装"时触发,首次安装不会收到