Expo 项目热更新:Pushy 与 EAS Update 对比及接入
Expo 项目要做热更新(OTA),常见的选择有两个:Expo 官方的 EAS Update(客户端为 expo-updates),以及 Pushy(客户端为 react-native-update)。两者都能在不重新提交应用商店审核的前提下更新 JS 代码和图片等资源,但在增量方式、分发网络、计费方式和平台覆盖上各有侧重。
本页先说明 Pushy 在 Expo 项目中是如何工作的,再给出两者的客观对比和选型建议,最后列出接入步骤。具体的原生配置、代码集成与发布命令请以安装配置、代码集成和发布热更新为准,本页只做串联和 Expo 相关的补充。
如果你使用支持 Skills 的 AI 编程工具,可以先安装 react-native-update Skill。它覆盖 Expo prebuild 工程的接入流程,并会检查 expo-updates 冲突、原生目录是否已经生成等常见问题。
Pushy 在 Expo 项目中如何工作
支持的 Expo SDK 版本
- 要求 Expo SDK 50 或更高版本。
react-native-update在 iOS(podspec)和 Android(build.gradle)构建时都会检测项目中安装的expo版本,只有 SDK 50 及以上才会启用其中的 Expo 模块;低于 50 时按普通 React Native 项目处理,需要像非 Expo 项目一样手动配置 Bundle URL。 - 使用新架构时建议 Expo SDK 51 及以上,Expo 51 以下的新架构支持并不完整。总体而言,建议使用尽可能新的 Expo 版本。
react-native-update仓库中的 Expo 示例工程(Example/expoUsePushy)目前基于 Expo SDK 57,并在 CI 中对 iOS 和 Android 两端做构建验证。
需要 prebuild,不能在 Expo Go 中使用
react-native-update 包含原生代码,所以 Expo 项目需要通过 npx expo prebuild 生成 ios、android 原生目录(即 Expo 所说的 Continuous Native Generation,CNG),然后编译出自己的 release 包。这也意味着:
- Expo Go 无法使用 Pushy,因为 Expo Go 不能加载项目自带的原生模块;
- 热更新只在 release 包中生效,DEBUG 模式下依然走 Metro 开发服务器;
- 原生包可以本地编译,也可以用 EAS Build 编译——Pushy 不依赖某种特定的构建方式,只要求把最终分发给用户的那个安装包上传为基准版本(见下文)。
不需要 config plugin:以 Expo Module 形式自动接入
react-native-update 没有提供 config plugin(没有 app.plugin.js),也不需要在 app.json 的 plugins 中声明。它在包内带有 expo-module.config.json,以 Expo Module 的形式被 Expo autolinking 自动链接:
- iOS:注册了一个
ExpoReactDelegateHandler,在启动时把 JS bundle 的地址交给RCTPushy.bundleURL()决定; - Android:注册了一个
ReactNativeHostHandler,通过getJSBundleFile返回UpdateContext.getBundleUrl(...)。
因此,对于 Expo SDK 48 及以上且 react-native-update >= 10.28.2 的项目,安装配置中「配置 Bundle URL」这一步可以跳过,AppDelegate / MainApplication 无需手改。
安装配置页中的其余原生步骤(例如关闭 Android 的 crunchPngs、react-native-screens 相关的 MainActivity 修改、测试用的 Deep Link)对 Expo 项目同样适用。注意在 CNG 工作流中,ios、android 目录会在 npx expo prebuild --clean 时重新生成,手动修改会丢失。请根据团队习惯选择:把原生目录提交到仓库长期维护,或用你自己的 config plugin 把这些修改固化下来。
与 expo-updates 的关系:二选一,不要同时安装
expo-updates 是 EAS Update 的客户端,它同样会在应用启动时接管 JS bundle 的加载。Pushy 与它的原理相同、作用位置相同,两者同时存在会争夺 bundle 加载权,导致热更新功能冲突。所以:
- 使用 Pushy 时,请不要安装
expo-updates; - 如果项目原本用的是 EAS Update,迁移时需要先移除
expo-updates,再重新 prebuild 和编译原生包(具体步骤见下文「接入步骤」)。
注意 EAS Build、EAS Submit 等其他 Expo 服务与 Pushy 并不冲突,可以继续使用;需要二选一的只是负责 OTA 的 expo-updates。
Pushy 与 EAS Update 对比
下表只比较热更新相关的能力。EAS Update 一侧的信息来自 Expo 官方文档(How EAS Update works、Bundle diffing、Rollouts、Error recovery、Standalone service、Pricing),产品会持续迭代,请以官方最新说明为准。
关于鸿蒙需要说明一点:Expo 本身不面向鸿蒙构建。如果你的 React Native 应用同时有鸿蒙版本(基于 React Native for OpenHarmony),Pushy 可以用同一套服务和命令行管理 iOS、Android 与鸿蒙三端的热更新;鸿蒙端的接入方式与 Expo 无关,按安装配置中的 Harmony 部分进行即可。
关于增量包大小,Pushy 在真实 React Native 0.86 应用上的实测数据(一行文案改动约 3.4 KB)见产品简介中的「实测数据」一节,评测代码与数据公开可复现。
如何选择
两者都是成熟可用的方案,可以按下面几个维度判断:
更适合 Pushy 的情况
- 主要用户在中国大陆,希望热更新走国内 CDN,提高下载速度和成功率;
- 应用体积大、迭代频繁,希望从安装后的第一次热更新起就下发尽可能小的差量包;
- 同时有鸿蒙版本,希望三端用同一套热更新服务;
- 希望流量不单独计费、成本可预期,或需要中文文档和付费专人技术支持;
- 需要「强制启动(救砖)」、版本健康度、按比例灰度等发布控制能力(参见数据分析)。
更适合 EAS Update 的情况
- 用户主要在海外,团队已深度使用 EAS Build、EAS Submit 等 Expo 服务,希望在同一个控制台管理构建与更新;
- 依赖 channel / branch / runtimeVersion 这套 Expo 原生的发布模型,或需要在 Expo 的 development build 中预览更新;
- 只发布 iOS 和 Android,没有鸿蒙需求。
如果你的情况介于两者之间,可以先用 Pushy 的免费版在一个测试应用上实测下载速度和包体积,再做决定。无论选择哪一个,同一个应用里都只保留一套 OTA 客户端。
接入步骤
以下是 Expo 项目接入 Pushy 的主线,每一步的细节请点开对应文档。
确认 Expo 版本并移除 expo-updates
确认 expo 为 SDK 50 或更高版本(使用新架构建议 51+)。如果项目已经安装了 expo-updates,先移除:
app.json 中仅供 expo-updates 使用的配置(如 updates、runtimeVersion)可以一并删掉。若此前用 EAS Build 发布过带 channel 的构建,eas.json 中的 channel 设置也不再需要。
安装 react-native-update 与命令行工具
预构建原生工程
Bundle URL 会由 Expo 模块自动接管,无需手改。然后按安装配置补齐 Android 关闭 crunchPngs、MainActivity(如使用 react-native-screens)和测试用 Deep Link 等步骤,并在其中的「登录与创建应用」一节创建应用、生成 update.json。
集成代码
按代码集成,从 update.json 读取 appKey,创建 Pushy 客户端并用 UpdateProvider 包裹根组件。使用 Expo Router 时,可以在根布局(如 app/_layout.tsx)中包裹。
准备 index.js 入口
pushy bundle 检测到 Expo 项目时会自动调用 expo export:embed 打包,默认入口文件为 index.js。使用 Expo Router 等没有 index.js 的项目,需要新建一个 index.js 引用框架入口,例如:
详见发布热更新中的说明。
编译 release 包并上传基准版本
用本地编译或 EAS Build 生成 release 包,然后用 pushy uploadIpa、pushy uploadApk 或 pushy uploadAab 上传。上传的必须是最终分发给用户的同一个文件,否则服务端无法确认设备上的原生包,差量补丁也无法生成。使用 EAS Build 时,请下载构建产物后再上传,并用同一个产物提交商店。
发布热更新
修改 JS 代码或图片后,运行 pushy bundle --platform <ios|android> 生成并上传热更新包,再绑定到对应的原生包版本即可。可以先用扫码测试验证,再通过灰度发布逐步放量。命令参数见命令行工具。
常见问题
已经用 EAS Build 打包,还能用 Pushy 吗?
可以。EAS Build 只负责编译原生包,Pushy 只要求你把最终分发的那个安装包上传为基准版本。需要注意的只是不要在项目中保留 expo-updates。
新增或升级了带原生代码的 Expo 包,能热更新吗?
不能。任何需要重新编译才能生效的修改(新增原生模块、修改 app.json 中影响原生工程的配置、升级 Expo SDK 等)都需要重新 prebuild、编译并上传新的原生基准版本。可热更新与不可热更新的范围见常见问题。
接入后热更新没有生效怎么办?
先确认测试的是 release 包而非 DEBUG 包,再检查是否残留了 expo-updates、原生包是否已上传为基准版本。其他报错可查阅错误码说明与常见问题,也可以到 GitHub Issues 提问。