diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index 9634ee0..5540745 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -359,16 +359,16 @@ Contract Design Plan(本文档) ##### 2.B Native→H5 反向 callback -- [ ] **2.8** `Source/Device/DeviceInfo.swift`:6 字段 snapshot - - `getphoneInfo` 收到调用后 → `bridge.call("getphoneinfo", data: ...)` 反向 -- [ ] **2.9** `Source/Device/BatteryMonitor.swift`:监听 `UIDeviceBatteryLevelDidChangeNotification` - - 触发 → `bridge.call("getBattery", data: .string("%.2f"))` -- [ ] **2.10** `Source/Device/NetworkMonitor.swift`:`NWPathMonitor` 包装 - - 状态变化 → `bridge.call("getnetwork", data: .string("1"/"2"/"3"))` -- [ ] **2.11** `Source/Device/AppLifecycleObserver.swift`:监听 SceneDelegate 前后台通知 - - → `bridge.call("appservice", data: .string("1"=后台 / "2"=前台))` -- [ ] **2.12** `Source/WebView/ShakeDetector.swift`:在 `WebContainerViewController` 重写 `motionEnded:` - - → `bridge.call("shakeEnd", data: nil)`(仅 `canshake=YES` 时触发) +- [x] **2.8** `Source/Bridge/Handlers/DeviceInfoHandler.swift`:DeviceInfoSnapshot 6 字段(Phase 2.5 一并完成) + - `getphoneInfo` 收到 → `bridge.call("getphoneinfo", data: 表 A 6 字段)` 反向 +- [x] **2.9** `Source/Resource/BatteryMonitor.swift`:监听 `UIDevice.batteryLevelDidChangeNotification` + - 触发 → 重写 `app_battery.js` + `bridge.call("getBattery", data: .string("%.2f", level))` +- [x] **2.10** `Source/Resource/NetworkMonitor.swift`:NWPathMonitor 包装(Phase 1 已建) + - 状态变化 → 重写 `app_network.js` + `bridge.call("getnetwork", data: .string("1"/"2"/"3"))` +- [x] **2.11** `Source/Resource/AppLifecycleObserver.swift`:监听 `UIApplication.didEnterBackgroundNotification` / `willEnterForegroundNotification` + - 后台 → `bridge.call("appservice", data: .string("1"))`;前台 → `"2"` +- [x] **2.12** ShakeDetector 集成在 `Source/Bridge/Handlers/ShakeHandler.swift`(Phase 2.3 一并完成) + - `WebContainerViewController.motionEnded(_:with:)` 转发 → `bridge.call("shakeEnd", data: nil)`(仅 `canShake=YES` 时触发) ##### 2.C 外部订阅生命周期 diff --git a/ylgamehall/Source/Resource/AppLifecycleObserver.swift b/ylgamehall/Source/Resource/AppLifecycleObserver.swift new file mode 100644 index 0000000..39ff0fb --- /dev/null +++ b/ylgamehall/Source/Resource/AppLifecycleObserver.swift @@ -0,0 +1,49 @@ +// +// AppLifecycleObserver.swift +// ylgamehall +// +// 监听 UIApplication 前/后台切换通知,提供 onBackground / onForeground 钩子。 +// +// 契约:docs/H5-Native-Contract.md §3.2 [4]appservice 反向 callback +// "1" = 进入后台 / "2" = 回到前台(命名错位沿用历史) +// Phase 2.11 +// + +import UIKit + +@MainActor +public final class AppLifecycleObserver { + + public static let shared = AppLifecycleObserver() + + public var onBackground: (@MainActor () -> Void)? + public var onForeground: (@MainActor () -> Void)? + + private var started = false + + public init() {} + + public func start() { + guard !started else { return } + started = true + let nc = NotificationCenter.default + nc.addObserver( + forName: UIApplication.didEnterBackgroundNotification, + object: nil, + queue: .main + ) { [weak self] _ in + MainActor.assumeIsolated { + self?.onBackground?() + } + } + nc.addObserver( + forName: UIApplication.willEnterForegroundNotification, + object: nil, + queue: .main + ) { [weak self] _ in + MainActor.assumeIsolated { + self?.onForeground?() + } + } + } +} diff --git a/ylgamehall/Source/Resource/BatteryMonitor.swift b/ylgamehall/Source/Resource/BatteryMonitor.swift new file mode 100644 index 0000000..591e021 --- /dev/null +++ b/ylgamehall/Source/Resource/BatteryMonitor.swift @@ -0,0 +1,49 @@ +// +// BatteryMonitor.swift +// ylgamehall +// +// UIDevice.batteryLevelDidChangeNotification 的最简 wrap: +// 提供 currentLevel 同步快照 + onChange 主线程回调。 +// +// 契约:docs/H5-Native-Contract.md §3.2 [2]getBattery 反向 callback +// Phase 2.9 +// + +import UIKit + +@MainActor +public final class BatteryMonitor { + + public static let shared = BatteryMonitor() + + /// 当前电量 0.0…1.0;模拟器无电池硬件返回 -1,BatteryMonitor.start() 后兜底为 0 + public private(set) var currentLevel: Float = 0 + + /// 电量变化回调(主线程触发) + public var onChange: (@MainActor (Float) -> Void)? + + private var started = false + + public init() {} + + public func start() { + guard !started else { return } + started = true + UIDevice.current.isBatteryMonitoringEnabled = true + currentLevel = max(UIDevice.current.batteryLevel, 0) + NotificationCenter.default.addObserver( + forName: UIDevice.batteryLevelDidChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + // forName: + queue: .main 的 block 闭包在 main 队列触发; + // 用 MainActor.assumeIsolated 把它桥到 main actor 隔离 + MainActor.assumeIsolated { + guard let self else { return } + let level = max(UIDevice.current.batteryLevel, 0) + self.currentLevel = level + self.onChange?(level) + } + } + } +} diff --git a/ylgamehall/Source/WebView/WebContainerViewController.swift b/ylgamehall/Source/WebView/WebContainerViewController.swift index e97b6c0..00fc369 100644 --- a/ylgamehall/Source/WebView/WebContainerViewController.swift +++ b/ylgamehall/Source/WebView/WebContainerViewController.swift @@ -216,8 +216,10 @@ public final class WebContainerViewController: UIViewController { ) } - /// 写 4 个 app_*.js 文件,并挂钩 battery / network 变化的重写监听。 - /// 与原 msext NewRootVC.initJSdata + changebattery + changenetstate 等价。 + /// 写 4 个 app_*.js 文件,并挂钩 battery / network / appservice 三组钩子: + /// 每次变化时同时(1)重写对应 .js 文件(下次 reload 读到新值) + /// 和(2)`bridge.call` 反向推送给当前页 H5(§3.2 [2][3][4])。 + /// 与原 msext NewRootVC.initJSdata + changebattery + changenetstate + appservice 等价。 private func writeAppDataFiles(resolved: ResolvedVersion) throws { let writer = AppDataWriter( bundleConfig: .shared, @@ -225,28 +227,43 @@ public final class WebContainerViewController: UIViewController { containerRole: .lobby ) - // 启动 NetworkMonitor(幂等:内部 started 标记) + // 启动 3 个 monitor(幂等:内部 started 标记) NetworkMonitor.shared.start() - UIDevice.current.isBatteryMonitoringEnabled = true + BatteryMonitor.shared.start() + AppLifecycleObserver.shared.start() // 首次写入:4 个文件全部落盘 try writer.writeInitial() - try writer.writeBattery(UIDevice.current.batteryLevel) + try writer.writeBattery(BatteryMonitor.shared.currentLevel) try writer.writeNetwork(NetworkMonitor.shared.currentCode) - // 持续监听:battery / network 变化时重写对应文件。 - // 注意:H5 此次加载已读到首次写入的值,下次 reload 才会读到新值; - // 业务期间的实时变化由 §3.2 [2][3]反向 callback 推送(Phase 2 实现)。 - NotificationCenter.default.addObserver( - forName: UIDevice.batteryLevelDidChangeNotification, - object: nil, - queue: .main - ) { _ in - try? writer.writeBattery(UIDevice.current.batteryLevel) + let bridge = bridgedWebView.bridge + + // §3.2 [2] getBattery — UIDevice 电量变化 + // data: 字符串 "%.2f" 形式的小数电量 + BatteryMonitor.shared.onChange = { level in + try? writer.writeBattery(level) + bridge.call("getBattery", + data: .string(String(format: "%.2f", level)), + callback: nil) } + // §3.2 [3] getnetwork — NWPath 变化 + // data: 字符串 "1"=无网 / "2"=WiFi / "3"=蜂窝 NetworkMonitor.shared.onChange = { code in try? writer.writeNetwork(code) + bridge.call("getnetwork", + data: .string("\(code)"), + callback: nil) + } + + // §3.2 [4] appservice — App 前后台切换(命名错位沿用历史) + // data: "1"=进入后台 / "2"=回到前台 + AppLifecycleObserver.shared.onBackground = { + bridge.call("appservice", data: .string("1"), callback: nil) + } + AppLifecycleObserver.shared.onForeground = { + bridge.call("appservice", data: .string("2"), callback: nil) } }