做了几年React Native开发,最让我烦躁的事情之一就是打包。每次要给测试发一个Android包,都得先检查本地JDK版本对不对、Android SDK是不是又缺了平台、Gradle源能不能拉通,运气好半小时搞定,运气不好一上午就搭进去了。后来项目迁到了Expo,开始用Expo云平台打包(EAS)把构建这摊事整个甩到云端,才算是真正解脱了。这篇就把我从零折腾EAS的完整经历、配置思路和踩坑记录整理出来,给正在犹豫要不要迁移到云构建的朋友做个参考。
这套东西适合谁?只要你在用Expo开发React Native应用,不管项目是纯托管工作流还是后来加的自定义原生代码,EAS都能帮你把构建、签名、上架这一整条链路串起来。如果你还在本地手动打包,或者正在用通用CI工具自己维护一堆构建脚本,看看这篇文章里的方案选型思路,大概率能帮你省下不少精力。
1. 方案选型:为什么从本地打包转向云端构建
1.1 本地打包的痛点和隐性成本
先说本地打包这件事。表面上你只是执行一条gradle assembleRelease或者让Xcode跑一下Archive,但实际上你是在维护一套完整的环境。Android这边,你需要匹配的JDK版本,需要完整的Android SDK平台,还要处理Gradle下载依赖时的网络抽风问题。iOS那边更折腾,Xcode版本升级可能导致项目结构变化,CocoaPods安装慢得让人怀疑人生,证书和描述文件的配置更是劝退一大批新手。
更隐蔽的成本在于签名文件和版本管理。团队开发的时候,keystore文件得小心翼翼地保管,一不小心弄丢了,上架应用就没法更新了;版本号每次手工递增,几个人同时开发,经常出现两个人打了两个版本号一样的包,测试那边根本分不清哪个是新的。这些琐碎问题单个看都不大,但累加起来,每周浪费在“打包-等-发现问题-重新打包”上的时间相当可观。
1.2 EAS Build与自建CI的取舍
有人会说,那我不用EAS,我用GitHub Actions或者其他CI服务自己搭一套自动化构建不行吗?完全可以,但这取决于你想维护多少东西。自建CI意味着你得自己写构建脚本,处理各种Expo配置的传递,维护签名文件的加密存储,还要在CI环境里复刻Expo项目的构建条件。说实话,任何一个环节出问题,排查起来都比本地构建更费劲,因为CI环境本身就像一个黑盒。
EAS Build的价值在于它是Expo官方的托管构建服务,和Expo CLI、应用配置天然打通。它不需要你理解Xcode怎么签名、Gradle怎么配置,你在网页端或者命令行发一个构建请求,它就在云端把环境准备好、依赖装好、构建跑完,最后给你一个可以直接安装的产物。对于不想维护构建环境的人而言,这是最省心的一条路。
1.3 EAS服务生态的整体认识
需要先建立一个全局认识,EAS不是一个单一功能,而是一套服务组合,核心是三个部分:
- EAS Build:云构建服务,生成Android的APK/AAB、iOS的IPA。
- EAS Submit:应用商店上传服务,直接把构建产物推到应用商店后台。
- EAS Update:无线更新服务,让JavaScript和资源文件的更新不必重新走应用商店审核流程。
我目前用得最多的是前两个,尤其是Build。理解它们的协作关系很重要:Build负责“把代码变成安装包”,Submit负责“把安装包送到商店门口”,Update负责“应用上线后继续更新小改动”。这三者可以独立使用,也可以组合成完整流水线。
2. 核心概念与前置准备:EAS Build的模式、配置与账号体系
2.1 三种构建档案模式
第一次看EAS文档的时候,我被development、preview、production这三个概念绕了一下。后来才明白,它们的本质差异不在代码,而在“这个包给谁用、用在哪里”。
- development档案:给开发人员自己用的调试构建,必须配合
expo-dev-client使用,能连接本地开发服务器,方便调试原生模块。 - preview档案:给测试人员和产品经理体验用的预览构建,可以独立安装运行,但不能直接上架商店。
- production档案:给正式发布用的生产构建,会做代码压缩、资源优化,提交商店基本都用它。
这三个档案不是必须全部配置,但建议从一开始就分清楚。我见过有些团队只用一个production档案打了所有环境的包,测试装的是生产包,结果测试环境的接口地址对不上,问题排查了大半天才发现是构建档位的问题。
2.2 eas.json配置详解与服务端映射
EAS构建的很多行为,都体现在项目根目录的eas.json文件里面。这个文件是EAS的构建配置中心,我用最常见的配置做个说明:
{ "cli": { "version": ">= 3.0.0", "appVersionSource": "remote" }, "build": { "development": { "developmentClient": true, "distribution": "internal", "channel": "development", "env": { "APP_ENV": "development" } }, "preview": { "distribution": "internal", "channel": "preview", "env": { "APP_ENV": "preview" } }, "production": { "autoIncrement": true, "channel": "production", "env": { "APP_ENV": "production" } } }, "submit": { "production": { "android": { "serviceAccountKeyFile": "./keys/google-play-service-account.json" }, "ios": { "appleTeamId": "TEAMID", "ascAppId": "com.example.app" } } } }几个关键字段解释一下:
developmentClient: true:声明这是开发客户端构建,构建产物会包含Expo开发客户端运行时,能主动连接开发服务器。distribution: "internal":表示构建产物是内部使用,不需要进入应用商店审核流程。channel:这个字段我一开始没太理解,后来在配合EAS Update时明白了,它就是给应用更新打的一个标签,客户端通过channel知道自己该从哪个更新通道拉取更新。autoIncrement:自动递增构建版本号,省去手动改版本号的麻烦,后面我会细说。env:在云端构建时注入的环境变量,这样不同档位的构建天然使用不同环境的接口配置,代码里通过process.env.APP_ENV读取即可。
2.3 账号、项目关联与必要的命令行准备
用EAS之前,需要先做一些准备工作。第一步就是安装EAS CLI,通常用npm全局安装就行:
npm install -g eas-cli装完之后登录Expo账号,这个账号会和后面所有构建行为绑定:
eas login接下来需要在项目目录里把项目和EAS平台建立关联。第一次运行eas init会自动申请一个项目ID,并在app.json里写入对应的配置。这一步千万别跳过,我见过有朋友直接把eas.json配好了就去跑构建,结果提示项目没有关联EAS服务,构建根本发不出去。
如果项目之前没有app.json,EAS CLI会询问你是否创建Expo项目配置文件,选择按默认配置生成就行。整个准备过程大概几分钟,但能避免后面各种“这服务不认这个项目”的诡异报错。
3. 完整实操流程:从初始化到商店提交的一站式构建
3.1 初始化项目配置与版本号策略
假设你已经有一个Expo项目,并且完成了eas init的项目关联。真正开始用EAS之前,我建议先想清楚版本号策略。App的版本号分两套维度:
- 面向用户的版本名(versionName):比如 1.2.0,用户能在设置页面看到。
- 面向构建系统的版本号(versionCode / buildNumber):单调递增的整数,应用商店用它判断哪个包更新。
手动维护这两个数字很痛苦,所以我推荐依赖EAS的autoIncrement。配置了autoIncrement: true之后,每次构建EAS都会自动读取上一次构建的版本号并递增,你再也不用为了“这次testing的包为什么装不上,是不是版本号一样导致系统不给装”这类问题发愁了。第一次生产构建时,它会把默认版本号设为1,之后逐次递增。
3.2 发起一次开发构建及其参数语义
开发阶段最有用的构建模式是development。发起命令如下:
eas build --platform android --profile development执行之后EAS CLI会干什么?它先读取本地的eas.json配置,然后把项目代码连同依赖一起上传到云端,云端构建完成后把下载链接显示在终端里,同时生成一个二维码供真机扫码安装。
这里要注意,development构建默认包含expo-dev-client,它不是纯应用壳,而是带开发调试能力的壳。你安装到手机上之后,打开App时可以选择连接本地的开发服务器,这样你在电脑上改代码,手机应用能实时刷新,和用Expo Go的开发体验类似,但多了支持原生自定义模块的能力。
如果你需要在iOS模拟器上构建,命令可以加参数:
eas build --platform ios --profile development --local--local表示在本地模拟器环境构建,EAS会帮你在本地完成Expo的预构建步骤,但真正的Xcode构建还是在本地跑。这个选项适合需要快速在模拟器上调试的场景。
3.3 构建产物下载与安装
构建完成后,终端会显示一个链接和二维码。Android设备扫码后可以直接下载APK文件安装,注意手机需要允许“安装未知来源应用”才行。iOS平台则受限一些,内部测试包通常通过TestFlight分发,或者用苹果的设备管理工具直接安装IPA。
拿到构建链接后,第一次安装的人可能会遇到一个问题:安装的development包有时候打不开本地的开发服务器。这种情况通常不是包的问题,而是手机和电脑不在同一个局域网,或者防火墙拦了8081端口。确保开发服务器是expo start启动的,并且手机和电脑连的是同一Wi-Fi,再重新开关一次应用,基本就能识别到。
3.4 生产构建与提交商店的衔接
当功能验证完、准备发版时,就要构建production包。首次生产构建需要配置签名信息。Android这边EAS提供两种方式:
- 托管签名模式:EAS帮你生成并保管keystore,这是默认方式,对新手最友好。
- 手动签名模式:你提供自己的keystore文件,适用于已有上架应用、需要延续签名的场景。
iOS这边核心任务是证书和描述文件。同样可以选择EAS托管管理,或者手动上传。注意首次构建时终端会提示你登录Apple账号,这一步需要能够正常完成双重认证。日常经验是Apple相关登录偶尔会比较慢,多试几次就好。
生产构建命令:
eas build --platform all --profile production构建完成之后如果要提交商店,可以直接用EAS Submit,它会读取构建产物并提交到应用商店后台。Android需要准备好Google Play服务账号的JSON密钥文件(可以通过应用商店后台创建),iOS需要Apple Team ID和App Store Connect应用ID。配置已经在前面eas.json里展示过。
提交命令也很简洁:
eas submit --platform android --profile production它问你要不要从最近的构建设置中选择提交时,选自己构建出来的那个版本就行。整个提交过程会自动上传二进制文件、填写基础信息,提交完成后你可以去商店后台继续完善审核所需的截图和描述。
3.5 环境变量与敏感信息的管理技巧
实际项目中,我们通常有多个环境的后端地址,不可能每次构建都手动改配置文件。EAS给了两种主要方式来管理环境变量。
第一种是写在eas.json的env字段里,这种方式配置简单、直观,适合区分不同构建档位的非敏感配置。第二种是通过EAS网页后台配置Environment Variables,这种方式适合存储敏感信息,比如推送服务密钥、支付密钥等。在项目设置页面找到Environment Variables入口,添加键值对,然后可以指定它用于哪个环境(development、preview、production)。
这两种方式可以混合使用,我自己的习惯是:非敏感的不同环境配置放eas.json的env,敏感的统一走EAS后台。需要特别注意的一点是,不要把API密钥、服务器密码这类信息硬编码在代码仓库里,尤其当仓库是多人协作或者开源的时候,这一点格外重要。
4. 常见问题与排查技巧实录
4.1 变更签名方案后的排查思路
我们曾经遇到过Android构建当时用了EAS托管签名,但团队希望切换到自己维护的keystore,结果发现不明所以的签名冲突。这种情况通常是项目已经在EAS侧关联了一个keystore,EAS在后续构建时自动沿用了旧签名,当你手动引入新keystore时两者不一致,导致构建异常或应用无法覆盖安装。
解决办法比较简单,在项目根目录执行:
eas credentials --platform android然后选择清楚已存在的Credentials记录,重新配置为你自己的keystore信息。注意这个操作要谨慎,如果应用已经上架,随便替换签名会导致商店版本更新时签名校验失败,那是比较严重的线上事故。务必确认你手头的新keystore和商店当前使用的签名一致,再执行清理操作。
4.2 云端构建超时与构建队列卡住的处理
EAS免费层有构建时间和并发数限制,构建任务排队时间过长或者一直停在某个阶段,是使用过程中比较常见的问题。首先要分清是“任务没被调度”还是“构建进行中”。
我自己的排查顺序:
- 登录EAS网页后台,查看当前构建队列的任务状态。
- 如果任务一直显示queued,大概率是免费层并发已达到上限,或者在云端资源高峰期排队。等一会儿通常能恢复。
- 如果任务显示in progress但很久没变化,可以打开构建日志,看卡在哪一步。常见卡点包括依赖下载慢、CocoaPods安装慢、或者某个环境配置错误导致脚本挂起。
- 确认代码没问题之后,实在不行就取消这次构建,重新发起一次,很多时候第二次能成功。
4.3 构建缓存问题带来的诡异报错
EAS Build为了提速,默认会启用缓存机制。这本来是好事,但它偶尔也会带来一些“幽灵问题”,比如你明明更新了某个原生依赖,构建日志里却显示用的还是旧版本,或者某些编译产物残留导致代码变更没有生效。
遇到这种情况,优先尝试清理缓存重新构建。EAS提供了缓存清理参数:
eas build --platform android --profile development --clear-cache--clear-cache会让云端清理依赖缓存和编译缓存,强制重新拉取依赖。我遇到过本地一切正常、一上云端构建就报resource找不到错误的情况,最终是加了这个参数重新构建解决的。日常规律是:当构建环境相关的问题无法用常规手段排查时,先清缓存重试一遍,大概率会省掉很多无谓的日志翻找。
4.4 常见问题速查表
整理一张平时用得比较多的排查速查表:
| 现象 | 可能原因 | 推荐处理方案 |
|---|---|---|
| 提示项目未关联EAS | 未执行项目关联命令 | 执行eas init重新关联 |
| Android构建报签名失败 | 签名信息缺失或与云端不一致 | 运行eas credentials --platform android检查签名配置 |
| iOS提交商店总是卡在登录 | Apple双重认证或登录态失效 | 换时间段重试,确保使用有上架权限的Apple账号 |
| 构建成功但安装后无法启动 | 原生依赖与Expo版本不兼容 | 检查expo-doctor输出,对齐依赖版本 |
| 测试包版本号一直不更新 | 未开启版本号自动递增 | 在eas.json对应profile设置"autoIncrement": true |
| 环境变量在构建中读不到 | 变量作用域或profile配置错误 | 检查env字段或EAS后台环境变量绑定范围 |
4.5 与Expo开发服务器相关的联调细节
用EAS做完development构建,不太容易绕开的一个环节是和本地开发服务器联调。这个阶段踩坑最多的是网络配置。手机上安装了development构建包之后,打开它会自动搜索相同局域网内的开发服务器,但有些办公网络会开启AP隔离,导致手机和电脑虽然连的是同一个Wi-Fi,却互相访问不到。
遇到这种情况,可以检查一下开发服务器的终端输出,确认它监听的IP是否对局域网开放。如果不行,考虑用USB连接手机并开启USB调试,通过adb反向端口映射来解决,但这属于备用方案。日常开发如果不涉及原生模块调试,直接用Expo Go也很舒服,没必要每次都用development构建,省去联调环节的无效折腾。
5. 扩展玩法:EAS Update实现免审核更新
前面主要围绕Build和Submit,最后聊一个我很推荐的功能,EAS Update。它解决的核心痛点是:应用上线后需要频繁发布小改动,但走应用商店的审核流程太慢了。
EAS Update和EAS Build的channel字段天然配合。生产环境的应用在构建时指定channel: "production",那么它运行后就会监听production通道的更新。当你修复了一个Bug或者改了样式,在本地执行:
eas update --channel productionEAS会把当前项目的JavaScript bundle和资源上传到云端更新服务,已经安装的应用下次启动时就会静默拉取新版本。因为被替换的是JS和资源,不涉及原生代码,所以不需要重新提交到应用商店。这种检测和更新策略非常适合活动页面调整、文案修改这类轻量场景。
不过需要留意的是,EAS Update不是万能的。如果你改了原生模块、升级了原生依赖版本,就必须走完整的构建流程重新发版,更新补丁覆盖不了原生层面的变化。所以我的用法是:能用Update解决的用Update,涉及原生的就老老实实走EAS Build再Submit,各司其职,不必强行用一个替代另一个。
回过头来看,把整个打包流程迁移到EAS之后,最大的收益不是“省了本地空间”这种小事,而是构建这件事的确定性变高了。在本地打包时,环境差异导致的错误五花八门,而且往往只有你会碰到;切到云端之后,环境由EAS统一管理,人人都用同一套规则,出问题时的排查路径也清晰了很多。我个人在实际操作中最深的体会是:不要一上来就追求把所有模式都配全,先用development模式把一个平台跑通,理解eas.json里每个字段的语义,再逐步把preview和production加进来,稳妥得多。最后再分享一个小技巧,每次构建后养成拖回构建日志的习惯,哪怕构建成功了也大致扫一眼,时间长了你就会对这些环节的耗时和隐患形成直觉,排查问题会快很多。