【Android】关于MY_PACKAGE_REPLACED广播

曾经遇到过一个问题,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());

✅ 七、最佳实践总结

建议 说明
使用静态注册 确保不丢失首次更新广播
避免耗时操作 广播在主线程,用 HandlerWorkManager 延迟处理
不要启动 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+ 仍支持静态注册
  • 适用于:数据迁移、缓存清理、配置重置、更新提示等场景
  • 记住:它只在"覆盖安装"时触发,首次安装不会收到
相关推荐
4Forsee3 小时前
【Android】动态操作 Window 的背后机制
android·java·前端
华锋20223 小时前
2025.12首次体验 arkui-x 跨平台开发库
android
allk554 小时前
Android 性能优化之启动加速:从底层原理到架构治理
android
QuantumLeap丶5 小时前
《Flutter全栈开发实战指南:从零到高级》- 24 -集成推送通知
android·flutter·ios
用户41659673693555 小时前
WebView 滚动失灵?剖析 `scrollBy()` 在现代 Web 布局中的失效陷阱
android
明川5 小时前
Android Gradle学习 - Gradle插件开发与发布指南
android·前端·gradle
二流小码农6 小时前
鸿蒙开发:上架困难?谈谈我的上架之路
android·ios·harmonyos
Propeller6 小时前
【Android】动态操作 Window 的背后机制
android·java