Phase 2.C:ExternalSubscriptions 生命周期管理(防双发预备)
按 Design §2.4.2 把 battery / network / appservice 三组外部事件源的订阅
挂钩与 VC 生命周期配对:viewWillAppear → setup,viewWillDisappear →
teardown。配套约束:未来 Phase 6 子游戏 push 上来时大厅自动 teardown,
避免桥事件双发(栈深 ≤ 2,§2.4.3)。
WebContainerViewController 改造:
- 新增 setupExternalSubscriptions():
* 幂等 start 3 个 monitor(NetworkMonitor / BatteryMonitor /
AppLifecycleObserver)
* 设 BatteryMonitor.onChange → evaluateJavaScript("window.app_getbattery=N")
+ bridge.call("getBattery", "%.2f")
* 设 NetworkMonitor.onChange → evaluateJavaScript("window.app_getnetwork=N")
+ bridge.call("getnetwork", "1"/"2"/"3")
* 设 AppLifecycleObserver.onBackground/Foreground → bridge.call("appservice",
"1"/"2")
- 新增 teardownExternalSubscriptions():
* 解绑 onChange / on{Background,Foreground} = nil
* 释放对 bridge/webView 的 closure 引用避免子游戏 push 后双发
* monitor 不停(保持后台 currentXxx 状态新鲜),下次 setup 直接 resume
- viewWillAppear / viewWillDisappear override 调 setup / teardown 配对
- writeAppDataFiles 精简:只写首次值 + 启动 NetworkMonitor/BatteryMonitor
(需要 currentXxx 读首次值);AppLifecycleObserver 启动挪到 setup
至此 Phase 2 全部完成(2.A 8 个 handler + 2.B 5 项反向 callback + 2.C
生命周期管理)。
BuildProject 通过
Plan §5 Phase 2.13 勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4986f36967
commit
09f071e38f
@@ -75,11 +75,22 @@ public final class WebContainerViewController: UIViewController {
|
||||
|
||||
public override var canBecomeFirstResponder: Bool { true }
|
||||
|
||||
public override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
// 外部订阅生命周期:栈顶时挂钩,避免子游戏 push 上来后双发(§2.4.2)
|
||||
setupExternalSubscriptions()
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
becomeFirstResponder()
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
teardownExternalSubscriptions()
|
||||
}
|
||||
|
||||
public override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
||||
shakeHandler.handleMotionEnded(motion)
|
||||
}
|
||||
@@ -216,10 +227,10 @@ public final class WebContainerViewController: UIViewController {
|
||||
)
|
||||
}
|
||||
|
||||
/// 写 4 个 app_*.js 文件,并挂钩 battery / network / appservice 三组钩子:
|
||||
/// 每次变化时同时(1)重写对应 .js 文件(下次 reload 读到新值)
|
||||
/// 和(2)`bridge.call` 反向推送给当前页 H5(§3.2 [2][3][4])。
|
||||
/// 与原 msext NewRootVC.initJSdata + changebattery + changenetstate + appservice 等价。
|
||||
/// 写 4 个 app_*.js 文件首次值(loadFileURL 之前调一次,monitor 已启动)。
|
||||
/// 业务期变化的桥事件挂钩由 `setupExternalSubscriptions` / `teardownExternalSubscriptions`
|
||||
/// 配对管理(详见 Design §2.4.2 ExternalSubscriptions 生命周期),避免大厅 push
|
||||
/// 子游戏后两个 VC 都在监听导致 H5 收到双倍桥事件。
|
||||
private func writeAppDataFiles(resolved: ResolvedVersion) throws {
|
||||
let writer = AppDataWriter(
|
||||
bundleConfig: .shared,
|
||||
@@ -227,27 +238,40 @@ public final class WebContainerViewController: UIViewController {
|
||||
containerRole: .lobby
|
||||
)
|
||||
|
||||
// 启动 3 个 monitor(幂等:内部 started 标记)
|
||||
// 启动 2 个 currentXxx 类 monitor(幂等)以便读首次值;
|
||||
// AppLifecycleObserver 不需要 currentXxx,挪到 setup 启动
|
||||
NetworkMonitor.shared.start()
|
||||
BatteryMonitor.shared.start()
|
||||
AppLifecycleObserver.shared.start()
|
||||
|
||||
// 首次写入:4 个文件全部落盘
|
||||
try writer.writeInitial()
|
||||
try writer.writeBattery(BatteryMonitor.shared.currentLevel)
|
||||
try writer.writeNetwork(NetworkMonitor.shared.currentCode)
|
||||
}
|
||||
|
||||
// MARK: - 外部订阅生命周期(Phase 2.C / Design §2.4.2)
|
||||
//
|
||||
// 把 battery / network / appservice 三个外部事件源的订阅挂钩与 VC 生命周期
|
||||
// 配对:viewWillAppear → setup,viewWillDisappear → teardown。
|
||||
// 配套约束:未来 Phase 6 子游戏 push 上来时,大厅的 viewWillDisappear 会自动
|
||||
// 触发 teardown,避免桥事件双发(参 §2.4.3 栈深 ≤ 2)。
|
||||
//
|
||||
// 业务期变化时**不重写文件**,改为直接 evaluateJavaScript 重新赋值 window.app_*
|
||||
// 全局变量(H5 已加载完,<script src> 不会再 fetch;重新赋值后 H5 业务期同步读
|
||||
// app_xxx 永远拿到最新值)。同时触发 §3.2 反向 callback 推送给注册了 handler 的
|
||||
// H5 业务。详见 docs/H5-Native-Implementation-Design.md §7.5.2 / §7.5.4。
|
||||
|
||||
private func setupExternalSubscriptions() {
|
||||
// 幂等启动 3 个 monitor(writeAppDataFiles 可能已经启动了 NetworkMonitor /
|
||||
// BatteryMonitor;AppLifecycleObserver 在 setup 内首次启动)
|
||||
NetworkMonitor.shared.start()
|
||||
BatteryMonitor.shared.start()
|
||||
AppLifecycleObserver.shared.start()
|
||||
|
||||
let bridge = bridgedWebView.bridge
|
||||
let webView = bridgedWebView.webView
|
||||
|
||||
// 业务期变化时**不重写文件**,改为直接 evaluateJavaScript 重新赋值
|
||||
// window.app_* 全局变量(H5 已加载完,<script src> 不会再 fetch;
|
||||
// 重新赋值后 H5 业务期同步读 app_xxx 永远拿到最新值)。
|
||||
// 同时触发 §3.2 反向 callback 推送给注册了 handler 的 H5 业务。
|
||||
// 详见 docs/H5-Native-Implementation-Design.md §7.5.2 / §7.5.4。
|
||||
|
||||
// §3.2 [2] getBattery — UIDevice 电量变化
|
||||
// data: 字符串 "%.2f" 形式的小数电量
|
||||
BatteryMonitor.shared.onChange = { level in
|
||||
let value = String(format: "%.2f", level)
|
||||
Task { @MainActor in
|
||||
@@ -256,8 +280,7 @@ public final class WebContainerViewController: UIViewController {
|
||||
bridge.call("getBattery", data: .string(value), callback: nil)
|
||||
}
|
||||
|
||||
// §3.2 [3] getnetwork — NWPath 变化
|
||||
// data: 字符串 "1"=无网 / "2"=WiFi / "3"=蜂窝
|
||||
// §3.2 [3] getnetwork — NWPath 变化(1 无网 / 2 WiFi / 3 蜂窝)
|
||||
NetworkMonitor.shared.onChange = { code in
|
||||
Task { @MainActor in
|
||||
_ = try? await webView.evaluateJavaScript("window.app_getnetwork=\(code);")
|
||||
@@ -266,8 +289,6 @@ public final class WebContainerViewController: UIViewController {
|
||||
}
|
||||
|
||||
// §3.2 [4] appservice — App 前后台切换(命名错位沿用历史)
|
||||
// data: "1"=进入后台 / "2"=回到前台
|
||||
// appservice 无对应 app_* 全局变量,只走 bridge.call 反向通知
|
||||
AppLifecycleObserver.shared.onBackground = {
|
||||
bridge.call("appservice", data: .string("1"), callback: nil)
|
||||
}
|
||||
@@ -276,6 +297,18 @@ public final class WebContainerViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func teardownExternalSubscriptions() {
|
||||
// 仅解绑 onChange / on{Background,Foreground} 回调,monitor 继续在
|
||||
// 后台运行(保持 currentXxx 状态新鲜),下次 setup 时直接 resume。
|
||||
// 单例 monitor 全局只有一个 closure 引用,子游戏 setup 时会覆盖;
|
||||
// 大厅 viewWillDisappear 时 teardown 释放对当前 bridge/webView 的引用
|
||||
// 避免双发。
|
||||
BatteryMonitor.shared.onChange = nil
|
||||
NetworkMonitor.shared.onChange = nil
|
||||
AppLifecycleObserver.shared.onBackground = nil
|
||||
AppLifecycleObserver.shared.onForeground = nil
|
||||
}
|
||||
|
||||
// MARK: - Alerts (terminal / retry states)
|
||||
|
||||
private var appDisplayName: String {
|
||||
|
||||
Reference in New Issue
Block a user