OTA 开发态会话修复总览(冷启动 / 二次进入 / Metro 切换)
Date: 2026-07-09
Status: Fixed
Scope: iOS bridgeless OTA、FeatureHost、split bundle 构建与缓存、bundle-server 发布
Release: order / promo v0.0.7 已 upload(含新 split 构建策略)
细分记录见 docs/fixes/README.md(问题簇索引 +
stories/面试叙事)。面试口述见 docs/interview/ota-reentry-case-study.md。
问题簇一览
本次调试实际包含 三个独立根因,症状相似(白屏 / unknown module / 加载失败),需按日志区分:
| # | 症状 | 典型日志 | 根因 |
|---|---|---|---|
| A | OTA 二次进入或 Metro→OTA 失败 | otaReentry=false,无 syncFromCache;或 instant re-entry 后 Metro 再 OTA 白屏 | bridgeless 下 segment 会 re-eval,但 registerFeature 不重跑;Metro 清了 cache/registry |
| B | 冷启动首次 OTA 崩溃 | Requiring unknown module "745032085",seg-1.js 渲染阶段 | 旧 split 把 use-latest-callback 排除在主包,Metro lazy 主包未注册该 module id |
| C | 已 download 仍用旧包 | staged pending order@0.0.6,path 仍指向旧 0.0.6.jsbundle | 同版本 hash 漂移:needsUpdate() 跳过;metadata 与磁盘/服务端不一致 |
易误判:版本号 UI 显示 v0.0.6 / v0.0.6 一致,不代表 bundle 字节相同;不是「本地文件被删」。
A. OTA 二次进入 & Metro ↔ OTA 切换
现象
- OTA → OTA 二次:曾出现
unknown module或「远程 bundle 不可用」 - Metro → OTA:日志
afterMetro=false,otaReentry=false,registry 未从 cache 恢复
根因
clearOtaComponentCache()在 Metro 入口销毁__OTA_COMPONENT_CACHE__tryInstantOtaReentrycache-only 分支跳过 native load,registry 空时无法恢复- bridgeless:
registerSegment总会 re-eval segment;registerFeature不会因 entryisInitialized重跑
方案
- Metro 只清 registration,保留 OTA component cache
- 新增
__METRO_LOADED_THIS_SESSION__→afterMetro+restoreRegistry = otaReentry || afterMetro - segment eval 后立即
syncFromCache;requireRegistration=false tryInstantOtaReentry仅 live OTA registry(且未经 Metro);禁止 cache-only instant
关键文件
otaSessionLoad.ts— OTA/Metro 会话标记useFeatureHost.ts— Metro 路径不再clearOtaComponentCachebundleLoader.ts—afterMetro/restoreRegistryotaFeatureReuse.ts— 收紧 instant 条件featureReload.ts— reload 清空双会话标记
B. 冷启动 unknown module 745032085
现象
重新 build 原生后,未经 Metro Remote,直接 OTA 首次进入即崩溃。
根因
- module
745032085=use-latest-callback/esm.mjs,React Navigation 依赖 - 旧
build-bundles.js:凡在主包 graph 的模块一律从 split 排除 - split 运行时
_r(745032085)期望主包已注册;DEV Metrolazy=true主包未必加载 - segment eval 成功,渲染 OrderNavigator 时才报错
方案
- split 排除策略:只排除 react/rn/metro core +「主包独有、split 不需要」的模块;split graph 共享依赖打进 split
- build audit:
[split-audit]外部主包依赖从 ~30 降至 ~12(仅 react core) - DEV preload:
otaSplitHostPreload.ts+index.js启动时 warm(兼容旧 bundle) - 发布:必须
build:bundles+ upload;仅本地 dist 不够
关键文件
scripts/build-bundles.js—collectSplitModulePaths、新shouldExcludeFromSplitModuleotaSplitHostPreload.ts—use-latest-callback/esm.mjs等bundleLoader.ts— load 前 preloadbundleUpdater.ts—unknown module友好文案
构建对比(order split)
| 修复前 | 修复后 | |
|---|---|---|
745032085 在 split 内 | ❌ | ✅ |
| 体积 | ~280KB | ~2MB |
| 外部主包依赖 | 30 | 12 |
C. 同版本 hash 漂移 & 缓存 bootstrap
现象
[OTA] staged pending order@0.0.6 (pre-entry download) 后仍加载旧 Documents/.../0.0.6.jsbundle。
根因
needsUpdate()同 semver 返回false(hash-only 变更交给 poll/pending)- pending 已下载但未 promote → active,首屏仍用旧 active
- 服务端曾长期是旧 hash(282KB),与本地 metadata 一致 → 不会触发更新
方案
ensureFeatureCached:比对 磁盘文件 hash 与 manifest(不只 metadata)- active 文件或 metadata 与 remote 不一致 → 立即
applyPendingFeature或checkAndUpdateFeature - 发布流程:
upload-bundle.sh;版本 bump(0.0.7)强制客户端拉新
关键文件
bundleUpdater.ts—hashBundleFileAtPath、activeBundleFileMatchesRemote、bootstrap apply pending
杂项修复
| 问题 | 修复 |
|---|---|
useOtaUpdatePoller doesn't exist | 恢复 useFeatureHost.ts 中误删的 import |
otaSplitHostPreload 动态 require() | 改静态 require,避免 Metro bundle build 失败 |
发布记录(2026-07-09)
# 构建
cd rn_app && npm run build:bundles:dev # package.json → 0.0.7
# 上传
cd bundle-server
./scripts/upload-bundle.sh order 0.0.7 dist/bundles/ota_order.0.0.7.ios.jsbundle
./scripts/upload-bundle.sh promo 0.0.7 dist/bundles/ota_promo.0.0.7.ios.jsbundle
| Feature | Version | Hash (prefix) |
|---|---|---|
| order | 0.0.7 | sha256:0cbd8ee5… |
| promo | 0.0.7 | sha256:72c1f83d… |
验证清单
自动化
-
npm test— 29 passed -
npm run verify:ota-scope -
npm run build:bundles:dev— split-audit 通过
手动(建议顺序)
- 冷启动 OTA:Reload Metro → 直接进 OTA Order → 下载/应用 v0.0.7 → 页面正常
- OTA → OTA 二次:返回再进 → 可有
instant re-entry (live registry) - Metro → OTA:切 Metro Remote → 再 OTA → 日志
afterMetro=true,页面正常 - 全路径:Metro → OTA → OTA 二次 → Metro → OTA
仍失败时
- 模拟器 Delete App 重装(清
Documents/rn-bundles/) - 确认 manifest hash 与工程仓
bundle-server/dist/bundles/build-manifest.json一致 - DEV 可选:
USE_METRO_BUNDLES=true避免静态 split 与 Metro 漂移
后续建议
LeakCheckerSurface 泄漏(反复 mount)单独排查- 每次
Metro --reset-cache或改 split 归属后:rebuild + upload - 面试材料以 ota-reentry-case-study.md 与 stories/01-ota-reentry-registry-lifecycle.md 为准;本总览供工程落地检索
相关文档
| 文档 | 内容 |
|---|---|
| README.md | 修复记录总索引 |
| stories/01-ota-reentry-registry-lifecycle.md | 问题簇 A 面试叙事 |
| stories/02-split-module-graph-and-shared-bundle.md | 问题簇 B 面试叙事 |
| ota-metro-switch-registry-restore.md | 问题簇 A — Metro 切换 |
| ota-split-shared-deps-unknown-module.md | 问题簇 B + C |
| ota-second-entry-load-register-fix.md | re-entry 最终 fix |
| archive/ | 排查笔记 + 已废弃中间方案 |