Apple App Store 在 2024 年全球下载量达到 283 亿次,数据来自 Sensor Tower,由 TechCrunch报道。分发说起来简单,做起来难:每家商店都有自己的签名、审核队列、发布控制和回滚限制。
对 Web 团队来说,把 iOS 和 Android 视为 两条发布流水线共享同一代码库. Capacitor 可以将 Web 应用带入原生项目,Ionic 可以提供移动 UI,但打包进 bundle 的文件仍需通过应用商店发布。远程内容和经过批准的空中更新(OTA)路径才是无需重新提交即可上线的变更。
目录
- 为什么现在的团队会同时发布 iOS 和 Android 版本
- 选择构建路径
- 如何构建并打包 Capacitor 应用
- Apple 和 Google 审核的真实关注点
- 确保双平台兼容的任务设计规则
- 发布前测试无障碍与设备碎片化
- 无需等待商店审核即可发布更新
- 常见的跨平台问题及其解决方案
- 选好构建路径,发布首个版本
为什么现在的团队会同时发布 iOS 和 Android 版本
消费者在 2024 年通过 iOS 和 Android 应用内购支出 1500 亿美元,较 2023 年增长 12.5%,据 Sensor Tower 数据,由 MediaPost报道。支持支付、订阅或客户账户的应用,需要的不仅仅是跨平台一致的截图。其创收工作流必须能适应不同操作系统的规则。
假设你的团队已经有一个 React 应用,可能是用 AI 编码工具或可导出源码的 no-code 构建器开发的。现在客户希望有主屏幕图标、相机权限、通知和应用商店上架。
使用 Capacitor 外壳作为起点很合理。你可以保留 Web 界面和业务逻辑,同时为每个应用商店添加原生项目。
实战规则: 共享同一个 code 产品,但要让每个平台拥有独立的发布清单、凭据、测试结果和审批门禁。
这种矛盾往往出现在屏幕之外。在 Android 上可用的权限请求,在 iOS 上可能需要不同的配置;而开发期间能成功的后台上传,可能会在操作系统挂起应用时中断。
不要把 iOS/Android 应用当成导出两次的网站,而应把它看作拥有不同交付路径的产品。围绕最棘手的原生需求选择实现方案,打包可复现的 build,验证商店上架准备情况,并把符合条件的 Web 更新与需要新二进制的变更区分开。
这样团队就有了具体的交付物: 构建路径决策、提交清单和更新策略。你可以修复符合条件的 Web 层缺陷,而无需不必要地重新构建原生壳,同时让平台变更走正规的应用商店流程。
选择构建路径
| 方案 | 语言 | Code 共享 | 设备 API 访问 | 最合适 |
|---|---|---|---|---|
| 原生(Swift 和 Kotlin) | Swift、Kotlin | 各平台 UI 实现之间无差异 | 拥有完整的原生访问权限 | 图形密集型应用,或设有独立 iOS 和 Android 工程师的团队 |
| 跨平台渲染 (React Native, Flutter) | JavaScript 或 TypeScript、Dart | 大部分 UI 和逻辑 | 原生模块、平台通道和插件 | 希望使用单一 UI 代码库的新应用 |
| 混合 Web 外壳 (Capacitor, Ionic) | HTML、CSS、JavaScript | 整个 Web 应用 | 通过 Capacitor 插件 | 已有 Web 应用的团队 |
| 渐进式 Web 应用 (PWA) | HTML、CSS、JavaScript | 整个 Web 应用 | 仅使用浏览器 API,并受平台限制 | 无需上架应用商店的内容类网站 |
从可复用的 code 开始
一个可用的 Web 应用本身就是一笔重要资产。借助 Capacitor,你可以保留路由、校验、API 客户端以及大部分界面组件,而无需在平台专属的 UI 框架中重新实现。
这并不意味着原封不动地套用桌面端布局。你需要替换只支持 hover 的交互、检查触摸目标大小、处理软键盘,并让用户从其他应用返回时导航行为保持合理。
Ionic 是可选的。它提供了适配移动端界面的 Web Components 和导航模式;Capacitor 则提供原生运行时和插件边界。你可以在现有的 React、Vue 或 Angular 界面中使用 Capacitor,无需采用 Ionic 的组件库。
为最难的平台特性做原型
围绕最可能推翻你选型的功能做一个小型技术验证 (spike)。如果是配件类应用,测试蓝牙连接恢复;如果是拍摄类工作流,测试相机输出、文件持久化和上传中断的情况。
插件的存在并不能证明它支持你的具体工作流。 需检查其原生实现、支持的操作系统、维护状态,以及应用退到后台时的行为。
原生开发能直接控制平台 API。Flutter 适合定制化程度高、图形密集型的界面;React Native 则适合愿意围绕原生组件重构 Web UI 的团队。对于游戏,应评估合适的游戏引擎,而不是想当然地认为 Web Shell 就够用。
让架构与团队匹配
Web 团队可以立即调试 DOM 布局和浏览器网络。但如果每个核心功能都需要一个没人能维护的自定义 Swift 或 Kotlin 插件,这种优势就消失了。
PWA 虽然无需原生打包,但浏览器能力和安装条件依然关键。务必在 iOS 上测试你所需的推送、文件和离线工作流,不要假设桌面浏览器的行为会照搬过来。
使用 跨平台开发与原生开发的对比 以评估这些取舍。 对于以内容、表单和标准设备集成为主的现有 Web 应用,Capacitor 通常是上架应用商店最快捷的路径。
如何构建并打包 Capacitor 应用
务必明确 Web 构建与原生打包的步骤。浏览器预览成功并不代表原生项目中已包含最新的文件。
打包流程如下:

生成并同步项目
-
生成可部署的 Web 文件。 运行
npm run build并检查输出目录。Capacitor 需要客户端资源,因此服务端渲染的应用可能需要进行静态导出或架构调整,而不仅仅是复制其服务端输出。 -
使用以下命令安装运行时:
npm install @capacitor/core并使用以下命令安装 CLI:npm install -D @capacitor/cli,然后运行npx cap init。设置应用名称,以及一个反向域名标识符,例如com.example.app,以及webDir到实际的构建输出目录。 -
添加原生目标。 安装
@capacitor/ios以及@capacitor/android,并让 Capacitor 包保持兼容版本。运行npx cap add ios以及npx cap add android以生成提交至应用商店的项目。 -
运行
npx cap sync在重新构建 Web 资源或更改插件后执行。 Sync 并不能替代你的 Web build 命令:同步过时的输出仍会打包过时的界面,这是发布版本看起来没有包含修复的常见原因。 -
仅安装你需要的插件。 例如,安装
@capacitor/camera,@capacitor/push-notifications,或@capacitor/filesystem,然后再次同步。完成每个插件的原生配置,包括 purpose strings、Capabilities 和平台专属配置。
配置并签名 Release 版本
-
使用以下工具打开项目:
npx cap open ios以及npx cap open android。iOS 本地开发需要在 Mac 上使用 Xcode;Windows 和 Linux 团队则需要云端 macOS 构建环境来运行这条 pipeline。 -
设置 Release 标识与展示信息。 确认 iOS 的 Bundle Identifier 和 Android 的
applicationId、用户可见版本、构建标识符、图标和启动屏资源。保持生产与开发配置分离,避免测试构建误用生产凭据。 -
针对 iOS,配置 Apple 分发证书和 Provisioning Profile,或使用托管签名。针对 Android,配置 upload key 和 Play App Signing;常见签名错误是所用凭据或 Profile 与应用标识符及已启用的 Capabilities 不匹配。
-
在真机上验证 Release 行为。 使用模拟器快速迭代,然后在 iOS 和 Android 真机上测试摄像头、通知、deep links、键盘行为和会话中断。仅做 Debug 测试可能漏掉 Release 构建中的配置差异。
-
在 Xcode 中归档 iOS 应用,并使用 Android Studio 或配置好的 Gradle release 任务生成签名的 Android App Bundle。在公开发布前,先上传到 TestFlight 和 Play 内部测试轨道,并安装这些分发构建,而不是信任本地安装的 debug 包。
使用 移动端构建类型指南 以确保开发、测试和生产产物清晰分离。
Apple 和 Google 审核的真实关注点
| 规则区域 | App Store(iOS) | Google Play(Android) |
|---|---|---|
| 构建上传 | 通过 App Store Connect 上传的签名构建版本 | 通过 Play Console 上传的签名 Android App Bundle |
| 隐私披露 | 适用的隐私清单与 App Privacy 标签 | 涵盖所收集和共享数据的数据安全表单 |
| 权限请求弹窗 | 在相应文件中的相关用途说明字符串 Info.plist |
敏感权限的运行时请求与声明 |
| 后台运行 | 已声明的后台模式与获批的系统 API | 后台限制及适用的前台服务类型 |
| 账号注销 | 适用账号创建规则时提供应用内注销 | 应用内注销路径与已声明的网页注销资源 |
| 已下载的 code 与更新 | 必须遵守对已审核功能变更的限制 | 动态 code 必须保持在政策允许的范围内 |
要质疑的假设很简单: web-code 修复无法补上缺失的原生配置Apple 的应用安全文档描述了强制沙盒、签名 entitlements,以及通过系统 API 限制后台执行。
假设应用请求后台定位,但其原生项目缺少相应的后台模式配置和权限描述。仅更新 JavaScript 请求流程并不能将这些设置注入已安装的二进制文件中。请添加正确的配置,移除不必要的权限,并提交重新签名的 build。
把这张图作为提交前的检查点:

检查实际行为,而不只是检查文档
常见的拒审风险包括:相机或定位说明模糊、声明了未使用的后台模式、审核员账号无法访问,以及创建账号却缺少必需的删除流程。如果你为主要账号提供第三方登录,请检查 Apple 的等效登录要求是否适用,以及你的应用是否在豁免范围内。
收集 SDK 仍由你承担披露责任。 即使你的应用 code 从未显式发送信息,分析库也可能会收集相关数据。
在一项针对 23,300 个 iOS 应用包的分析中, 35% 的应用未能披露其收集的数据,根据 根据 NowSecure 的移动隐私分析。不要只根据功能列表生成商店问卷答案;要盘点原生依赖及其实际行为。
提交前:
- 确保功能与构建产物一致。 检查已签名 build 的 entitlements 和后台配置,而不只是查看项目编辑器。
- 审计依赖项。 检查适用的 SDK 隐私清单、required-reason API 声明以及数据收集设置。
- 核对披露信息。 对比 App Privacy 和 Data safety 的回答、同意流程以及 SDK 行为。
- 删除新注册的账号。 验证面向用户的流程和后端处理,包括关联数据及合法的数据保留例外。
本指南中介绍的 iOS 提交清单 帮助将这些检查变成可重复的发布门禁。为审核人员提供可用凭据,并针对他们不易发现的功能给出准确说明。
确保双平台兼容的任务设计规则
- 用户即时操作。 在前台提交表单,持久化其状态,并显示明确的成功或重试结果。
- 可延迟的同步。 使用带适当网络约束的 Android WorkManager 和 iOS
BGProcessingTask在任务符合条件时。 - 定期刷新。 在 Android 上使用 WorkManager 的周期性任务,以及
BGAppRefreshTask在 iOS 上使用相应机制。系统决定符合条件的任务何时运行。 - 推送触发的拉取。 在 Android 上使用合适的 FCM 数据消息,并在 iOS 上使用
content-available后台远程通知。两者都无法保证执行时间。 - 大文件传输。 在 Android 上使用合适的 WorkManager 传输工作流,并在 iOS 上使用后台
URLSession传输,同时具备原生持久化和恢复能力。
在后台任务中,共享接口最明显地不再代表共享执行。JavaScript 定时器不是可靠的调度器,让 WebView 保持忙碌也替代不了原生后台 API。
本指南中介绍的 Android 后台优化指南 建议将可延迟的工作移至调度 API,并避免长时间持有部分唤醒锁。按 紧急程度、持久性和连接性 对工作分类,再选择实现方式。
确保中断的工作安全
将业务意图保留在共享 code 中,例如记录订单需要上传。让原生插件负责持久化调度、操作系统回调和传输持久化。后台回调不能假设 WebView 可用。
每个可重试任务都应 幂等在创建订单时分配一个稳定的操作标识符,在传输前持久化,并让服务器能识别该标识符的重复提交。
重试应只重复尝试,而不是重复客户的购买。
在合适的网络和充电条件下批量处理可延迟的任务,而不是为每个微小变化都唤醒设备。在持久化边界保存进度,并区分可重试的连接失败与需要用户处理的校验错误。
在开启 Android Battery Saver 和 iOS Low Power Mode 的情况下测试。还要测试传输中断网、终止应用,以及服务器已接受请求但客户端尚未保存响应时重新打开应用的情况。
在诊断事件中记录任务标识符、平台、原生版本和完成状态。这样就能区分是调度问题,还是服务器拒绝或防重复决策。
发布前测试无障碍与设备碎片化
Android 约占 2025 年全球移动操作系统使用率的 72.72%,相比之下, iOS 为 26.92%,根据 (StatCounter)。Android 还覆盖众多厂商和价位,因此只用旗舰机测试会漏掉重要的内存、渲染和键盘行为问题。
请纳入一台低配 Android 设备和你支持的最旧 WebView 配置。在 iOS 上,覆盖会显著影响界面的受支持屏幕尺寸与操作系统组合。
使用辅助技术运行核心流程
-
启用 VoiceOver 和 TalkBack。 在不依赖视觉位置的情况下完成注册、结账和账号注销。检查路由切换、对话框及校验失败后的焦点顺序。
-
将系统文本大小调整为支持的最大值。确认按钮、导航标签、总计和错误消息仍然可见且可操作,而不是在固定高度容器中被裁剪。
-
测量对比度。 使用对比度分析器,对照所选的无障碍标准检查文本、控件、焦点指示器和错误状态。分别测试浅色和深色外观。
-
检查表单语义。每个输入框都需要可访问的标签,错误信息必须指明受影响的字段,并在适当时进行播报。
-
测试本地化和设备限制。 使用较长的翻译字符串,打开键盘,旋转支持的屏幕,并在内存和连接受限的情况下重复工作流程。
在规划基于任务的测试环节时,Uxia 关于 如何测试移动应用可用性 的指南,可以与技术层面的无障碍检查结合使用。屏幕上的标签即使显示正确,用户仍可能无法理解下一步操作。
本指南中介绍的 《欧洲无障碍法案》已于 2025 年 6 月 28 日开始实施,涵盖电子商务、银行和客运服务,由……总结 Cliffex 移动端开发概览. 请核查服务的范围及适用义务,不要把商店审核当作无障碍认证。
使用 移动端测试清单 为每次发布留存证据。部署后,按平台、原生版本和 Android WebView 版本,将崩溃、失败流程和渲染投诉与上一版本进行对比。
无需等待商店审核即可发布更新
| 变更类型 | 示例 | Live update | 新的商店构建 |
|---|---|---|---|
| JavaScript 逻辑修复 | 修复失效的结账按钮 | 是的,在已审核用途范围内 | 否 |
| CSS 或布局修复 | 修复 Web 层 safe-area 布局缺陷 | 是的,在已审核用途范围内 | 否 |
| 图片或资源变更 | 更换 onboarding 插图 | 是的,在已审核用途范围内 | 否 |
| 新增原生插件 | 添加相机或生物识别 plugin | 否 | 是 |
| 权限或授权 | 添加后台定位功能 | 否 | 是 |
| Capacitor 或原生 SDK 升级 | 升级原生运行时或 SDK | 否 | 是 |
Live Updates 可将符合条件的 Web 资源交付到已安装的原生壳中。 它们不会替换其已编译的插件、entitlements、权限或原生库。
在准备部署时,请始终明确这条边界:

仅仅因为 JavaScript 实现避开了原生编译,并不代表它自动符合条件。不要用下载的 bundle 引入违反应用商店政策、改变已审核用途或加载被禁止的可执行 code 的功能。发布前请检查是否符合条件,尤其是在更改身份验证、支付或敏感数据相关行为时。
渐进式部署结账流程修复
假设一个结算按钮停止响应,因为点击处理函数拒绝了有效的购物车状态。修复该处理函数,添加回归测试,构建 bundle,并针对你打算支持的每个原生版本进行验证。
对于假设的发布流程,将 bundle 暴露给 符合条件的设备的 5%,然后扩大到 25% ,最后 100% 检查结果后。这些是示例部署设置,并非行业基准。
使用 production 和 beta 通道、原生版本定向,并保留一个稳定 bundle。Observe 可帮助检查采用情况和更新失败,但 bundle 安装成功并不能证明结账正常:还要监控应用的结账错误和完成事件。
发布保障: 在部署前而非用户反馈问题后,定义失败阈值和最小样本量。
若超过配置阈值,请暂停扩大发布范围,并通过回滚工作流恢复稳定 bundle。确认哪些设备已安装该更新,因为离线设备无法立即收到修复。
本指南提供了更新工作流的另一种视角:
按照 OTA 更新实施指南 以配置分发机制和兼容性规则。分别对比 iOS 和 Android 的采用率与应用失败率;旧版 Android WebView 可能会暴露 iOS 上从未出现过的 JavaScript 或 CSS 不兼容问题。
若更新获取失败,请确保初始应用仍可使用。网络中断应让用户停留在可用的内置 bundle 或之前验证过的版本上,而不是卡在空白加载界面。
常见的跨平台问题及其解决方案
| 现象 | 可能原因 | 修复 |
|---|---|---|
| 内容被刘海屏或底部指示条遮挡 | 缺少安全区域处理 | 设置 viewport-fit=cover 并填充 env(safe-area-inset-bottom) |
| iPhone 键盘弹出时页面横向滑动 | WebView 中横向溢出 | 设置 overflow-x: hidden 在 body 上,并移除固定宽度元素 |
| Android 返回键会关闭多步流程 | 未处理返回事件 | 添加一个 Capacitor 应用 backButton 通过 history 路由的监听器 |
| Android 能收到推送,iOS 收不到 | 缺少 APNs 配置或推送能力 | 启用推送通知并配置服务商的 APNs 身份验证密钥 |
| 布局在模拟器中正常,但在旧款 Android 手机上错乱 | 旧版 WebView 缺少必要的 CSS 特性 | 测试支持的最低版本 WebView 并添加降级方案 |
| Play Console 拒绝数据安全表单 | 未声明 SDK 收集 | 审查 SDK 行为并在提交前修正表单 |
| 更新仅覆盖部分用户 | 定向规则或原生兼容性不匹配 | 在 Observe 中检查版本定向、适用资格和 bundle 兼容性 |
将“原因”列视为初步假设,而非最终诊断。推送失败也可能来自权限被拒、token 注册错误或 APNs 环境错误。
修复底层边界问题
对于安全区域,请添加 viewport-fit=cover 到 viewport meta 配置中,并为接触屏幕边缘的容器应用 inset padding。不要盲目地给页面和固定页脚都加相同的 padding,否则会造成过大的间距。
针对键盘溢出问题,请使用 WebView 检查找出宽度超过 viewport 的元素。隐藏水平溢出有助于控制布局,但不应掩盖仍位于屏幕外的必需控件。
使用以下方式处理 Android 导航: App.addListener('backButton', ...) 来自 @capacitor/app。沿当前流程回溯,在适当时关闭已打开的模态框,仅在有意到达根状态时才退出。
对于 iOS 推送,检查已签名应用的 capability 配置、注册结果、provider 凭据和投递环境。如果应用从未注册有效 token,上传 APNs key 也无济于事。
未完成的更新 rollout 不一定意味着 rollout 失败。 设备可能处于离线状态、被 channel 排除、与 bundle 不兼容,或不在 staged cohort 中。在调整 targeting 之前,先区分是有意排除还是下载或激活失败。
使用一致的诊断顺序: 先查平台和原生应用版本,再查 bundle 版本,然后查原生 capability 列表。接着在完全相同的条件下复现问题,捕获设备 logs,并修改能解释故障的最小层级。
选好构建路径,发布首个版本
- 现有 Web 应用和小团队。 从 Capacitor 开始,在打磨所有界面之前,先验证最难的 native 集成。
- 图形密集型或高度依赖平台特性的产品。 根据实际工作量,评估原生开发、Flutter 或合适的游戏引擎。
- 没有本地 Mac。 使用云端 macOS 构建服务,同时明确签名归属和恢复流程。
- 客户端应用或白标版本。 选择具备独立应用身份、凭据、渠道和访问控制的工具链。
把决定变成一次发布
- 确定构建路径并梳理所需的原生能力,包括后台任务、身份验证、通知、存储和删除行为。
- 明确签名归属。 在本地或云端生成已签名的构建产物,并记录谁有权恢复证书和上传凭据。
- 通过 TestFlight 和 Play 内部测试进行分发。针对已安装的产物运行提交检查。
- 将 Beta 与生产交付分离。 定义兼容的原生版本,并保留一个已知可用的回退版本。
- 从有意控制的小规模分阶段用户群开始,并配置失败告警。发布前设定扩大范围的标准。
- 审查实际设备上的结果。 调查意外故障,必要时停止推广,并验证回滚是否完成,而不是假设它已完成。
云原生构建和 Live Update 分发可以位于同一账户中,但应保持独立的审批路径。批准原生权限变更的人与批准样式表修复的人需要不同的证据。
在编写第一个发布任务之前,回答这个问题: 哪些功能需要原生构建,哪些可以通过空中下载方式更改?
Capgo 为 Capacitor 和 Ionic 应用提供云 Native Build、Live Updates 和发布监控,将原生打包与受控的 web bundle 分发相结合。访问 Capgo 设置商店构建工作流,创建 beta 和 production 渠道,并准备带稳定回退的分阶段更新。