Expo OTA更新怎么工作?EAS Update怎么用?
Expo的OTA(Over-the-Air)更新让开发者绕过应用商店审核流程,直接向用户推送JavaScript和资源文件的更新。这项能力来自EAS Update服务,配合expo-updates原生模块在客户端完成检查、下载和应用更新的全流程。
OTA更新的底层机制
一次OTA更新涉及三个核心概念:分支(Branch)、通道(Channel)和运行时版本(Runtime Version)。
分支是更新在服务端的组织方式。每次执行eas update,Expo会将打包后的JavaScript bundle和资源文件上传到指定分支,分支上的最新更新即为活跃更新。通道则是客户端与分支之间的桥梁——客户端通过app.json中配置的通道名称连接到对应分支,从而获取更新。运行时版本是兼容性的守门员:只有运行时版本匹配的更新才会被下载,防止含原生依赖变更的更新在不兼容的二进制上运行导致崩溃。
更新下载分两个阶段进行。应用启动时,expo-updates先请求最新的更新清单(manifest),清单包含更新元数据和所需资源列表。接着只下载当前缓存中缺失的资源文件,已缓存的部分直接复用。SDK 55引入的bundle diffing机制进一步将更新体积缩小60%到80%——客户端只需下载新旧bundle之间的差异部分,而非整个bundle。
EAS Update的完整配置流程
1. 安装依赖并初始化
bashnpx expo install expo-updates npm install -g eas-cli eas login eas init
eas init会在app.json中写入EAS项目ID,expo-updates则是客户端检查和下载更新所依赖的原生模块。
2. 配置app.json
json{ "expo": { "runtimeVersion": { "policy": "appVersion" }, "updates": { "url": "https://u.expo.dev/your-project-id", "enabled": true, "fallbackToCacheTimeout": 0, "checkAutomatically": "ON_LOAD" } } }
checkAutomatically控制更新检查时机,ON_LOAD表示应用启动时自动检查,WIFI_ONLY仅在WiFi下检查。fallbackToCacheTimeout设为0表示不等待更新下载完成,直接加载缓存版本。
3. 构建支持更新的二进制
OTA更新只在production或preview构建中生效,Expo Go不支持:
basheas build --platform all --profile production
构建时expo-updates被编译进二进制,并嵌入了构建时的初始更新作为回退版本。
4. 发布更新
bash# 向production通道发布 eas update --branch production --message "修复登录按钮样式" # 指定运行时版本 eas update --branch production --runtime-version 1.0.0 # 向preview通道发布用于测试 eas update --branch preview --message "测试新功能"
5. 查看和回滚更新
bash# 列出所有更新 eas update:list # 查看指定分支的更新 eas update:list --branch production # 回滚到上一版本 eas update:rollback --channel production # 回滚到特定版本 eas update:rollback --channel production --target-message "上一个稳定版本"
运行时版本策略选择
三种策略各有适用场景:
- appVersion(推荐):运行时版本跟随app.json中的version字段。每次改了原生依赖就升version,简单可靠,适合大多数团队。
- nativeVersion:跟随原生构建号,比appVersion更细粒度,适合频繁发版但原生变更不多的场景。
- 自定义版本字符串:完全手动控制,灵活性最高但需要团队约定版本命名规范。
关键原则:只要添加、删除或升级了原生依赖,就必须同步更新运行时版本,否则OTA更新可能导致原生模块找不到而崩溃。
客户端程序化控制更新
手动控制更新检查和应用的场景很常见,比如在设置页提供"检查更新"按钮,或在关键操作前确保代码是最新的:
typescriptimport * as Updates from 'expo-updates'; async function checkAndApplyUpdate() { if (__DEV__) return; // 开发模式不支持OTA try { const update = await Updates.checkForUpdateAsync(); if (update.isAvailable) { const result = await Updates.fetchUpdateAsync(); if (result.isNew) { // 立即重载应用新版本 await Updates.reloadAsync(); } } } catch (error) { // 更新失败不影响正常使用,静默处理或上报 console.warn('更新检查失败:', error.message); } }
监听更新事件可以在后台下载完成时通知用户:
typescriptuseEffect(() => { if (__DEV__) return; const subscription = Updates.addListener((event) => { if (event.type === Updates.UpdateEventType.DOWNLOAD_FINISHED) { // 提示用户下次启动将使用新版本 Alert.alert('更新已就绪', '重启应用以使用最新版本', [ { text: '稍后', style: 'cancel' }, { text: '立即重启', onPress: () => Updates.reloadAsync() }, ]); } if (event.type === Updates.UpdateEventType.ERROR) { console.warn('更新下载出错:', event.message); } }); return () => subscription.remove(); }, []);
OTA更新的边界与限制
OTA能更新的是JavaScript业务逻辑、React组件树、样式、静态资源和导航结构。以下变更必须发新构建:新增或删除原生依赖、修改app.json中的原生字段(权限、URL Scheme等)、升级Expo SDK大版本、更换应用图标或启动屏。
苹果和谷歌对OTA更新的政策有明确要求:更新不得改变应用的核心功能定位,不得绕过应用商店审核引入付费功能或隐私敏感变更。实际操作中,大多数UI修复和小功能调整都符合政策。
通道与分支的运维实践
通道和分支的典型组合:
| 环境 | 分支 | 通道 | 用途 |
|---|---|---|---|
| 生产 | production | production | 面向所有用户 |
| 预发 | staging | staging | 内部测试验证 |
| 预览 | preview | preview | 功能预览 |
通道还支持按比例灰度发布。通过eas channel:rollout命令可以逐步将新更新推送给一定比例的用户,观察错误率后再全量发布:
bash# 先向20%用户推送 eas channel:rollout production --percent 20 # 确认无问题后扩大到50% eas channel:rollout production --percent 50 # 全量发布 eas channel:rollout production --percent 100
错误恢复机制
expo-updates内置了自动错误恢复:如果更新后的应用在启动时连续崩溃,模块会自动回退到上一个已缓存的可用版本。这为线上事故提供了兜底,但仍建议在发布前通过preview通道充分测试。
手动回滚同样简单。EAS Update保留每个通道的完整更新历史,回滚只是将活跃指针指向上一个版本,客户端会在下次启动时下载并切换。
面试追问
OTA更新和发新版本各自的适用场景? JavaScript层面的bug修复、UI调整、文案改动适合OTA;新增原生模块、修改权限声明、升级SDK大版本必须发新构建。判断依据是变更是否涉及原生代码。
运行时版本不匹配会发生什么? 客户端会忽略不匹配的更新,继续运行当前缓存版本。这保护了应用不会因缺少原生模块而崩溃,但也意味着如果忘记同步运行时版本,用户将收不到更新。
如何保证OTA更新的安全性? EAS Update默认通过HTTPS传输更新包,expo-updates在加载前会校验更新签名。自托管更新服务器时需确保同样启用HTTPS和签名验证。