从 CodePush 迁移到 Pushy
本文面向正在使用 react-native-code-push(Microsoft CodePush)的 React Native 项目,说明如何把热更新迁移到 Pushy(npm 包名 react-native-update):先对照两边的概念、API 和命令,再按步骤替换依赖与原生配置、发布第一个热更新,最后说明迁移期间需要注意的事项。
如果你使用支持 Skills 的 AI 编程工具,可以先安装 react-native-update Skill,再让 AI 按本文的清单移除 CodePush 并完成 Pushy 接入,然后对照本文逐项校对改动。
背景:CodePush 已随 App Center 退役
根据微软官方的 App Center 退役公告,Visual Studio App Center 已于 2025 年 3 月 31 日退役,此后无法再登录账号或调用其 API。CodePush 托管服务随之停止;仅 Analytics 与 Diagnostics 两项功能获得了延期支持,与热更新无关。
对于 CodePush,微软给出的后续方案是一个可独立于 App Center 运行的版本 microsoft/code-push-server,需要自行部署和运维。该仓库与客户端仓库 microsoft/react-native-code-push 均已于 2025 年 5 月归档为只读,code-push-server 的说明中写明微软不为其提供支持服务。
因此,原本使用托管版 CodePush 的团队通常有两个选择:自建并长期维护 code-push-server,或者迁移到仍在持续维护的托管热更新服务。如果你选择后者,Pushy 提供以下能力,可以覆盖 CodePush 的常用场景:
- 下发增量补丁(bsdiff/hdiff,并针对 Hermes 字节码做了专项优化),而不是完整 bundle,详见产品简介;
- 新版本启动崩溃自动回滚,后台支持按比例灰度发布和版本健康度统计;
- 支持 iOS、Android 和鸿蒙(HarmonyOS),兼容 Expo、Hermes 与新架构;
- 客户端 SDK 与命令行工具开源,有免费版,配额见价格页。
概念对照
发布模型
CodePush 和 Pushy 最大的区别在于「热更新与原生包如何关联」:
- CodePush:每次
release-react会把 bundle 发布到某个 deployment(如Staging、Production),并按targetBinaryVersion匹配客户端的原生版本号。 - Pushy:先把编译好的原生包(ipa/apk/aab/app)上传到服务器作为原生基准版本,再把热更新版本绑定到一个或多个原生基准版本上。服务器以原生包内置的 bundle 为基准计算增量补丁,客户端只下载差异部分。
所以迁移后多了一个「上传原生包」的步骤,而且上架分发的安装包必须和上传到 Pushy 的那一份完全一致(服务器会比对编译时间戳),否则无法获取热更新,原因见发布热更新中「为什么重新打包后要先上传」一节。
命令行
App Center CLI(appcenter codepush ...)已随服务停用,下表 CodePush 一列以微软 code-push-server 自带的 CLI(命令名 code-push-standalone)为准。Pushy 的命令来自 react-native-update-cli,完整参数见命令行工具。
客户端 API
迁移步骤
下面的步骤以「已有一个接入了 CodePush 的 React Native 工程」为起点。迁移需要发布新的原生包,建议在一个新的原生版本号上完成。
移除 react-native-code-push
在项目根目录卸载依赖:
然后清理原生侧的 CodePush 配置。CodePush 的接入方式随 RN 版本和项目历史有所不同,请以你项目里实际存在的内容为准,逐项检查:
iOS
AppDelegate(或你的bundleURL所在文件)中的#import <CodePush/CodePush.h>,以及return [CodePush bundleURL];(Swift 项目中为对应的CodePush.bundleURL()调用)。下一步会把它替换为 Pushy 的写法。Info.plist中的CodePushDeploymentKey,以及可能存在的CodePushServerURL(自建 code-push-server 时使用)、CodePushPublicKey(代码签名时使用)。- 如果是 RN 0.60 以下手动在
Podfile中添加过pod 'CodePush', ...,一并删除。修改后在ios目录重新执行pod install。
Android
android/app/build.gradle中的apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"。android/settings.gradle中手动添加过的include ':react-native-code-push'和对应的projectDir配置(RN 0.60 以上的自动链接项目一般没有)。MainApplication中的import com.microsoft.codepush.react.CodePush和返回CodePush.getJSBundleFile()的getJSBundleFile覆盖。res/values/strings.xml中的CodePushDeploymentKey,以及可能存在的CodePushServerUrl、CodePushPublicKey。
JS
- 删除所有
import codePush from "react-native-code-push",以及根组件外的codePush(...)(App)包裹和codePush.sync()调用。下文会用 Pushy 的写法替换。
不要让 CodePush 和 Pushy 同时生效。两者都通过接管 bundleURL / getJSBundleFile 来加载热更新 bundle,同时保留会导致加载哪个 bundle 不可预期。
安装 react-native-update 并完成原生配置
按安装配置安装命令行工具和 react-native-update,并完成各平台的原生配置。对于 CodePush 迁移来说,关键是把上一步删掉的 bundle 入口换成 Pushy 的:
iOS(Objective-C 示例;Swift、RN 0.88 起的 SceneDelegate 写法见配置 Bundle URL):
Android(Kotlin,RN 0.81 或以下的 DefaultReactNativeHost 写法;RN 0.82 起改为给 getDefaultReactHost 传入 jsBundleFilePath = UpdateContext.getBundleUrl(this),见配置 Bundle URL):
同时请按安装配置页完成这几项 CodePush 没有要求、但 Pushy 需要的设置:
- Android 在
android/app/build.gradle的 release 配置中关闭crunchPngs,否则会影响增量补丁生成; - 使用
react-native-screens时,按文档覆盖 AndroidMainActivity的onCreate,避免热更后重启白屏; - 建议添加测试用的 Deep Link,用来替代 CodePush 的 Staging deployment 做发布前测试(见下文);
- 鸿蒙项目按文档完成 Harmony 部分的配置。
Expo 项目要求 Expo 50 或以上并通过 npx expo prebuild 预构建,且不要同时安装 expo-updates。
登录并创建应用
在 Pushy 管理后台注册账号后,在项目根目录登录并按平台分别创建应用:
完成后项目根目录会生成 update.json,其中包含各平台的 appId 和 appKey,可以提交到 Git;登录生成的 .update 文件则不要提交。详见安装配置中的「登录与创建应用」。
替换 JS 代码
以一个典型的 CodePush 接入为例:
对应的 Pushy 写法:
如果原来是用 codePush.sync() 手动控制流程、自己画更新界面,可以设置 updateStrategy: null,在 UpdateProvider 的子组件中通过 useUpdate() 取得 checkUpdate、downloadUpdate、switchVersion、switchVersionLater、updateInfo、progress 等自行组合,完整示例见代码集成中的「自定义更新界面」。注意 useUpdate() 只能在 UpdateProvider 的子组件里调用。
Pushy 默认在开发环境(__DEV__)中不检查更新;即便打开 debug: true,也只能检查和下载,实际应用热更新必须在 release 包中进行。
打包并上传原生基准包
这是 CodePush 没有、但 Pushy 必需的一步。先提高原生版本号(iOS 的 CFBundleShortVersionString、Android 的 versionName),按正常流程打 release 包,然后上传:
上传后再把同一份安装包提交到应用商店或分发渠道。以后只要重新打原生包,都要先改版本号、再上传,最后分发。同时产出 apk 和 aab、iOS 不要重新 archive 等细节,见发布热更新。
发布第一个热更新
修改一处 JS 代码后执行:
按提示发布并绑定原生包即可;也可以一条命令完成打包、发布和绑定(需 cli v1.46.1+):
其中 --packageVersion 换成你刚上传的原生版本号,作用类似 CodePush 的 --targetBinaryVersion;需要一次绑定多个原生版本时可改用 --packageVersionRange。绑定后服务器会在几秒内生成增量补丁。
测试与灰度验证
CodePush 常用 Staging deployment 做发布前测试。在 Pushy 中,推荐用管理后台的测试二维码:配置好测试用 Deep Link 后,用系统相机扫描某个热更版本的二维码,即可在与线上用户完全一致的 release 包上安装该版本,无需先绑定、也不影响线上用户,详见测试与回滚。如果应用自带扫码功能,也可以用 parseTestQrCode 实现。
测试通过后,先以较小比例灰度发布,观察版本健康度和数据分析,再逐步扩大到 100%:
灰度的工作原理和注意事项见灰度发布。同一原生包同时保留一个灰度版本和一个全量版本需要客户端 react-native-update v10.32.0+、cli v2.4.0+。
注意事项
已发出的老版本仍连着 CodePush
热更新 SDK 是编译进原生包里的,已经安装在用户手机上的老版本仍然使用 CodePush 客户端,无法通过热更新切换到 Pushy。只有安装了集成 Pushy 的新原生包的用户,才会从 Pushy 获取热更新。
因此迁移的实际节奏是:
- 发布集成 Pushy 的新原生包(上一节的步骤),并通过应用商店让用户升级;
- 在新原生包覆盖率上升之前,老版本用户无法再从托管版 CodePush 获得更新。如果你在过渡期使用了自建 code-push-server,可以继续用它维护老版本,直到老版本用户占比足够低;
- 如果需要引导老版本用户尽快升级,可以通过你自己的业务接口或应用内提示实现。对于已接入 Pushy 的原生包,以后若再发布不兼容的新原生版本,可以在管理后台把旧原生包设为过期,客户端会收到
expired结果并引导用户下载(需在后台填写下载地址),见 API 参考中checkUpdate的说明。
回滚
- 客户端自动回滚:CodePush 要求新版本调用
notifyAppReady(),否则下次启动会回滚。Pushy 默认在新版本启动后自动标记成功(autoMarkSuccess默认为true),新版本启动崩溃会自动回滚到上一个可用版本,一般不需要手动调用markSuccess()。设备上已回滚过的版本不会被再次安装。 - 服务端回滚:对应 CodePush 的
rollback命令,Pushy 中可先在管理后台对原生包或整个应用暂停热更,再把原生包重新绑定到之前正常的热更版本(后台拖拽,或pushy update --versionId <旧版本 id>),也可以回滚代码后重新发布一个修复版本,见测试与回滚。 - 救砖:如果某个热更版本让应用启动即崩溃、JS 已无法运行,可以对修复版本使用「强制启动(救砖)」,由原生冷启动检测把修复版本拉下来并在下次启动生效。需要原生包中的
react-native-updatev10.52.1+,详见发布热更新中的「强制启动(救砖)」一节。CodePush 没有对应功能。
强制更新
CodePush 发布时可加 --mandatory,客户端据此跳过用户确认。Pushy 没有内置的强制标记,常见做法有两种:
- 全局采用
updateStrategy: "silentAndNow",下载后立即应用; - 在发布时填写元信息(
--metaInfo '{"mandatory": true}',字段名由你自定),在updateStrategy: null的自定义流程里读取updateInfo.metaInfo决定是否跳过确认、立即调用switchVersion()。元信息的用法见场景实践中的「元信息(Meta Info)的使用」。
注意「强制启动(救砖)」是救急用的通道,会越过客户端的更新策略,不建议当作日常的强制更新使用。
其他差异
- 原生包必须先上传:重新打包而没有上传,会因编译时间戳不一致无法获取热更新,见常见问题。
- 哪些修改能热更新:与 CodePush 相同,只有 JS 代码和 JS 中引用的图片等资源可以热更新,任何原生代码或原生依赖的变更都需要发新原生包。
- 海外合规:如果项目对海外数据存放或运营主体有额外要求,可以考虑由独立公司实体运营的全球服务 Cresc。