Phase 1.16:SceneDelegate 接 WebContainer + 退役 RootViewController
- SceneDelegate.scene(_:willConnectTo:options:) 中 rootViewController
从 M0 占位 RootViewController() 改为 WebContainerViewController()
- 删除 ylgamehall/RootViewController.swift(M0 + Phase 1.2–1.13 烟雾测试
整体退役);所有 print 验证记录在 git log,留着是死代码
- File System Synchronized Group 自动适应文件删除,无需改 pbxproj
- BuildProject 通过
至此 Phase 1.16 完成;启动链路:
SceneDelegate → WebContainerViewController.viewDidLoad
→ setupBridgedWebView + setupSplash + registerBridgeHandlers
→ runBootPipeline (ensureReady → fetch → resolve → upgrade → loadFileURL)
→ didFinish 淡出 splash
Plan 进度已勾选(§5 Phase 1.16 + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
6c943e2924
commit
c95e80df9c
@@ -312,8 +312,10 @@ Contract Design Plan(本文档)
|
|||||||
- 入参忽略(参考 msext `RootVC.m:1816-1818`:`time` 参数实际不读取)
|
- 入参忽略(参考 msext `RootVC.m:1816-1818`:`time` 参数实际不读取)
|
||||||
- 副作用:`AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)`;responseCallback:`.string("vibrator")`
|
- 副作用:`AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)`;responseCallback:`.string("vibrator")`
|
||||||
- `WebContainerViewController.viewDidLoad` 新增 `registerBridgeHandlers()` 单一聚合点,Phase 2+ 在此追加。BuildProject 通过
|
- `WebContainerViewController.viewDidLoad` 新增 `registerBridgeHandlers()` 单一聚合点,Phase 2+ 在此追加。BuildProject 通过
|
||||||
- [ ] **1.16** `SceneDelegate` → `WebContainerViewController` 作 rootViewController
|
- [x] **1.16** `SceneDelegate` → `WebContainerViewController` 作 rootViewController
|
||||||
- 替换 M0 占位 `RootViewController`
|
- 替换 M0 占位 `RootViewController` + 删除 `ylgamehall/RootViewController.swift`(M0 烟雾测试堆栈整体退役,所有 1.2–1.13 print 验证记录在 git log,留着是死代码)
|
||||||
|
- File System Synchronized Group 自动适应文件删除,无需改 pbxproj
|
||||||
|
- BuildProject 通过
|
||||||
- [ ] **1.17** 把渠道目录里临时填入的 demo 值 + 一个 demo H5 跑通 / 真机验证完整 1.10-1.16 链路
|
- [ ] **1.17** 把渠道目录里临时填入的 demo 值 + 一个 demo H5 跑通 / 真机验证完整 1.10-1.16 链路
|
||||||
|
|
||||||
#### 验收
|
#### 验收
|
||||||
@@ -808,7 +810,7 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
|
|||||||
- [x] 1.14.d WebContainerViewController(SplashOverlay + 16:9 letterbox + 启动流水线 + 三种 alert)
|
- [x] 1.14.d WebContainerViewController(SplashOverlay + 16:9 letterbox + 启动流水线 + 三种 alert)
|
||||||
- [x] 1.14.e WebView didFinish 后 0.3s 淡出 splash + removeFromSuperview
|
- [x] 1.14.e WebView didFinish 后 0.3s 淡出 splash + removeFromSuperview
|
||||||
- [x] 1.15 VibratorHandler(单次振动,AudioServicesPlaySystemSound + responseCallback "vibrator")
|
- [x] 1.15 VibratorHandler(单次振动,AudioServicesPlaySystemSound + responseCallback "vibrator")
|
||||||
- [ ] 1.16 SceneDelegate → WebContainerViewController
|
- [x] 1.16 SceneDelegate → WebContainerViewController(删除 M0 占位 RootViewController.swift)
|
||||||
- [ ] 1.17 Demo H5 联调 + 真机验证升级路径
|
- [ ] 1.17 Demo H5 联调 + 真机验证升级路径
|
||||||
|
|
||||||
### Phase 2 大厅简单 handler + 反向 callback
|
### Phase 2 大厅简单 handler + 反向 callback
|
||||||
|
|||||||
@@ -1,202 +0,0 @@
|
|||||||
//
|
|
||||||
// RootViewController.swift
|
|
||||||
// ylgamehall
|
|
||||||
//
|
|
||||||
// M0 阶段临时根控制器,后续将被 Coordinator + LobbyViewController 替换。
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
final class RootViewController: UIViewController {
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
view.backgroundColor = .black
|
|
||||||
|
|
||||||
// Phase 1.2 烟雾测试:打印 ChannelConfig.plist 的 11 项渠道注入值
|
|
||||||
let cfg = BundleConfig.shared
|
|
||||||
print("""
|
|
||||||
[BundleConfig] 渠道注入读取结果:
|
|
||||||
qiniuDomain = \(cfg.qiniuDomain)
|
|
||||||
gameId = \(cfg.gameId)
|
|
||||||
channel = \(cfg.channel)
|
|
||||||
gameDir = \(cfg.gameDir)
|
|
||||||
gameStart = \(cfg.gameStart)
|
|
||||||
gameConfig = \(cfg.gameConfig)
|
|
||||||
market = \(cfg.market)
|
|
||||||
agent = \(cfg.agent)
|
|
||||||
appVersion = \(cfg.appVersion)
|
|
||||||
other = "\(cfg.other)"
|
|
||||||
appleConfig = "\(cfg.appleConfig)"
|
|
||||||
""")
|
|
||||||
|
|
||||||
// Phase 1.3 烟雾测试:打印 SandboxPaths 计算路径
|
|
||||||
print("""
|
|
||||||
[SandboxPaths] 路径计算结果:
|
|
||||||
caches = \(SandboxPaths.caches.path)
|
|
||||||
documents = \(SandboxPaths.documents.path)
|
|
||||||
bundle = \(SandboxPaths.bundle.path)
|
|
||||||
lobbyRoot = \(SandboxPaths.lobbyRoot.path)
|
|
||||||
lobbyIndex = \(SandboxPaths.lobbyIndex.path)
|
|
||||||
lobbyVersionXML = \(SandboxPaths.lobbyVersionXML.path)
|
|
||||||
""")
|
|
||||||
|
|
||||||
// Phase 1.5 烟雾测试:触发 ResourceUnzipper,确认 lobby index.html 存在
|
|
||||||
Task {
|
|
||||||
let start = Date()
|
|
||||||
do {
|
|
||||||
try await ResourceUnzipper.shared.ensureReady()
|
|
||||||
let elapsed = Date().timeIntervalSince(start)
|
|
||||||
let exists = FileManager.default.fileExists(atPath: SandboxPaths.lobbyIndex.path)
|
|
||||||
print("""
|
|
||||||
[ResourceUnzipper] ensureReady 完成(耗时 \(String(format: "%.3f", elapsed))s)
|
|
||||||
lobbyIndex 存在: \(exists)
|
|
||||||
""")
|
|
||||||
} catch {
|
|
||||||
print("[ResourceUnzipper] ERROR: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 1.8 烟雾测试:确认 WebViewJavascriptBridge.js 已入 Bundle 且可读
|
|
||||||
// 注:synchronized group 会把 ylgamehall/Resources/JS/ 子目录扁平化为 bundle 根,
|
|
||||||
// 所以无需 subdirectory 参数。
|
|
||||||
if let url = Bundle.main.url(forResource: "WebViewJavascriptBridge",
|
|
||||||
withExtension: "js"),
|
|
||||||
let content = try? String(contentsOf: url, encoding: .utf8) {
|
|
||||||
print("""
|
|
||||||
[WVJB.js] 已就绪
|
|
||||||
path = \(url.path)
|
|
||||||
bytes = \(content.utf8.count)
|
|
||||||
head = \(content.prefix(60).replacingOccurrences(of: "\n", with: " ⏎ "))…
|
|
||||||
""")
|
|
||||||
} else {
|
|
||||||
print("[WVJB.js] ERROR: 找不到 Bundle 内 JS/WebViewJavascriptBridge.js")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 1.10 烟雾测试:拉远端渠道配置 .txt(含 cache-busting + 短文本响应识别)
|
|
||||||
Task {
|
|
||||||
let start = Date()
|
|
||||||
do {
|
|
||||||
let outcome = try await RemoteConfigClient.shared.fetch()
|
|
||||||
let elapsed = Date().timeIntervalSince(start)
|
|
||||||
switch outcome {
|
|
||||||
case .parsed(let cfg):
|
|
||||||
print("""
|
|
||||||
[RemoteConfigClient] 拉取成功 .parsed(耗时 \(String(format: "%.3f", elapsed))s)
|
|
||||||
showmessage = \(cfg.showmessage ?? "<nil>")
|
|
||||||
agentlist count = \(cfg.agentlist?.count ?? 0)
|
|
||||||
""")
|
|
||||||
if let first = cfg.agentlist?.first {
|
|
||||||
print("""
|
|
||||||
agent[0].agentid = \(first.agentid ?? "<nil>")
|
|
||||||
agent[0].appVersion = \(first.appVersion ?? "<nil>")
|
|
||||||
agent[0].gameVersion = \(first.gameVersion ?? "<nil>")
|
|
||||||
agent[0].channellist = \(first.channellist?.count ?? 0) 个
|
|
||||||
agent[0].gamelist = \(first.gamelist?.count ?? 0) 个
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 1.11 烟雾测试:chulishengji 双子树合并
|
|
||||||
let bc = BundleConfig.shared
|
|
||||||
let resolved = VersionResolver.resolve(
|
|
||||||
config: cfg,
|
|
||||||
agentId: bc.agent,
|
|
||||||
channelId: bc.channel,
|
|
||||||
marketId: bc.market,
|
|
||||||
gameId: bc.gameId
|
|
||||||
)
|
|
||||||
print("""
|
|
||||||
[VersionResolver] chulishengji 双子树合并结果:
|
|
||||||
appVersion = \(resolved.appVersion)
|
|
||||||
appDownload = \(resolved.appDownload ?? "<nil>")
|
|
||||||
gameVersion = \(resolved.gameVersion)
|
|
||||||
gameZip = \(resolved.gameZip ?? "<nil>")
|
|
||||||
showmessage = "\(resolved.showmessage ?? "<nil>")"
|
|
||||||
""")
|
|
||||||
|
|
||||||
// Phase 1.12 烟雾测试:读本地版本号 + 与远端对比
|
|
||||||
let localApp = LocalVersionReader.localAppVersion
|
|
||||||
let localGame = LocalVersionReader.localGameVersion
|
|
||||||
let needIPAUpgrade = resolved.appVersion > localApp
|
|
||||||
let needZipUpgrade = resolved.gameVersion > localGame
|
|
||||||
print("""
|
|
||||||
[LocalVersionReader] 本地 vs 远端对比:
|
|
||||||
本地 appVersion = \(localApp)(来自 ChannelConfig.plist)
|
|
||||||
远端 appVersion = \(resolved.appVersion)
|
|
||||||
→ IPA 升级 = \(needIPAUpgrade ? "需要" : "不需要")
|
|
||||||
本地 gameVersion = \(localGame)(来自 version.xml /game/version@value)
|
|
||||||
远端 gameVersion = \(resolved.gameVersion)
|
|
||||||
→ H5 zip 升级 = \(needZipUpgrade ? "需要" : "不需要")
|
|
||||||
""")
|
|
||||||
|
|
||||||
// Phase 1.13 / 1.14.c 烟雾测试:触发 LobbyZipUpgrader(如果需要升级),
|
|
||||||
// 并打印 progress 回调(每 10% 报一次,避免刷屏)
|
|
||||||
let upgradeStart = Date()
|
|
||||||
do {
|
|
||||||
let lastReported = ProgressTicker()
|
|
||||||
let outcome = try await LobbyZipUpgrader.shared.upgradeIfNeeded(
|
|
||||||
resolved: resolved,
|
|
||||||
onProgress: { p in
|
|
||||||
lastReported.tickIfPassed(p) { reported in
|
|
||||||
print(String(format: "[LobbyZipUpgrader] 下载进度 %3d%%", Int(reported * 100)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
let elapsed = Date().timeIntervalSince(upgradeStart)
|
|
||||||
switch outcome {
|
|
||||||
case .noop:
|
|
||||||
print("""
|
|
||||||
[LobbyZipUpgrader] 无需升级,跳过(耗时 \(String(format: "%.3f", elapsed))s)
|
|
||||||
""")
|
|
||||||
case .upgraded(let from, let to):
|
|
||||||
let newLocalGame = LocalVersionReader.localGameVersion
|
|
||||||
print("""
|
|
||||||
[LobbyZipUpgrader] 升级完成 \(from) → \(to)(耗时 \(String(format: "%.3f", elapsed))s)
|
|
||||||
重新读 version.xml: localGameVersion = \(newLocalGame)
|
|
||||||
lobbyIndex 存在: \(FileManager.default.fileExists(atPath: SandboxPaths.lobbyIndex.path))
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("[LobbyZipUpgrader] ERROR: \(error)")
|
|
||||||
}
|
|
||||||
case .shortText(let msg):
|
|
||||||
print("""
|
|
||||||
[RemoteConfigClient] 拉到短文本响应(耗时 \(String(format: "%.3f", elapsed))s)
|
|
||||||
运营杀手锏:应当弹窗显示并停止重试
|
|
||||||
内容 = "\(msg)"
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("[RemoteConfigClient] ERROR: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
|
||||||
.landscape
|
|
||||||
}
|
|
||||||
|
|
||||||
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
|
|
||||||
.landscapeRight
|
|
||||||
}
|
|
||||||
|
|
||||||
override var prefersStatusBarHidden: Bool { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 仅供 Phase 1.14.c 烟雾测试使用:按 10% 阶梯节流 progress 打印,避免几百行刷屏。
|
|
||||||
/// `@unchecked Sendable`:状态仅一个 Int 阈值,用 NSLock 保护写入。
|
|
||||||
private final class ProgressTicker: @unchecked Sendable {
|
|
||||||
private let lock = NSLock()
|
|
||||||
private var lastBucket: Int = -1
|
|
||||||
|
|
||||||
func tickIfPassed(_ progress: Double, action: (Double) -> Void) {
|
|
||||||
let bucket = Int(progress * 10)
|
|
||||||
lock.lock()
|
|
||||||
let shouldFire = bucket > lastBucket
|
|
||||||
if shouldFire { lastBucket = bucket }
|
|
||||||
lock.unlock()
|
|
||||||
if shouldFire {
|
|
||||||
action(Double(bucket) / 10.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
options connectionOptions: UIScene.ConnectionOptions) {
|
options connectionOptions: UIScene.ConnectionOptions) {
|
||||||
guard let windowScene = scene as? UIWindowScene else { return }
|
guard let windowScene = scene as? UIWindowScene else { return }
|
||||||
let window = UIWindow(windowScene: windowScene)
|
let window = UIWindow(windowScene: windowScene)
|
||||||
window.rootViewController = RootViewController()
|
window.rootViewController = WebContainerViewController()
|
||||||
self.window = window
|
self.window = window
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user