想象你是一家“Flutter 餐厅”的老板。你经营着两家分店:一家开在 Android 美食广场,另一家开在 iOS 步行街。每道招牌菜都由 Dart 食材精心烹饪,而“发布一个新版本”就像把一道新菜推向市场:既要给餐盒盖出品章(签名打包),又要等卫生局检查(商店审核),然后才能正式端上桌。更棘手的是,有些客人已经坐下来用餐了,你发现菜谱要微调,想偷偷往盘子里加料——这就是“热更新”。可 iOS 步行街的物业规定:已经端上桌的菜,不许再动。于是聪明的老板想出了各种办法:把盐罐子放桌上让客人自己调(远端配置)、设计成自助 DIY 菜品(动态化方案)、先请熟客试吃再逐步推广(灰度发布)。这篇文章,就像一位老师傅带你走一遍 Flutter 应用从打包、签名、上架、审核,到灰度发布与热更新替代方案的完整后厨流程。
对于 AI 时代的移动端工程师来说,理解 Flutter 发布链路与热更新的合规边界,是保证产品迭代速度与稳定性、规避审核下架风险的核心工程能力。
目录
可视化演示
展示Flutter应用构建发布流程、热更新机制原理与Android/iOS双端热更新合规边界对比。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Flutter应用发布流程 Flutter构建:dart编译为原生代码,Android生成APK/AAB,iOS生成IPA 签名打包后提交应用商店审核,通过后灰度发布 flutter build → sign → store_review → gray_release
发布全流程:备菜、盖章与卫生检查
餐厅开业的前一天,后厨会经历四件事:备齐食材、把菜装进餐盒、给餐盒盖上出品章、请卫生局来检查。这四步做完,餐厅才敢开张迎客。Flutter 应用发布的过程,本质上就是这套流程的“数字化翻版”:Dart 代码是食材,APK 或 IPA 是餐盒,签名文件是出品章,应用商店审核则是卫生局的检查。
为什么这套流程不能省?因为商店和用户都需要“信任链”:签名证明这个安装包确实出自你的后厨,没有被任何人在运输途中调包;审核证明这道菜的基本品质和用料声明(权限声明)是可信的。没有签名和审核的应用,就像没有出品章和卫生许可的餐盒,移动系统会直接拒绝安装。
技术落地时,你通常会在项目根目录执行 flutter build apk --release 或 flutter build appbundle --release 来产出 Android 安装包,用 flutter build ipa 渲染 iOS 包。Android 在 android/app/build.gradle 中通过 signingConfigs 配置 keystore 签名;iOS 则需要一枚发布证书(Distribution Certificate)和对应的描述文件(Provisioning Profile)。与此同时,版本号也必须提前规划,商店正是靠它来识别“哪个包更新”。
从后厨到餐桌,每一步都有章可循。正因为有签名、审核和版本号的组合,用户才能在应用商店里放心地下载“这道菜”——这也是 Flutter 发布流程的第一课。
- 主版本 major产品的里程碑版本号,例如 2.x.x 中的 2,表示一次大的架构或体验变革。
- 次版本 minor功能迭代替号,例如 x.3.x 中的 3,表示新增了若干功能但保持了兼容。
- 补丁版本 patch用于修复 bug 的小版本号,例如 x.x.1 中的 1。
- 加权拼接把三个数字乘上不同权值后相加,使版本号天然满足“单调递增”的排序规则。
双端构建差异:两家分店的不同规矩
同样是你的招牌菜,Android 美食广场的店规是“用一次性餐盒就能进场”,而 iOS 步行街的店规是“必须用定制的精致托盘,且托盘上要贴物业发放的专属标签”。两家店的装修标准、施工时间、验收方式都各不相同——这就是 Flutter 在 Android 与 iOS 上构建配置差异的直观感受。
在 Android 侧,你需要面对三个 SDK 版本:minSdk(最低兼容安卓版本)、targetSdk(适配目标版本)、compileSdk(编译使用的 API 版本)。三者的合理区间决定了你的“菜”能在多老的“厨房”里被烹饪,又能用上多新的“厨具”。Android 还支持多渠道打包(productFlavors),相当于同一道菜可以贴上不同分店的标签。
而在 iOS 侧,流程就“死板”许多:你需要 .p12 发布证书标识开发者身份,.mobileprovision 描述文件绑定 App ID、设备列表与 entitlements 权限声明。构建产物需要通过 Xcode Archive 打包,再上传到 App Store Connect。版本号也比 Android 更讲究:展示给用户的是 CFBundleShortVersionString,内部递增的是 CFBundleVersion。
两家分店的规矩都写在墙上,掌勺的厨师必须两头都熟。Android 的灵活与 iOS 的严格并不是谁对谁错,而是两家“物业”对菜品的验收哲学不同——作为 Flutter 开发者,最好的做法就是同时把两本“店规”都背下来。
- minSdk 最低门槛低于该版本的系统直接拒绝安装,保护了“老厨房”的基本运行能力。
- targetSdk 目标版本声明开发时适配到哪个 Android 版本,系统据此决定兼容行为。
- compileSdk 编译版本编译时可用的最新 API 集合,通常大于 targetSdk 以获得新特性。
- 递进关系三者必须从低到高排列,否则会出现“编译过了却跑不起来”的隐患。
热更新概念:给上桌的菜“偷偷加料”
顾客已经在切牛排了,你突然发现酱汁不够咸。此时把牛排端回后厨重做,客人肯定不答应;但如果你趁他不注意,用小勺在盘子边缘补一点酱——这就相当于“热更新”:不重新上菜(不发版),也不打扰顾客(不经过商店),直接在小范围内调整味道。
CodePush 是 React Native 圈子里最著名的“加料小勺”。它的原理是让 App 内置一个 JavaScript 引擎,启动时主动向服务端询问:“有没有新的 JS 代码包?”如果有,就下载并替换本地的 bundle,下次启动即可生效。然而 Flutter 的菜式结构完全不同:Release 模式下,Dart 代码会被 AOT(预先编译)成原生机器码,App 里根本没有一个“解释器”能动态执行刚下载的新代码。这就好比你的菜是整块牛排,而不是可以随时更换配料的组合拼盘。
更关键的是平台态度。iOS 的 App Store 审核条款明确禁止“下载并执行代码”,Android 虽然相对宽松,但 Google Play 也在通过 Play Integrity API 等手段检测异常的代码注入。因此,“偷偷加料”这条路在 iOS 上几乎是封死的。
“偷加料”听着神不知鬼不觉,实际上既受制于后厨规则(平台审核),也受制于菜品结构(AOT 编译)。Flutter 这道菜,天生就不适合用 CodePush 那样的小勺去补味。
- 分子-成功安装数只有真正应用了新补丁的用户才算成功。
- 分母-收到补丁总数包括下载失败、安装失败的用户。
- 比值成功数除以总数,得到一个 0 到 1 的小数。
- 百分化乘以 100%,得到可读的成功率指标。
替代方案:盐罐子上桌与 DIY 菜品
既然不能偷偷改已经上桌的菜,聪明的餐厅老板会换一种思路:干脆把盐罐子放在桌上,让顾客自己调节咸淡。对应到 App 里,就是“远端配置”(Remote Config):把文案、颜色、功能开关、活动配置等可变参数抽离到服务端,客户端启动时拉取一份 JSON,功能表现随之改变。整个过程不涉及任何代码执行,自然不触碰审核红线。
更进一步,老板还可以把“配菜”设计成可替换的:图片、海报、动画素材、语言包这类静态资源,走 CDN 下发即可。Flutter 的 Image.network、rootBundle 加载的 JSON、Lottie 动画文件,都可以在不动代码的情况下完成视觉更新。这相当于换了桌上的装饰花,但菜还是那道菜。
再激进一些的团队会研究动态化框架,比如 Fair、MXFlutter 这类尝试在 Flutter 中引入脚本解释执行能力的方案。它们让一部分 Dart 逻辑可以被远端脚本驱动,但也正是“动态执行代码”这一点,让它们在 iOS 上的合规性充满风险——你可以把这道菜理解成“顾客自己 DIY”的半成品,美味但“危险”。
热更新受限并不可怕,真正可怕的是只知道一条路走到黑。把“不能动的菜”改成“能调节的盐罐子”和“可更换的装饰花”,才是 Flutter 团队最务实的动态化战略。
- 分子-可动态更新模块数能用配置或资源驱动更新的功能模块数量。
- 分母-功能模块总数App 所有功能模块的集合。
- 比值两者相除,得出动态化的“比例感”。
- 百分化转成百分比,方便团队设定目标与红线。
合规边界:卫生局的明文红线
卫生局的红线往往就贴在厨房门口:已经端上桌的菜不许回锅,私藏过期食材会被吊销执照。对应到 App 领域,iOS 的“卫生局”就是 App Store 审核团队,而那条最著名的红线,是审核指南第 3.3.2 条:应用不得下载、注入或执行任何改变其自身功能的代码。
什么行为容易踩线?用 JavaScriptCore 动态执行远程 JS、从远端下载 zip 包解压后执行其中的代码、集成 JSPatch 这类热修框架,以及将动态化框架直接用于 iOS 生产环境——这些都属于“下载并执行代码”的高风险动作。一旦被苹果发现,轻则拒绝上架,重则下架并封禁开发者账号。
Android 的“卫生标准”则宽松一些:Google Play 允许通过 WebView 或 JS 引擎更新部分逻辑,但同样不支持随意下载原生二进制代码执行;国内各安卓应用市场(华为、小米、OPPO、vivo 等)也都有各自的安全扫描与隐私合规检测。别忘了,热更新如果悄悄追加了权限申请或数据采集行为,还涉及工信部对移动应用的隐私合规监管——这就像卫生局还会抽查后厨的食材进货单,你换供应商(SDK)的每一笔记录都要有据可查。
红线划得清清楚楚,聪明的老板永远在线内跳舞。把动态代码的冲动收敛为“资源更新 + 配置下发”,是 Flutter 双端发布时代最基本的生存智慧。
- 高风险档下载二进制代码并立即执行,是 iOS 审核绝对的红线。
- 中风险档解释执行远程脚本,仍在审核人员的重点关注范围内。
- 低风险档仅更新资源与配置,是最稳妥的“线下操作”。
- 分段映射规则把“更新方式”映射到对应风险等级,一目了然。
版本管理策略:菜单的修订记录
餐厅的菜单每隔一段时间就要换新版,但每一版菜单都会印上版本号和日期。客人点菜时会说“我要今年春季那本菜单的第二道菜”——如果某道菜已经下架(不再兼容),服务员就得收回旧菜单,引导客人看新菜单。这就是版本管理和强制升级。
在 Flutter 工程中,pubspec.yaml 里声明的 version: 2.1.0+3 同时包含了展示版本(2.1.0)和构建号(3)。Android 把展示版本叫 versionName,把构建号叫 versionCode;iOS 则对应 CFBundleShortVersionString 和 CFBundleVersion。构建号只要求单调递增,展示版本则遵循“语义化版本”:主版本号破坏性变更,次版本号新增功能,补丁号修复问题。
当你发现旧版本有严重漏洞或服务端已不再兼容时,就需要“强制升级策略”:服务端维护一个最低合理版本号,客户端启动时把自己当前的版本号发过去比对,若低于最低版本,就弹出不可跳过的升级弹窗。这个策略同样要设计回滚路径:远端配置的开关可以随时关闭,数据库的 schema 迁移也要预留降级方案——就像菜单可以换,但顾客手里的菜不能突然消失。
菜单的每一次修订都值得被记录,版本号的每一次递增都应该被尊重。当你的 App 用户量过亿时,“版本管理”就是你的后厨台账——它决定了出问题时你能多快找到是哪个版本、哪次改动闯的祸。
- 当前版本 V_当前用户正在使用的 App 版本号。
- 最低合理版本 V_最低合理服务端能够安全兼容的最低版本。
- 大小比较当前版本低于门槛则强制升级,否则仅推荐升级。
- 分段策略由比较结果确定最终拿给用户的交互方案。
灰度发布:先让熟客试吃
新菜研发完毕,老板不会直接写进菜单,而是先请几位熟客免费试吃:他们口味敏锐,反馈最快,而且就算吃坏了胃也不会四处投诉。试吃通过后,再扩大到常客群;最后才面向所有顾客正式推出。如果试吃阶段有人拉肚子(崩溃),这道菜会立刻撤下。这就是灰度发布的完整隐喻。
实现灰度发布的关键在于“稳定的用户分组”。工程上一般用一致性哈希:对用户的唯一标识(如 user_id)做哈希,映射到一个固定区间,再根据当前灰度比例决定该区间内的用户是否启用新逻辑。这样同一个用户下次进入时依然属于同一组,不会出现“上午是新版、下午是旧版”的割裂体验。
放量节奏通常采用阶梯式:5% 内部员工 → 20% 种子用户 → 50% 活跃用户 → 100% 全量。每个阶段停留 24 到 48 小时,期间监控三类指标:崩溃率(App 是否稳定)、安装成功率(用户是否有升级障碍)、核心链路性能(启动耗时、帧率)。当崩溃率超过阈值(例如 0.1%),系统应自动把灰度比例降为 0%——就像试吃出现食物中毒,立刻停止发菜并回收编号。
熟客试吃通过后,新菜才会写上正式菜单——这也是“发布”的最后一公里。灰度发布不是发布流程的附加项,而是一个负责任后厨出菜的必经环节。
- 分子-已升级用户数当前被分流到新版本的活跃用户数量。
- 分母-目标用户总数本次发布希望覆盖的用户整体。
- 覆盖比例两者相除得到一个小数,表示新版本的覆盖面。
- 百分化乘以 100%,得到我们常说的“灰度百分比”。
AI 应用场景
移动端 CI/CD 工程师的自动灰度发布
阶段:发布与运维阶段(DevOps)
解决的问题:传统发版是一次性全量推送,新版本出现崩溃时只能紧急回滚或重新提交审核,影响面大、恢复慢。AI 化发布平台可在灰度阶段实时收集崩溃数据,自动决策放量或回滚。
具体案例:某工具类 App 采用远端配置中心管理灰度比例,新版本先下发 5% 内测用户,观察 24 小时崩溃率低于阈值后自动放量到 20%、50%、100%。第 2 天发现某 Android 机型崩溃率飙升,平台在 5 分钟内将灰度比例降为 0%,同时把故障信息推送给开发群。
Flutter 架构师的合规热更新替代方案
阶段:方案设计与架构评审
解决的问题:产品经理要求每周调整首页 Banner、文案和活动配置,原生发版根本无法满足节奏。AI 辅助方案利用远端配置与静态资源 CDN 实现“不发版改 UI”。
具体案例:某内容社区 App 在 Flutter 中集成了 Firebase Remote Config,将推荐位开关、Banner 图片 URL、活动运营文案全部配置化。运营人员在后台修改 JSON,App 冷启动后自动拉取,10 分钟内全局生效,全程不碰代码,iOS 审核无感知。
移动端质量工程师的迭代质量监控
阶段:质量保障与线上监控
解决的问题:双端发布后版本碎片化严重,用户停留在不同旧版,问题排查困难。通过 AI 化的版本归属管理与崩溃聚合分析,可以快速定位某个版本独有的问题。
具体案例:某出行 App 在服务端维护最低支持版本,并结合埋点上报每个用户端的 versionCode。灰度发布期间,质量平台自动把崩溃按“版本号×机型×系统版本”做聚合,一旦发现新版特定组合下的崩溃率环比上升 3 倍以上,立即标记为阻断缺陷并触发灰度暂停。
Python 演示
"""模拟 Flutter 应用的灰度发布与回滚决策""" import hashlibimport random def user_bucket(uid: int, base: int = 10000) -> int: """用一致性哈希将用户映射到 [0, base) 的桶中,保证稳定性""" digest = hashlib.sha256(str(uid).encode("utf-8")).hexdigest() return int(digest[:8], 16) % base def in_gray(uid: int, ratio: float, base: int = 10000) -> bool: """灰度命中判定:桶号小于 ratio * base 即命中""" return user_bucket(uid, base) < ratio * base def main() -> None: users = list(range(1, 10001)) ratios = [0.05, 0.20, 0.50, 1.00] crash_threshold = 0.01 for ratio in ratios: upgraded, crash = 0, 0 for uid in users: if not in_gray(uid, ratio): continue upgraded += 1 # 模拟升级:2% 的机器升级后立即崩溃 if random.random() < 0.02: crash += 1 crash_rate = crash / upgraded decision = "继续放量" if crash_rate < crash_threshold else "暂停回滚" print("灰度比例 %5.0f%% | 下发 %5d 人 | 崩溃率 %.2f%% | %s" % (ratio * 100, upgraded, crash_rate * 100, decision)) if __name__ == "__main__": random.seed(42) main()
def user_bucket(uid: int, base: int = 10000) -> int:把用户 ID 映射到固定桶位,保证同一用户每次都会落到同一实验组。hashlib.sha256(str(uid).encode("utf-8")).hexdigest()对用户 ID 做 SHA-256 哈希,让分组结果足够均匀且稳定。int(digest[:8], 16) % base取哈希前 8 位转整数后取模,得到 0 到 9999 的桶号。def in_gray(uid: int, ratio: float, base: int = 10000) -> bool:桶号小于ratio * base时命中灰度,等价于“随机但不抖动”的分流。ratios = [0.05, 0.20, 0.50, 1.00]灰度放量的四个里程碑,对应熟客试吃、常客试吃、全场半开、全量开放。crash_threshold = 0.01崩溃率阈值,超过 1% 就判定新版本不安全。if not in_gray(uid, ratio): continue只对灰度组内的用户“下发新版”,其他用户继续用旧版。crash_rate = crash / upgraded计算当前批次的真实崩溃率,作为回滚决策依据。decision = "继续放量" if crash_rate < crash_threshold else "暂停回滚"阈值比较后自动输出决策结果,这正是灰度平台的核心逻辑。
常见误区
误区网上经常看到“Flutter 热更新方案”的帖子,我在生产环境集成一个插件,上线后出了 bug 直接热修不就行了?
正解Flutter 的 Release 模式是 AOT 编译,Dart 代码已经变成机器码,没有内置解释器去执行刚下载的新代码。即使是第三方插件,也大多停留在实验或部分模块可动态化的水平。更关键的是,iOS 上任何“下载并执行代码”的行为都违反 App Store 审核 Guideline 3.3.2。能真正稳定热更的,通常是资源和配置,而不是业务代码本身。
为何常见:React Native 和早期 JSPatch 的成功案例让很多人默认“所有跨端框架都能热更”,而忽略了 Flutter 在架构上根本没有可注入代码的 JS 引擎。
误区我的 App 只上国内安卓应用市场,不碰 App Store,那我是不是想怎么热更就怎么热更?
正解安卓渠道相对宽松,但 Google Play 有 Play Integrity API 检测代码注入,国内各应用市场也都有自己的审查机制和安全扫描。更重要的是,工信部对移动应用 SDK 与隐私行为的监管越来越严,如果热更新偷偷改变了权限声明或数据采集行为,一样会被通报甚至下架。合规不是“iOS 的专利”,而是所有移动应用的基本门槛。
为何常见:人们容易把“iOS 限制热更”等同于“合规的全部”,从而忽视了安卓市场和国内监管的双重约束。
误区灰度发布很简单,从用户池里随机抽 10% 的人发新版,测完没问题再全量推,不就行了?
正解随机抽取会让同一个用户在不同时间落入不同分组,导致实验数据混乱、无法在同一批用户身上观察前后变化,也无法平滑地增量放量。工程上的正确做法是使用一致性哈希对 user_id 做分组,让用户归属稳定,再按桶号逐步放开比例。这样每个用户从灰度到全量都始终处于同一版本逻辑下。
为何常见:把“随机抽样”和“灰度实验分组”混为一谈,忽略了灰度发布的核心诉求是“稳定分组+渐进放量+可回退”。
误区既然图片和 JSON 可以远程更新,那把可执行逻辑加密成一段字符串塞在 JSON 里下发,不就能绕过 iOS 审核了吗?
正解审核不仅看文件格式,更看行为。苹果的安全团队会结合抓包、用户举报、动态分析来判断是否存在“代码注入”。把可执行逻辑混淆进资源文件,依然属于“动态下发代码”,一旦被认定,后果是下架和开发者账号封禁,远比正常发版慢得多。合规的关键在于内容是否可执行,而不只是文件后缀。
为何常见:钻审核漏洞的思维在行业内长期存在,但随着苹果审核策略收紧,“乔装打扮”的路只会越走越窄。
误区旧版本有严重 bug,我只要把 versionCode 从 1 改成 2,重新传上去,用户不就都自动升级了吗?
正解versionCode 只告诉商店“新包比旧包新”,商店不会自动把用户都推到新版。要真正实现强制升级,你需要服务端维护“最低支持版本”,客户端启动时上报当前版本号,服务端比对后下发“强制升级”还是“推荐升级”的指令;客户端还要处理弹窗、下载、安装、失败补偿等全链路问题。版本号只是链路中的一小环。
为何常见:把“发布 → 升级”这条长链路简化成“改个版本号就完事”,忽略了服务端校验、客户端弹窗策略和商店分发机制之间的配合。
面试题
Q1. Flutter 项目中,用于配置 Android 发布签名(signingConfigs)的文件是?
- A.build.gradle 中配置 signingConfigs← 正确答案
- B.AndroidManifest.xml 中配置包名
- C.pubspec.yaml 中配置依赖
- D.Info.plist 中配置权限
答案:A。Android 的签名配置在 android/app/build.gradle 的 signingConfigs 块中完成。AndroidManifest.xml 负责包名与组件声明,pubspec.yaml 管理 Dart 依赖,Info.plist 是 iOS 的配置文件。
Q2. iOS 应用上架 App Store 前,需要哪一组文件来标识开发者身份?
- A.keystore 密钥库文件
- B.发布证书与描述文件← 正确答案
- C.DEX 字节码文件
- D.混淆映射表文件
答案:B。iOS 使用发布证书(Distribution Certificate)标识开发者身份,使用描述文件(Provisioning Profile)绑定 App ID、设备与权限。keystore 是 Android 的签名概念,DEX 是 Android 的字节码格式。
Q3. 为什么 Flutter 应用无法直接使用 CodePush 完成热更新?
- A.因为 Flutter 不支持网络请求
- B.因为 Flutter 代码编译为原生机器码← 正确答案
- C.因为 Google 禁止所有代码更新
- D.因为 Flutter 只能通过命令行构建
答案:B。CodePush 依赖内置 JS 解释器动态加载 JS bundle,而 Flutter Release 模式采用 AOT 将 Dart 代码编译为原生机器码,没有可执行远程代码的解释器。因此不是网络或政策原因,而是架构层面决定了这条路走不通。
Q4. 以下哪些行为属于 iOS 审核明确禁止的“热更新”高风险行为?
- A.用 JavaScriptCore 动态执行远程代码← 正确答案
- B.使用 TestFlight 进行内部测试分发
- C.从远端下载 zip 包并解压执行代码← 正确答案
- D.通过推送通知引导用户前往商店升级
答案:AC。A 和 C 都属于“下载并执行代码”,违反 App Store 审核 Guideline 3.3.2。B 的 TestFlight 是苹果官方的测试分发工具,完全合规;D 通过推送引导用户去商店升级,正是苹果推荐的标准升级路径。
Q5. 灰度发布中,为保证同一用户在一次迭代内始终处于同一实验组,应采用哪种分组方式?
- A.用随机数生成临时分组
- B.对用户 ID 做一致性哈希分组← 正确答案
- C.按用户注册时间先后顺序分组
- D.每次进入应用时重新掷骰子
答案:B。一致性哈希对 user_id 计算后映射到固定桶位,保证用户归属稳定,便于观察同一用户在新旧版本下的行为差异。随机数和掷骰子会让用户在不同次访问中落入不同分组,注册时间分组则可能导致分组严重不均衡。
Q6. 关于 Flutter 热更新的替代方案,以下哪些说法是正确的?
- A.静态资源可更新图片、文案和配置← 正确答案
- B.远端配置开关可切换功能逻辑← 正确答案
- C.动态化框架 iOS 上完全绕开审核
- D.第三方热更方案在 iOS 上已完全成熟
答案:AB。A 和 B 是合规且主流的替代方案:资源走 CDN,配置走远端下发。C 中“完全绕开审核”不现实,动态化框架在 iOS 上仍面临合规审查;D 中第三方热更方案在 iOS 上因为政策限制远未成熟,因此两项都不对。
Q7. Android 中 versionCode 与 versionName 的核心区别是什么?
- A.versionCode 只参与应用签名校验
- B.versionCode 是机器识别的递增整数,versionName 是可读的版本名称← 正确答案
- C.versionCode 与 versionName 含义完全相同
- D.versionName 必须小于 versionCode 的数值
答案:B。versionCode 是给系统识别新旧用的单调递增整数,versionName 是展示给用户看的字符串(如 2.1.0)。两者用途完全不同,也没有“必须谁大谁小”的关系,更不是同一个属性的两种叫法。
Q8. 以下哪些做法属于规范的动态发布与版本升级策略?
- A.远端配置下发功能开关← 正确答案
- B.更换网络图片 URL 更新素材← 正确答案
- C.iOS 解压 zip 包动态执行代码
- D.引导用户去应用商店升级← 正确答案
答案:ABD。远端配置和网络图片 URL 是合规的动态内容更新方式,不涉及执行代码。引导用户去应用商店升级是苹果推荐的标准路径。C 在 iOS 上解压 zip 并动态执行代码属于严重违规,会触发 3.3.2 审核条款。
Q9. 为什么 Google Play 强制要求新应用使用 AAB 格式发布?
- A.AAB 只是比 APK 更安全的压缩格式
- B.AAB 让商店按设备动态生成最优 APK← 正确答案
- C.AAB 主要为了绕过商店的审核
- D.AAB 是 iOS 上架要求的格式
答案:B。AAB(Android App Bundle)包含应用的编译代码和资源,由 Google Play 根据目标设备的屏幕密度、CPU 架构等动态生成最精简的 APK,从而显著减小下载体积。它不是 APK 的简单压缩,也与绕过审核无关,更和 iOS 没有关系。
Q10. 旧版本 Flutter 客户端的“升级地址”写死且已失效,用户升级受阻,以下哪种方案最实际?
- A.推送通知携带应用商店新版本链接← 正确答案
- B.给旧版本注入补丁代码修改地址
- C.在应用商店后台修改历史版本逻辑
- D.调用 iOS 私有 API 替换旧版本资源
答案:A。旧版本如果本身没有热更通道,就无法用补丁修改升级地址;应用商店后台不能修改已发布版本的二进制逻辑;iOS 私有 API 不可用且有封号风险。推送一条带有商店链接的通知,把用户引导到新版本页面,是最合规、最实际的补救手段。这也提醒我们:升级入口必须走服务端动态下发,不能写死在客户端。