老王在巷子口开了家“老王食堂”,他熬制的番茄牛腩饭远近闻名。可巷子口的生意终究有限,老王想把这道招牌菜送进城里最火的两家美食城——“A 精品美食城”和“G 连锁超市”去卖。问题来了:在家里现炒现卖没问题,要进大商场,就得把菜变成标准化、可运输、能追溯的商品。本文要讲的 React Native(RN)应用打包与上架流程,正是这“把菜变成商品”的最后一公里:选餐盒(APK/AAB 构建)、盖防伪印章(签名证书)、给秘方加密(ProGuard 混淆)、贴批次号(版本管理)、通过品控评审(双商店审核),最后让中央厨房自动流水线批量出餐(CI/CD 发布管线)。全程跟着老王走一遍,你就能理解一个 RN 应用从“能跑”到“能卖”的全部奥秘。

岗位关联

对 AI 从业者而言,掌握打包上架流程意味着你能独立把 AI 应用从原型变成商店里真实可下载的产品——这是从“会写算法”到“能交付价值”的关键一步,也是移动端、跨平台、AI 产品落地岗位的必备技能。

目录
  1. 可视化演示
  2. 安卓双包构建:便当盒还是中央厨房大箱?
  3. iOS归档上传:密封银罐的规矩
  4. 签名证书配置:一枚不能丢失的私房菜章
  5. ProGuard混淆:用密语重写独家菜谱
  6. 版本号管理:每批菜的批次号
  7. 双商店审核上架:两家美食城的不同品控
  8. 自动化发布管线:从一口铁锅到一条流水线
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示React Native应用从源码构建到商店上架的完整流程:Android/iOS双平台构建管线、签名混淆配置,以及App Store与Google Play审核发布流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

RN应用构建管线流程 源码经Metro打包生成JS Bundle,链接原生模块 Gradle/Xcode编译原生代码,生成APK/AAB或IPA 签名证书配置确保应用身份可信 Build=Compile+Bundle+Sign+Package 构建管线决定发布质量

安卓双包构建:便当盒还是中央厨房大箱?

G 连锁超市给老王下了规矩:“我们只接收大箱装,进了我的中央厨房,会按每位顾客的口味自动分装成小盒。”而老王想摆摊散卖时,得直接带上即食便当盒。于是他准备了两种容器:一张嘴就能递出去的“便当盒”(APK),和交给超市大仓的“整箱食材包”(AAB)。容器不同,食材却都出自同一口锅——这正是跨平台框架“一份代码、双端打包”的底气。

在 RN 的世界里,Android 端的打包产物正是这两兄弟。APK(Android Application Package)是一个完整的、可直接安装到手机上的单文件安装包;AAB(Android App Bundle)则是一种发布格式,它不能直接安装,而是交给 Google Play 的“智能分装线”,由商店根据每位用户手机的屏幕密度和 CPU 架构,动态生成最合适的 APK 再下发。自 2021 年 8 月起,Google Play 已强制要求新应用使用 AAB 上架。

RN 项目构建这两个产物,只需在 android 目录下执行 Gradle 命令(Gradle 是 Android 的“厨房指挥系统”):打调试包 ./gradlew assembleDebug,打发布包 ./gradlew assembleRelease;要生成 AAB 则执行 ./gradlew bundleRelease。产物分别输出到 android/app/build/outputs/apk/ 和 android/app/build/outputs/bundle/ 目录。注意,默认生成的 Release 包是未签名的,需要先配置签名信息,否则只能得到“盖不了章的半成品”。

AAB 分装的精妙之处,在于“把同一个菜谱按顾客身材做定制份量”:

老王想明白了:进 G 超市必须整箱送(AAB),自己发版、内测、线下安装,就用便当盒(APK)。两种容器各司其职,锅里的菜却永远是同一道——这就是 RN 这类跨平台框架最迷人的地方:一次烹饪,处处上桌。

S下载=Sbase+Sdensity+Sarch\displaystyle S_{\text{下载}} = S_{\text{base}} + S_{\text{density}} + S_{\text{arch}}
用户实际下载的安装包体积,等于基础包、屏幕密度资源、CPU 架构库三部分之和——通常远小于全量 APK。
  1. 基础包 base所有设备都需要的代码与资源骨架,AAB 把它单独拆出。
    SbaseS_{\text{base}}
  2. 屏幕密度包 density按 mdpi/xhdpi/xxhdpi 拆分图片资源,用户只下载匹配自己屏幕的那份。
    SdensityS_{\text{density}}
  3. CPU 架构包 arch按 arm64-v8a/armeabi-v7a 等指令集拆分原生库,只下载对应架构的 .so 文件。
    SarchS_{\text{arch}}
  4. 三份相加才是真相把三部分加在一起,才是单个用户的真实下载量,这解释了 AAB 对全量 APK 的体积优势。
    S下载=Sbase+Sdensity+SarchS_{\text{下载}} = S_{\text{base}} + S_{\text{density}} + S_{\text{arch}}

iOS归档上传:密封银罐的规矩

A 精品美食城规矩更严:不收散盒,只收“密封银罐”。老王得把做好的菜装入统一的银罐,盖紧盖子(归档)、贴上防伪码,再送到美食城的专属收货口(上传 App Store Connect)。银罐,就是苹果世界里的 Archive 归档。

iOS 的构建流程与 Android 大不相同。在 Xcode(苹果官方的“御用厨房”)中用 Release 配置构建并归档后,会得到一个 .xcarchive 包;接着选择 Export,导出为 .ipa 文件——IPA 就是 iPhone/iPad 应用的安装包,相当于 Android 的 APK。导出时 Xcode 会提供四种分发方式:Development(开发调试)、AdHoc(指定设备测试)、Enterprise(企业内部分发)和 App Store(上架专用)。老王要进美食城,自然选最后一种。

拿到 IPA 后,需要用 Transporter 或 Xcode Organizer 上传到 App Store Connect。一个合格的 IPA 银罐里必须装齐三样东西:编译出的二进制主体、签名信息(下节细讲)、以及 dSYM 符号表——符号表是崩溃日志的“翻译词典”,没有它,线上崩溃报告只是一堆看不懂的内存地址。

老王把银罐交到专属收货口,终于松了口气。但刚出锅的菜也怕被冒名顶替——美食城怎么知道这罐菜真是老王的手艺?这就引出了发布流程中最关键的一环:防伪印章。

IPA=App+签名+dSYM\displaystyle \text{IPA} = \text{App} + \text{签名} + \text{dSYM}
一个可上架的 IPA 等于“应用二进制 + 签名信息 + 符号表”三件套,缺了符号表,出了线上事故你将无从查起。
  1. App 二进制主体Xcode 把 Swift/Objective-C 代码,连同 RN 打包好的 JS Bundle 一起编译成可执行文件。
    App=JS Bundle+原生框架\text{App} = \text{JS Bundle} + \text{原生框架}
  2. 签名封条使用 App Store 发布证书对二进制进行签名,相当于银罐的防伪封条,证明你是合法开发者。
    CodeSign(App)\text{CodeSign}(\text{App})
  3. dSYM 符号词典记录内存地址与函数名的对应关系,用于线上崩溃堆栈的符号还原。
    dSYM={addr→func}\text{dSYM} = \{\text{addr} \to \text{func}\}
  4. 三者合罐把三部分打成一个 IPA 安装包,上传后才会被商店接受。
    IPA=App+签名+dSYM\text{IPA} = \text{App} + \text{签名} + \text{dSYM}

签名证书配置:一枚不能丢失的私房菜章

美食城有条规定:每道菜必须盖“老王私房菜”的防伪印章,没章的一律拒收。顾客收到菜后,用印在菜单上的“验章图”一比对,就能确认菜没被人中途掉包。这套“盖章 + 验章”的机制,在应用世界里就叫签名证书。

Android 侧,老王用 JDK 的 keytool 工具生成一个 .keystore 签名文件——相当于一枚铜章,里面包含一把私钥和一把公钥。在 android/app/build.gradle 的 signingConfigs 里配好 storeFile、storePassword、keyAlias、keyPassword,再在 buildTypes.release 中引用,执行 assembleRelease 时 Gradle 就会自动“盖章”。RN 项目模板中签名配置被刻意留空,就是要开发者在上架前亲手填上。

iOS 侧更复杂:苹果要求“官方备案的钢印”(开发者证书)加上“临时通行证”(描述文件 Provisioning Profile)。开发者证书向苹果申请,描述文件则把 App ID、开发者证书和测试设备绑定在一起。每次 Export 归档,系统都会用描述文件里的证书对 IPA 做代码签名。签名的本质是密码学操作:用私钥对包内容的哈希加密,验证方用公钥解开比对——任何字节被篡改,比对立刻失败,系统直接拒绝安装。

老王把铜章(keystore)复制了三份,分别锁进保险柜、银行保险箱和老家地窖——因为 Android 的签名一旦丢失,就无法对新版本做覆盖签名,老用户将永远停在旧版,只能换包名重新上架。印章比菜还金贵,这就是签名证书的江湖地位。

verify(m,σ,pk)  ⟺  σ=Esk(hash(m))\displaystyle \text{verify}(m, \sigma, pk) \iff \sigma = E_{sk}(\text{hash}(m))
验签过程可理解为:对安装包内容算 DNA 指纹,用私钥给指纹加密,手机拿到后用公钥解密比对,一致才算“正版老王出品”。
  1. hash(m) 数字指纹对安装包全部内容计算出一个固定长度的摘要,内容差一个字节,指纹就完全不同。
    hash(APK)\text{hash}(\text{APK})
  2. E_sk 私钥盖章用 keystore 里的私钥把指纹加密,得到签名 σ;私钥全世界只有老王(开发者)一人持有。
    σ=Esk(hash(m))\sigma = E_{sk}(\text{hash}(m))
  3. verify 验章用户手机用公钥解密 σ,重新计算包的指纹并比对,一致则通过,确认包未被篡改。
    verify(m,σ,pk)\text{verify}(m, \sigma, pk)

ProGuard混淆:用密语重写独家菜谱

老王发现,便当盒虽方便,但有心人拆开盒子就能看到完整菜谱,连独家香料配比都被学走。他决定用一套“密语缩写”重新誊写菜谱:把“番茄”写成"T7“、“牛腩”写成”N3",外人看了莫名其妙,自家徒弟(系统内部)却一看就懂。

在 Android 的 release 构建中,ProGuard/R8 就是这套密语系统。它一口气做三件事:压缩(shrink)删除未使用的代码与资源;优化(optimize)简化字节码;混淆(obfuscate)把类名、方法名、字段名重命名为 a、b、c 之类的短名。RN 项目模板在 build.gradle 中已默认开启 minifyEnabled true,并附带了 proguard-rules.pro 规则文件。

但“密语”不能乱用——有些代码是“公开合同”,改名就全乱套:通过反射调用的类、被 AndroidManifest 引用的组件、以及 RN 的原生桥接接口(NativeModule)和 JNI 方法。这些必须用 -keep 规则明确保护。初学者最常见的错误是“一混淆就崩溃,干脆把 minifyEnabled 改成 false”——这等于为防小偷把整个保险柜扔掉。正确做法是借助 R8 输出的 mapping.txt 还原崩溃堆栈,定位被误伤的类,逐个补进 keep 规则。

老王给菜谱的关键页贴上了“请勿混淆”的便利贴:徒弟们看到便利贴就知道这两页要原样保留。RN 的 keep 规则,正是贴在 NativeModule、反射类等关键代码上的便利贴——保住了桥,菜才能稳稳端上桌。

S混淆后≈S混淆前×(1−ρ),ρ∈[0.15,0.25]\displaystyle S_{\text{混淆后}} \approx S_{\text{混淆前}} \times (1-\rho), \quad \rho \in [0.15, 0.25]
经验数据显示,RN 应用开启混淆后,APK 体积通常能缩小 15%~25%,相当于既给 App“瘦了身”,又给菜谱上了锁。
  1. 混淆前体积未开启 ProGuard 的原始包大小。
    S混淆前S_{\text{混淆前}}
  2. 压缩率 ρ实测中 RN 应用的体积缩减比例,大多落在 0.15 到 0.25 之间。
    ρ∈[0.15,0.25]\rho \in [0.15, 0.25]
  3. 缩小系数 (1-ρ)混淆后体积约为原来的七到八成,代码也更难被逆向读懂。
    S混淆后=S混淆前×(1−ρ)S_{\text{混淆后}} = S_{\text{混淆前}} \times (1 - \rho)

版本号管理:每批菜的批次号

老王每次出摊都要在餐盒上贴一张批次号:今天做的是“2.3.1 批”,明天改进配方就是“2.3.2 批”。顾客吃了肚子疼,凭批次号能追溯到是哪锅;美食城也知道哪批是新菜、哪批该下架。版本号,就是应用的批次号。

Android 的版本号有两个字段:versionCode 是整数,给机器看,每次上传商店必须严格递增,否则商店直接拒绝;versionName 是字符串,给人看,比如“1.2.3“。iOS 对应的则是 CFBundleShortVersionString(展示版本)和 CFBundleBuildNumber(构建号)。在 RN 项目中,双端版本号统一维护在 package.json 的 version 字段里,再通过构建脚本同步到原生工程,避免”两端版本打架”。

实践中常用“三段式合成法”生成 versionCode:主版本号、次版本号、修订号各占固定进位。这样 2.3.1 就编码成 20301,2.3.10 编码成 20310——天然单调递增,彻底解决“改了小版本忘了改 versionCode”的尴尬。

批次号还承担另一个职责——追溯。发布到商店的每一版都和 Git 标签、构建时间绑定,一旦线上出了问题,老王能立刻说出“这批菜是上周三下午三点出的锅”。版本管理,就是发布流程的“后悔药”和“照妖镜”。

versionCode=major×10000+minor×100+patch\displaystyle \text{versionCode} = \text{major}\times 10000 + \text{minor}\times 100 + \text{patch}
versionCode 的三段式合成:主版本占万位、次版本占百位、修订号占个位,每个数字各就各位,保证新版本号永远大于旧版本号。
  1. 主版本占万位主版本号 2 乘以 10000,在万位上站岗,得 20000。
    major×10000\text{major} \times 10000
  2. 次版本占百位次版本号 3 乘以 100,在百位上值守,得 300。
    minor×100\text{minor} \times 100
  3. 修订号占个位修订号 1 直接落在个位,得 1。
    patch\text{patch}
  4. 三段求和20000 + 300 + 1 = 20301,固定进位让版本号“只涨不跌”。
    versionCode=2×10000+3×100+1=20301\text{versionCode} = 2 \times 10000 + 3 \times 100 + 1 = 20301

双商店审核上架:两家美食城的不同品控

两家美食城的品控规矩完全不同。A 精品美食城派出“评审团”人工试吃每一道菜,看重食材来源(隐私政策)、有没有“偷工减料”(违规 API)、有没有暗中加料(隐藏功能);G 连锁超市则用“机器初筛 + 人工抽检”,更在意你用的油是否符合行业标准(目标 API 级别)、包装是否合规(强制 AAB)、有没有乱标功效(权限声明)。

App Store 审核是“人审为主”:开发者要在 App Store Connect 填完整元数据、隐私政策 URL、提供审核账号,凡涉及内购必须走苹果支付渠道。审核周期通常 1~7 天,节假日高峰期更久;被拒后通过 Resolution Center 与审核员沟通。Google Play 则是“机审为主”:机器扫描恶意代码与权限,再配合政策审查,审核通常几小时到几天。上架前必须完成 Data safety 表单、隐私政策链接,并满足 target API 级别要求——近年 Google 强制新应用使用更高版本的 targetSdk。

上架最佳实践是“灰度发布”:Google Play 支持分阶段发布(5% → 20% → 50% → 100%)逐步放量;iOS 虽然不能按比例放量,但可以用 TestFlight 内测 + 版本更新审核控制节奏。无论哪家,把“审核信息清单”做成模板(元数据、截图、隐私政策、审核账号)都能大幅缩短评审往返周期。

老王总结:进 A 美食城要准备一份精美的“专题报告”(详细元数据),进 G 超市要遵守“标准货架协议”(政策合规)。两家餐厅规矩不同,但都要求“诚实经营、信息透明”——隐私与合规,永远是上架这条路上的通行证。

T上架=T排队+T评审+T返工\displaystyle T_{\text{上架}} = T_{\text{排队}} + T_{\text{评审}} + T_{\text{返工}}
一次上架的总耗时等于“排队等审 + 实际评审 + 被拒返工”三者之和——审核清单准备得越充分,第三项越趋近于零。
  1. 排队耗时提交后等待进入评审队列的时间,受节假日和提交高峰期影响最大。
    T排队T_{\text{排队}}
  2. 评审耗时审核员实际检查的时间,苹果的人审通常长于谷歌的机审。
    T评审T_{\text{评审}}
  3. 返工耗时被拒后修改代码、重新提交的额外时间,能不能压到零,取决于资料是否一次齐备。
    T返工T_{\text{返工}}
  4. 三段时间相加总上架时间等于三者之和,“一次过审”是发布效率的生命线。
    T上架=T排队+T评审+T返工T_{\text{上架}} = T_{\text{排队}} + T_{\text{评审}} + T_{\text{返工}}

自动化发布管线:从一口铁锅到一条流水线

第一批菜上架后,老王发现每次发版都要手动洗菜、切菜、炖煮、装盒、盖章,一个环节出错就前功尽弃。他咬咬牙,给自己建了一条“中央厨房自动流水线”:新鲜菜谱一提交到总厨房(代码仓库),流水线自动开始洗切(依赖安装)、炖煮(编译构建)、装盒(打包)、盖章(签名)、送检(上传商店/内测平台)。

这条流水线在工程上叫 CI/CD(持续集成/持续交付)。用 Jenkins、GitHub Actions 或 GitLab CI 作为“总控台”,把“还原依赖 → 单元测试 → 构建 APK/AAB/IPA → 签名 → 上传 TestFlight/Firebase → 通知审核”串成一条命令链。其中 Fastlane 是最常用的移动端自动化工具链:它是流水线的“班组长”,用 Fastfile 描述一个个 lane(通道),一句 fastlane beta 就能完成证书下载、构建、签名、上传全套动作。

自动化管线的成败藏在“秘密”管理里:签名密钥、密码、API Key 绝不能写进代码仓库,而要放进 CI 平台的环境变量或密钥管理服务。同时要设计“保险丝”:流水线任何一步失败立即报警,绝不带着坏包继续往下传;发布后配合 Crashlytics/Sentry 监控崩溃率,一旦异常立刻回滚。发布成功率本质上是各环节成功率的乘积——每一环都要稳。

流水线运转那天,老王泡了壶茶,看着屏幕上的任务一个个变绿。他想起了当初蹲在巷口炒菜的日子。从一口铁锅到一条流水线,变的只是交付方式,不变的,是那道让用户记住的招牌菜——产品力,永远是发布流程的起点。

P发布成功=∏i=1npi\displaystyle P_{\text{发布成功}} = \prod_{i=1}^{n} p_i
串联流水线的总成功率等于每道工序成功率的连乘:环环相扣,任何一环失手,整批菜都出不了厨房。
  1. 单环节成功率 p_i依赖还原、构建、签名、上传、审核,每个环节都有各自的成功概率。
    pi(i=1,2,…,n)p_i \quad (i = 1, 2, \dots, n)
  2. 连乘符号 ∏串联系统中,任一步失败则整体失败,总成功率是各环节概率的乘积。
    P发布成功=p1×p2×⋯×pnP_{\text{发布成功}} = p_1 \times p_2 \times \cdots \times p_n
  3. 量化教训若 8 个环节每个都有 98% 成功率,总成功率仅约 85%——每多一次人工操作,就多一次失手可能。
    ∏i=180.98≈0.85\prod_{i=1}^{8} 0.98 \approx 0.85

AI 应用场景

场景1:跨平台发布平台建设

阶段:AI 应用的工程化交付与持续迭代

解决的问题:传统双端手工打包一次要 1~2 天,签错证书、传错包是家常便饭;而 AI 应用迭代节奏极快(模型更新、Prompt 调整、推理库升级都要发版),手工流程成为瓶颈。CI/CD 管线 + Fastlane 把“构建 → 签名 → 上传 → 通知”压缩到 20 分钟,团队成员只需合并代码,剩下的交给流水线。

具体案例:某 AI 笔记 App 的算法团队每周更新一次关键词抽取模型。RN 端通过 GitHub Actions 在 iOS/Android 双端并行构建,产物自动上传 TestFlight 和 Firebase App Distribution,产品经理第二天就能在手机上体验新版模型效果。

场景2:灰度发布与崩溃监控

阶段:模型上线后的风险控制

解决的问题:AI 功能(如拍照识物)直接发布到全量用户,若在部分机型上崩溃,影响面巨大。通过 Google Play 分阶段发布 + 崩溃监控,先把新版本发给 5% 用户,观察 Crashlytics 上报的崩溃率和核心功能埋点,确认无异常再逐步放量,把风险关在笼子里。

具体案例:某出海相机 App 发布“AI 场景识别”版本时,采用 5% → 20% → 50% → 100% 四段式放量,每段观察 24 小时。期间发现旧款机型 GPU 不支持新的推理库,于是针对低端机回退到轻量模型,避免了一场大规模差评。

场景3:商店合规自动化检查

阶段:出海 AI 应用的合规审计

解决的问题:App Store 和 Google Play 对 AI 应用越来越关注“数据训练来源、用户数据使用、生成内容管控”。传统做法是上架前人工逐项核对隐私政策,漏一项就被拒,来回折腾一周。现在把检查项写成脚本:自动扫描权限声明、隐私政策 URL 有效性、元数据完整性,甚至用 NLP 检查隐私政策是否覆盖 AI 数据处理条款。

具体案例:某 AI 聊天助手出海前,用脚本批量生成 40 余国语言版本的隐私政策摘要,并自动校验 Google Play Data safety 表单与代码中实际申请的权限是否一致,最终审核一次通过。

Python 演示

python60 行
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""RN 应用发布管家:版本号自增检查 + 打包流水线模拟""" import jsonimport time  def build_version_code(major, minor, patch):    """三段式合成 versionCode: 主版本占万位, 次版本占百位, 修订占个位"""    return major * 10000 + minor * 100 + patch  def check_increasing(new_code, history):    """对比历史版本列表, 防止 versionCode 回退"""    if not history:        return True, "首个发布版本, 允许上架"    latest = max(history)    if new_code <= latest:        return False, "版本号必须大于历史最大值 %d" % latest    return True, "版本号 %d 大于历史最大值 %d, 校验通过" % (new_code, latest)  PIPELINE = [    ("还原依赖", "npm install / pod install 拉齐依赖"),    ("编译打包", "Gradle 产出 AAB, 或 Xcode 归档出 IPA"),    ("签名盖章", "keystore 或证书描述文件完成签名"),    ("ProGuard 混淆", "压缩体积并保护 keep 规则外的代码"),    ("上传商店", "Transporter 传 App Store, 或上传 Google Play"),    ("等待审核", "等待品控评审结果, 通过后即可发布"),]  def run_pipeline(app_name, version_name, version_code):    print("== %s v%s (versionCode=%d) 发布流程 ==" % (app_name, version_name, version_code))    for index, (stage, desc) in enumerate(PIPELINE, 1):        print("[%d/%d] %s... %s" % (index, len(PIPELINE), stage, desc))        time.sleep(0.3)        print("    完成")    print("== 已送达商店评审台, 等待品控结果 ==")  def main():    config = json.loads(        '{"app_name":"老王食堂","major":2,"minor":3,"patch":1,'        '"history":[20001,20100,20200]}'    )    version_code = build_version_code(config["major"], config["minor"], config["patch"])    version_name = "%d.%d.%d" % (config["major"], config["minor"], config["patch"])    ok, message = check_increasing(version_code, config["history"])    print(message)    if not ok:        print("发布中止: 版本号回退会被商店直接拒绝")        return    run_pipeline(config["app_name"], version_name, version_code)    print("审核通过后, 即可按灰度比例逐步放量")  if __name__ == "__main__":    main()
目的:模拟 RN 应用发版前的两道关键工序——先用三段式合成 versionCode 并校验其单调递增,再走完“还原 → 构建 → 签名 → 混淆 → 上传 → 审核”的完整发布流水线。
  1. return major * 10000 + minor * 100 + patch把 2.3.1 编码成 20301,让版本号天然只增不减。
  2. latest = max(history)取出历史最大版本号作为底线,防止发版回退。
  3. if new_code <= latest:商店判定“是不是新版本”的唯一依据,就是 versionCode 是否增大。
  4. PIPELINE = [...]用一张表描述流水线的六个环节,环节可增可减,模拟真实 CI 配置。
  5. for index, (stage, desc) in enumerate(PIPELINE, 1):逐站“走流水线”,每一站都有明确职责说明。
  6. ok, message = check_increasing(...)先校验再打包,把“版本号回退”这类低级错误挡在构建之前。
  7. if __name__ == "__main__":Python 的约定入口,保证脚本既可被导入也能直接运行。

常见误区

误区“Google Play 只收 AAB,那 AAB 就是新一代安装包,我给用户传 AAB 文件,他们也能直接装。”

正解AAB(Android App Bundle)是“发布格式”而不是“安装格式”,它天生不能直接安装。它就像“中央厨房的整箱食材”,Google Play 拿到后,按用户设备的屏幕密度与 CPU 架构现场分装成真正的 APK 再下发。开发者若想绕过商店直接分发,必须自己构建并签名 APK。

为何常见:大家听说“以后都用 AAB”,又看到文件后缀带着 Android,容易想当然地把它当成新型安装包,忽略了“发布格式”与“安装格式”的本质区别。

误区“我把 versionName 从 1.0 改成 1.1,versionCode 还是 1,商店应该能认出这是新版本吧?”

正解商店判定“是不是新版本”的唯一依据是 versionCode(整数必须严格递增)——versionName 只是给人看的标签。versionCode 不变,商店会认为你重复提交了同一个版本,直接拒绝。正确做法是每次构建让 versionCode 自动 +1,或使用三段式合成法保证只增不减。

为何常见:人眼习惯看 1.0、1.1 这种语义化版本号,而机器其实只认一个递增整数,两套认知体系错位,就闹出了“改了名字没改身份证”的笑话。

误区“开 ProGuard 后 App 一启动就闪退,这工具太坑了,我直接把 minifyEnabled 设成 false 算了。”

正解混淆本身不会“制造”崩溃,崩溃几乎总源于某些类被重命名后找不到引用——比如反射调用的类、RN 的 NativeModule、JNI 方法。正确姿势是用 R8 输出的 mapping.txt 还原崩溃堆栈,定位被误伤的关键类,把它们加进 -keep 规则。关掉混淆等于为防小偷把保险柜整个扔掉,体积、逆向风险全都回来了。

为何常见:崩溃恰好发生在混淆之后,时间上强相关,初学者容易把因果关系扣在“混淆”头上,而忽略真正原因是“被重命名的类断了链”。

误区“我的 keystore 文件丢了,找 Google 客服再生成一个不就行了,反正应用都在商店里。”

正解Android 签名的私钥无法“补办”。签名丢失后,你虽然能用新密钥生成新 APK,但签名不同,用户无法覆盖安装——老用户要么永远停在旧版,要么卸载重装(数据全丢)。业界标准做法是使用 Play App Signing:上传应用时把上传密钥交给 Google、由 Google 保管应用签名密钥,同时一定要对 keystore 做异地加密备份。

为何常见:很多人把 keystore 类比成“账号密码”,觉得密码忘了找客服重置是天经地义的,但私钥在密码学上就代表“你本人”,丢了就是“身份没了”,无法重置。

面试题

Q1. 关于 Android 的 APK 与 AAB,下列说法正确的是?

单选题 · easy

  • A.AAB 可以直接安装到手机
  • B.APK 是商店发布专用格式
  • C.AAB 由商店按需生成 APK← 正确答案
  • D.两者都是源码文件格式

答案:C。AAB 是发布格式,不能直接安装;APK 才是可直接安装的安装包。C 正确:Google Play 拿到 AAB 后按设备动态生成 APK。D 显然错误,两者都是构建产物而非源码。用老王的故事说,AAB 是中央厨房大箱,APK 是分装好的便当盒。

Q2. iOS 提交 App Store 的归档产物是?

单选题 · easy

  • A.APK 安装包文件
  • B.AAB 应用束文件
  • C.IPA 安装包文件← 正确答案
  • D.ZIP 源码压缩包

答案:C。iOS 的安装包格式是 IPA,由 Xcode Archive 后 Export 得到,相当于 Android 的 APK。APK/AAB 都是 Android 侧产物,ZIP 只是源码压缩包,都不能直接用于提交 App Store。

Q3. Android 版本号中商店强制要求递增的是?

单选题 · easy

  • A.versionName(展示用)
  • B.versionCode(整型数值)← 正确答案
  • C.buildType(构建类型)
  • D.minSdk(最低系统版本)

答案:B。商店以 versionCode 判断版本新旧,必须严格单调递增,否则拒绝提交。versionName 只是给人看的字符串,buildType 是 debug/release 之分,minSdk 是最低系统版本,都与“版本新旧判定”无关。

Q4. Android 签名文件 keystore 丢失后,最直接的后果是?

单选题 · mid

  • A.无法对 App 进行代码混淆
  • B.用户无法覆盖安装新版本← 正确答案
  • C.应用市场拒绝展示更新
  • D.无法申请新的开发者账号

答案:B。签名丢失后,新包的签名与旧包不一致,Android 系统不允许覆盖安装,老用户只能停在旧版或卸载重装。混淆与签名无关;市场不会因签名丢失拒绝展示;开发者账号也不受影响。这正说明 keystore 比“印章”还金贵,必须异地加密备份。

Q5. 开启 ProGuard 混淆后,下列哪些对象通常需要添加 keep 规则保护?

多选题 · mid

  • A.通过反射调用的工具类← 正确答案
  • B.RN 的桥接原生模块类← 正确答案
  • C.布局 XML 中的 id 名称
  • D.自定义的 Application 类

答案:AB。反射调用和 RN 原生桥接(NativeModule)都是“按名字找类”,一旦被重命名就会断链崩溃,必须 keep。布局 id 属于资源,不受类混淆影响;自定义 Application 类在 Manifest 中有引用,默认会被保留,无需额外配置。混淆安全性来自“只动该动的,保护该保护的”。

Q6. App Store 审核常见的拒审原因包括?

多选题 · hard

  • A.缺少隐私政策与权限说明← 正确答案
  • B.内购未使用苹果的支付渠道← 正确答案
  • C.启动速度比竞品稍微慢一点
  • D.界面配色不符合苹果审美

答案:AB。苹果审核最看重隐私合规与支付合规:缺少隐私政策 URL、未声明权限用途会直接拒审;数字内容类内购不走苹果支付(违反 3.3.2 条款)也会被拒。启动速度不是固定拒审项,界面配色则纯属主观,一般不影响过审。

Q7. 关于 Fastlane 在发布管线中的作用,正确的是?

单选题 · mid

  • A.负责编写应用的业务功能
  • B.自动化构建签名上传等流程← 正确答案
  • C.编译优化 JS 源码的性能
  • D.替代商店的人工审核机制

答案:B。Fastlane 是移动端发布自动化工具链,能把证书下载、构建、签名、上传流水线串成一条命令,相当于中央厨房的“班组长”。它不写业务代码、不优化 JS 编译,更不可能替代商店的人工审核。

Q8. 关于 Google Play 分阶段发布(staged rollout),下列说法正确的有?

多选题 · hard

  • A.按用户百分比逐步扩大放量← 正确答案
  • B.发布途中可暂停或回滚版本← 正确答案
  • C.只适用于系统应用与车机应用
  • D.分阶段发布无需提供隐私政策

答案:AB。分阶段发布让开发者先给 5% 用户,观察崩溃和口碑后再逐步放量,过程中可随时暂停或回滚,是灰度发布的标准姿势。它适用于所有普通应用;而且无论是否分阶段,隐私政策都是上架硬性要求。这与“AI 相机 App 四段式放量”的场景完全吻合。

Q9. iOS 导出 IPA 失败且提示签名相关错误,最可能的排查方向是?

单选题 · mid

  • A.证书与描述文件类型不匹配← 正确答案
  • B.手机连接的 Wi-Fi 信号较弱
  • C.电脑屏幕分辨率设置过高
  • D.应用内图标数量超过上限

答案:A。iOS 签名是“证书 + 描述文件 + App ID”三位一体,任何一环不匹配(如用开发证书配了发布描述文件)都会导致导出失败。Wi-Fi、分辨率、图标数与签名错误没有因果关系。这提醒我们:银罐的封条和通行证必须成对使用。

Q10. 把签名密钥接入 CI 自动化发布时,以下做法最安全的是?

单选题 · hard

  • A.加密后存入仓库环境变量← 正确答案
  • B.明文写进构建脚本文件
  • C.随源码一起提交到 Git
  • D.复制给全体团队成员共享

答案:A。签名密钥属于最高敏感级机密,正确做法是存入 CI 平台的环境变量或密钥管理服务,构建时动态注入。明文写脚本、提交到 Git、广泛共享都会导致密钥泄露——泄露密钥等于把“私房菜印章”的模子送给了外人,攻击者可以伪造你的应用签名。