diff --git a/CLAUDE.md b/CLAUDE.md index fc7b9e1..687b8f2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -231,7 +231,7 @@ H5 → Native 时 data 字典中的 key,以及 Native → H5 callback payload - `sharelogin` 回包中 `Province` 是**大写 P**(不是 `province`) - `OpenurlTitleData` 入参中 `"title "` 末尾**有一个空格**(不是 `"title"`) - `getphoneinfo` 回包用**小写 i**(H5 调用方是 `getphoneInfo` 大写 I) - - `appservice` 用字符串 `"1"`(后台)/ `"2"`(前台),不是数字 + - `appservice` 用字符串 `"2"`(后台)/ `"1"`(前台),不是数字 ⚠️ 值错位沿用 msext WKWebView 路径历史,参 `docs/H5-Native-Contract.md` §3.2 `appservice` 值错位说明 - `getlocationinfo` 中 `latitude` / `longitude` 是 **string** 而不是 number(`stringWithFormat:@"%f"` 产生) ### 3. 参数数据结构 diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index da4886f..e4f9853 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -367,7 +367,7 @@ Contract Design Plan(本文档) - [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"` + - 后台 → `bridge.call("appservice", data: .string("2"))`;前台 → `"1"`(值错位沿用 msext WKWebView 路径历史,参 Contract.md §3.2 `appservice` 值错位说明) - [x] **2.12** ShakeDetector 集成在 `Source/Bridge/Handlers/ShakeHandler.swift`(Phase 2.3 一并完成) - `WebContainerViewController.motionEnded(_:with:)` 转发 → `bridge.call("shakeEnd", data: nil)`(仅 `canShake=YES` 时触发) diff --git a/docs/H5-Native-Contract.md b/docs/H5-Native-Contract.md index 6113511..09add41 100644 --- a/docs/H5-Native-Contract.md +++ b/docs/H5-Native-Contract.md @@ -11,7 +11,7 @@ 新外壳与现网外壳之间,**只有以下三类内容是契约,必须 1:1 一致**: 1. **桥接接口名** — H5 调用的 handler 名字符串(如 `mediaTypeAudio`、`friendsSharetypeUrlToptitleDescript`、`OpenurlTitleData`),少一个字符 H5 就找不到。 -2. **参数字段名 & 数据结构** — H5 传入的 `data` 字段名、Native 回调时的 payload 结构。**含历史包袱**:`sharelogin.Province`(大写 P)、`OpenurlTitleData` 的入参键名 `"title "`(末尾有空格)、`appservice` 用 `"1"`/`"2"` 字符串而不是数字、字段值约定(如 `isloop: -1/0/1` 三态),**必须照抄**。 +2. **参数字段名 & 数据结构** — H5 传入的 `data` 字段名、Native 回调时的 payload 结构。**含历史包袱**:`sharelogin.Province`(大写 P)、`OpenurlTitleData` 的入参键名 `"title "`(末尾有空格)、`appservice` 用字符串 `"2"`=进入后台 / `"1"`=回到前台(值错位,沿用 msext WKWebView 路径历史,参 §3.2 第 4 行)、字段值约定(如 `isloop: -1/0/1` 三态),**必须照抄**。 3. **空实现接口也必须注册** — `opensaoma`、`getGameplay` 等当前为空实现的 handler,新外壳必须以同名空 handler 注册(响应 callback 但不做任何事),否则 H5 调用时会触发"bridge not found"错误分支。 **除以上三类之外,原生内部一切都可以重写**:技术栈(Swift/SwiftUI/Combine 都行)、WebView 接入方式(直接用 WKScriptMessageHandler 或继续用 WebViewJavascriptBridge)、网络层(URLSession/Alamofire)、解压库(ZIPFoundation)、状态机、控制器层级、命名风格——只要外部行为不变,怎么实现都行。本文档后续章节中描述的"现有实现细节"只是为了帮助你理解**为什么 H5 期待某种行为**,不是要求你照抄实现方式。 @@ -558,7 +558,7 @@ window.rootViewController = NavgationController | 1 | `getphoneinfo` | 注册 `getphoneInfo` handler 收到调用后 | 见下方表 A | ✓ | ✓ | | 2 | `getBattery` | UIDeviceBatteryLevelDidChangeNotification | 字符串 `"%.2f"` 形式的小数电量(0~1)| ✓ | ✓ | | 3 | `getnetwork` | AFNetworkReachability 变化 | 字符串 `"1"`/`"2"`/`"3"` (1=无网,2=WiFi,3=蜂窝)| ✓ | ✓ | -| 4 | `appservice` | App 前后台切换 | 字符串 `"1"`=进入后台,`"2"`=回到前台(命名错位,沿用历史)| ✓ | ✓ | +| 4 | `appservice` | App 前后台切换 | 字符串 `"2"`=进入后台,`"1"`=回到前台(值错位,沿用 msext WKWebView 路径历史。详见下方注释)| ✓ | ✓ | | 5 | `shakeEnd` | motionEnded 摇一摇结束 | `nil` | ✓ | ✓ | | 6 | `getlocationinfo` | 定位成功/失败 | 见下方表 B | ✓ | ✓ | | 7 | `gameui_play_voice` | mediaTypeAudio 开始播放(且 voicePlaying=1 时)| 字符串 user_id | ✓ | ✓ | @@ -575,6 +575,16 @@ window.rootViewController = NavgationController - `phonestate`:H5 子游戏会在收到 `"2"` 时主动哑麦/暂停,收到 `"0"` 恢复,跟视频房间体验强相关 - `recordSuccess`:H5 收到后会拿 `fileUrl` 而非 `audiourl` 走业务(早期接口与新接口并存的兼容产物);新外壳两个 handler 都得发,data 字段不同 +**`appservice` 值错位说明**(极容易看反,新人务必照抄不要"修正"): + +原 msext WKWebView 路径(`NewRootVC.m:1769-1786` / `gameController.m:1319-1336`)的真实代码是: +- iOS `applicationDidEnterBackground`(**真·进入后台**)→ `AppDelegate` post 一个**名为 `enterForeground` 的 NSNotification**(NSNotification 名错位,沿用历史)→ VC 的 `-enterForeground` 方法触发 → `[_bridge callHandler:@"appservice" data:@"2"]` +- iOS `applicationDidBecomeActive`(**真·回到前台**)→ `AppDelegate` post 一个**名为 `applicationWillResignActive` 的 NSNotification**(NSNotification 名错位)→ VC 的 `-applicationWillResignActive` 方法触发 → `[_bridge callHandler:@"appservice" data:@"1"]` + +H5 端 N 年来收到的实际语义就是:**`"2"` = 进入后台、`"1"` = 回到前台**。新外壳不论 NSNotification 内部命名怎么改、用 `willEnterForegroundNotification` 还是 `didBecomeActiveNotification`,桥发出的字符串值必须按上面这套对应关系发,**不要被 NSNotification 名误导而把值发反**。 + +(顺带:原 msext 还有一条 iOS<9 UIWebView 路径 `RootVC.m:516-543` / `fourviewVC.m:1254-1282`,发的值反过来:进入后台 → `"1"` / 回到前台 → `"2"`。该路径在新外壳最低 iOS 15.6 下完全不可达,**不能作为对齐依据**,请忽略。) + **表 A — `getphoneinfo`**: ```json { @@ -880,18 +890,18 @@ zip 包下载 + 解压由 `ASIHTTPRequest`(旧)/ `AFNetworking`(新)+ `Z | 通知名 | 发送方 | 接收方 | 用途 | |--------|--------|--------|------| -| `enterForeground` | AppDelegate `applicationDidEnterBackground` | Root/NewRootVC/fourview/game/three | 转 `appservice("2")` | -| `applicationWillResignActive` | AppDelegate `applicationDidBecomeActive`(节流 0.3s) | 同上 | 转 `appservice("1")` | +| `enterForeground` | AppDelegate `applicationDidEnterBackground`(真·进入后台)| Root/NewRootVC/fourview/game/three | 转 `appservice("2")`(即 H5 收到 "2" 才是真·进入后台)| +| `applicationWillResignActive` | AppDelegate `applicationDidBecomeActive`(真·回到前台,节流 0.3s) | 同上 | 转 `appservice("1")`(即 H5 收到 "1" 才是真·回到前台)| | `backgameDatatwo` | threeView / 子游戏 / RootVC.backgameData: | 父大厅 | 转 `getWebdata(data)` | | `stopradio` | 任意 | 主 VC | 暂停 backgroundPlayer | | `canbackye` | 任意(防抖恢复)| 主 VC | 允许返回手势 | | `UIDeviceBatteryLevelDidChangeNotification` | 系统 | 主 VC | 转 `getBattery(level)` | -**命名注意**:旧 msext 中 `enterForeground` 实际表示"进入后台",`applicationWillResignActive` 实际表示"回到前台"(命名被换反,历史包袱)。 +**命名注意**:旧 msext 中 `enterForeground` 实际表示"进入后台",`applicationWillResignActive` 实际表示"回到前台"(命名被换反,历史包袱)。**这个命名错位顺势把桥的字符串值也带反了**——H5 收到 `appservice("2")` 才表示进入后台、收到 `appservice("1")` 才表示回到前台。 -这些 NSNotification 是**模块内通讯**,**不是 H5 可见的契约边界** —— H5 只能看到桥事件 `appservice("1")`/`appservice("2")`。 +这些 NSNotification 是**模块内通讯**,**不是 H5 可见的契约边界** —— H5 只能看到桥事件 `appservice("2")`(后台)/`appservice("1")`(前台)。 -新项目自由命名(推荐用 `.appDidEnterBackground` / `.appDidBecomeActive` 等清晰命名),只要最终把 `"1"`/`"2"` 正确转发给 H5 桥即可。详见 `H5-Native-Implementation-Design.md` §4.4。 +新项目自由命名(推荐用 `.appDidEnterBackground` / `.appDidBecomeActive` 等清晰命名),但桥发出的值**必须按上面错位关系**对应:`didEnterBackground → "2"` / `willEnterForeground → "1"`,**不要按 NSNotification 字面语义改回去**。详见 `H5-Native-Implementation-Design.md` §4.4 与 §3.2 `appservice` 值错位说明。 --- @@ -1049,7 +1059,7 @@ Library/Caches/ - [ ] **H5 调 `confirm("是否退出")`** → 弹"取消"/"确定"两按钮 → 取消返回 false / 确定返回 true,H5 业务正常分支 ### C. 系统事件 -- [ ] 切后台 → H5 收到 `appservice("1")`;回前台 → 收到 `appservice("2")` +- [ ] 切后台 → H5 收到 `appservice("2")`;回前台 → 收到 `appservice("1")` ⚠️ 值错位沿用历史,参 §3.2 `appservice` 值错位说明 - [ ] 电量变化 → H5 收到 `getBattery("0.XX")` - [ ] 飞行模式开关 → H5 收到 `getnetwork("1")` / `getnetwork("2"|"3")` - [ ] 摇一摇(startshake==YES 时)→ H5 收到 `shakeEnd` diff --git a/docs/H5-Native-Implementation-Design.md b/docs/H5-Native-Implementation-Design.md index 7462614..2accd13 100644 --- a/docs/H5-Native-Implementation-Design.md +++ b/docs/H5-Native-Implementation-Design.md @@ -267,7 +267,8 @@ final class ExternalSubscriptions { bridge?.call("getnetwork", data: .string(state.rawValue), callback: nil) } lifecycle.onChange = { [weak bridge] state in - bridge?.call("appservice", data: .string(state == .background ? "1" : "2"), callback: nil) + // 值错位沿用历史 msext WKWebView 路径:背景="2" / 前台="1" + bridge?.call("appservice", data: .string(state == .background ? "2" : "1"), callback: nil) } battery.start(); network.start(); lifecycle.start() // 立即派发一次,补齐暂停期间的状态 @@ -1069,18 +1070,20 @@ func sceneDidBecomeActive(_ scene: UIScene) { } // LobbyHandlers / SubGameHandlers 内监听并转桥 +// 注:契约值与 NSNotification 字面语义相反,沿用 msext WKWebView 路径历史。 +// 真·进入后台 → "2";真·回到前台 → "1"。详见 Contract.md §3.2 `appservice` 值错位说明。 notifications.observe(.appDidEnterBackground) { [bridge] _ in - bridge.call("appservice", data: .string("1"), callback: nil) // 契约:"1"=后台 + bridge.call("appservice", data: .string("2"), callback: nil) // 契约:进入后台="2" } notifications.observe(.appDidBecomeActive) { [bridge] _ in - bridge.call("appservice", data: .string("2"), callback: nil) // 契约:"2"=前台 + bridge.call("appservice", data: .string("1"), callback: nil) // 契约:回到前台="1" } ``` **0.3s 节流决策**:旧外壳在 `applicationDidBecomeActive` 里设了 `flag=NO` + 0.3s 后恢复,防止双重触发。新外壳**默认不要这个节流**,理由: 1. 节流的根因是旧代码在 `applicationDidBecomeActive` 内被多处触发,新外壳只有 SceneDelegate 一处发通知 2. 测试如果证实双触发,加节流是 1 行代码的事(`DispatchQueue.asyncAfter`),不是架构改动 -3. H5 端通常对重复 `appservice("2")` 是幂等的(就是刷新 UI 状态) +3. H5 端通常对重复 `appservice("1")`(回前台)是幂等的(就是刷新 UI 状态) 如真机回归发现 H5 双触发产生问题,在 LobbyHandlers 内加 throttle 即可,不污染 SceneDelegate。 @@ -1253,7 +1256,8 @@ extension BridgeProtocol { } func callAppService(_ state: AppState) { - call("appservice", data: .string(state == .background ? "1" : "2"), callback: nil) + // 值错位沿用历史 msext WKWebView 路径:背景="2" / 前台="1" + call("appservice", data: .string(state == .background ? "2" : "1"), callback: nil) } // ... 15 个全列出 } @@ -2231,11 +2235,12 @@ private func runBootPipelineSteps() async throws { bridge.call("getnetwork", data: .string("\(code)"), callback: nil) } // appservice 无对应 app_* 全局变量,只走 bridge.call + // 值错位沿用历史 msext WKWebView 路径:背景="2" / 前台="1" AppLifecycleObserver.shared.onBackground = { - bridge.call("appservice", data: .string("1"), callback: nil) + bridge.call("appservice", data: .string("2"), callback: nil) } AppLifecycleObserver.shared.onForeground = { - bridge.call("appservice", data: .string("2"), callback: nil) + bridge.call("appservice", data: .string("1"), callback: nil) } } ``` @@ -2877,12 +2882,13 @@ public struct DeviceHandlers { networkMonitor.start() // 【4】 appservice — App 前后台切换 - // 数据:字符串 "1"=进入后台 / "2"=回到前台(命名错位,沿用历史) + // 数据:字符串 "2"=进入后台 / "1"=回到前台(值错位沿用 msext WKWebView 路径历史, + // 参 Contract.md §3.2 `appservice` 值错位说明,以及原 NewRootVC.m:1773/1782) appLifecycle.onBackground = { [weak bridge] in - bridge?.call("appservice", data: .string("1"), callback: nil) + bridge?.call("appservice", data: .string("2"), callback: nil) } appLifecycle.onForeground = { [weak bridge] in - bridge?.call("appservice", data: .string("2"), callback: nil) + bridge?.call("appservice", data: .string("1"), callback: nil) } appLifecycle.start() } @@ -4263,7 +4269,7 @@ extension BridgeProtocol { func callGetPhoneInfo(_ info: [String: String]) { call("getphoneinfo", data: .object(info.mapValues(BridgeData.string)), callback: nil) } func callGetBattery(_ level: Float) { call("getBattery", data: .string(String(format: "%.2f", level)), callback: nil) } func callGetNetwork(_ state: NetworkState) { call("getnetwork", data: .string(state.rawValue), callback: nil) } // "1"/"2"/"3" - func callAppService(_ s: AppLifecycleState) { call("appservice", data: .string(s == .background ? "1" : "2"), callback: nil) } + func callAppService(_ s: AppLifecycleState) { call("appservice", data: .string(s == .background ? "2" : "1"), callback: nil) } // 值错位沿用 msext WKWebView 路径 func callShakeEnd() { call("shakeEnd", data: nil, callback: nil) } func callGetLocationInfo(_ d: LocationPayload) { call("getlocationinfo", data: .object(d.dict), callback: nil) } // 字段见契约 §3.2 表B func callGameUIPlayVoice(_ user: String) { call("gameui_play_voice", data: .string(user), callback: nil) } diff --git a/docs/Verification-Checklist.md b/docs/Verification-Checklist.md index ed3f67d..51006e8 100644 --- a/docs/Verification-Checklist.md +++ b/docs/Verification-Checklist.md @@ -152,7 +152,7 @@ H5 需先用 `bridge.registerHandler('callbackName', fn)` 注册: - [ ] **F.1 模拟器 Features → Battery State 切换** → H5 收到 `getBattery` "0.XX" + `window.app_getbattery` 实时跟随 - [ ] **F.2 模拟器 Features → Network Link Conditioner 切换 100% Loss** → H5 收到 `getnetwork` "1" + `window.app_getnetwork === 1`;切回 WiFi → 收 "2";切 3G → 收 "3" -- [ ] **F.3 App 切后台**(Home 键 / 上滑)→ H5 收到 `appservice` "1";回前台 → 收 "2" +- [ ] **F.3 App 切后台**(Home 键 / 上滑)→ H5 收到 `appservice` "2";回前台 → 收 "1" ⚠️ 值错位沿用 msext WKWebView 路径历史,参 Contract.md §3.2 - [ ] **F.4 摇一摇** → shakeEnd(见 E.7) - [ ] **F.5 getphoneInfo 触发 getphoneinfo**(见 E.10) diff --git a/ylgamehall.xcodeproj/project.pbxproj b/ylgamehall.xcodeproj/project.pbxproj index 1ad7a07..6f2fa49 100644 --- a/ylgamehall.xcodeproj/project.pbxproj +++ b/ylgamehall.xcodeproj/project.pbxproj @@ -222,6 +222,7 @@ "$(PROJECT_DIR)/ylgamehall/Source/Audio/AMRFileCodec", ); INFOPLIST_FILE = ylgamehall/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "进贤聚友棋牌"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; @@ -271,6 +272,7 @@ "$(PROJECT_DIR)/ylgamehall/Source/Audio/AMRFileCodec", ); INFOPLIST_FILE = ylgamehall/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "进贤聚友棋牌"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = ""; diff --git a/ylgamehall/Source/Resource/AppLifecycleObserver.swift b/ylgamehall/Source/Resource/AppLifecycleObserver.swift index 39ff0fb..8b48374 100644 --- a/ylgamehall/Source/Resource/AppLifecycleObserver.swift +++ b/ylgamehall/Source/Resource/AppLifecycleObserver.swift @@ -5,7 +5,8 @@ // 监听 UIApplication 前/后台切换通知,提供 onBackground / onForeground 钩子。 // // 契约:docs/H5-Native-Contract.md §3.2 [4]appservice 反向 callback -// "1" = 进入后台 / "2" = 回到前台(命名错位沿用历史) +// "2" = 进入后台 / "1" = 回到前台(值错位沿用历史 msext WKWebView 路径, +// 参 daoqi/msext NewRootVC.m:1773/1782、gameController.m:1323/1332) // Phase 2.11 // diff --git a/ylgamehall/Source/WebView/BridgedWebView.swift b/ylgamehall/Source/WebView/BridgedWebView.swift index af71f53..775d94d 100644 --- a/ylgamehall/Source/WebView/BridgedWebView.swift +++ b/ylgamehall/Source/WebView/BridgedWebView.swift @@ -53,6 +53,11 @@ public final class BridgedWebView: UIView { // ── 创建 WKWebView + BridgeBus ───────────────────────── let webView = WKWebView(frame: .zero, configuration: configuration) + #if DEBUG + if #available(iOS 16.4, *) { + webView.isInspectable = true + } + #endif let bridge = BridgeBus(webView: webView, controller: configuration.userContentController) diff --git a/ylgamehall/Source/WebView/OverlayViewController.swift b/ylgamehall/Source/WebView/OverlayViewController.swift index 211217d..66ad03d 100644 --- a/ylgamehall/Source/WebView/OverlayViewController.swift +++ b/ylgamehall/Source/WebView/OverlayViewController.swift @@ -108,6 +108,11 @@ public final class OverlayViewController: UIViewController { } webView = WKWebView(frame: .zero, configuration: config) + #if DEBUG + if #available(iOS 16.4, *) { + webView.isInspectable = true + } + #endif webView.scrollView.bounces = false webView.scrollView.contentInsetAdjustmentBehavior = .never webView.translatesAutoresizingMaskIntoConstraints = false diff --git a/ylgamehall/Source/WebView/SplashOverlay.swift b/ylgamehall/Source/WebView/SplashOverlay.swift index e5efda3..a67c6f2 100644 --- a/ylgamehall/Source/WebView/SplashOverlay.swift +++ b/ylgamehall/Source/WebView/SplashOverlay.swift @@ -43,7 +43,8 @@ final class SplashOverlay: UIView { imageView.translatesAutoresizingMaskIntoConstraints = false addSubview(imageView) - label.textColor = .white + // SplashImage 底部是白色留白,白字会完全融进去 → 用黑色。 + label.textColor = .black label.font = .systemFont(ofSize: 14, weight: .medium) label.textAlignment = .center label.numberOfLines = 1 diff --git a/ylgamehall/Source/WebView/SubGameViewController.swift b/ylgamehall/Source/WebView/SubGameViewController.swift index 9ba7033..4087f97 100644 --- a/ylgamehall/Source/WebView/SubGameViewController.swift +++ b/ylgamehall/Source/WebView/SubGameViewController.swift @@ -336,11 +336,13 @@ public final class SubGameViewController: UIViewController { bridge.call("getnetwork", data: .string("\(code)"), callback: nil) } + // §3.2 [4] appservice — 值错位沿用历史 msext WKWebView 路径: + // 真·进入后台 → "2",真·回到前台 → "1"。参 WebContainerViewController 同位置注释。 AppLifecycleObserver.shared.onBackground = { - bridge.call("appservice", data: .string("1"), callback: nil) + bridge.call("appservice", data: .string("2"), callback: nil) } AppLifecycleObserver.shared.onForeground = { - bridge.call("appservice", data: .string("2"), callback: nil) + bridge.call("appservice", data: .string("1"), callback: nil) } // 电话状态 — 契约 §3.1 [14]:来电 "2" / 挂断 "0" diff --git a/ylgamehall/Source/WebView/WebContainerViewController.swift b/ylgamehall/Source/WebView/WebContainerViewController.swift index e871503..ec24bf2 100644 --- a/ylgamehall/Source/WebView/WebContainerViewController.swift +++ b/ylgamehall/Source/WebView/WebContainerViewController.swift @@ -359,6 +359,12 @@ public final class WebContainerViewController: UIViewController { throw BootError.ipaUpgradeRequired(downloadURL: dl) } + // 过渡文案:config 已拉到、进入 zip 升级前的"建连等首字节"静默阶段。 + // 不补这一步,用户会看到"拉取配置中..."一直显示到 zip CDN 冷 DNS+TLS 握完 + // 才跳到"下载更新中 1%",慢网络下感知像卡死。这里把 UI 状态机补齐到与 + // 实际工作阶段对应。 + splash.update(text: "校验版本中...", progress: nil) + // H5 zip 升级失败仍致命(用户体验 ≠ 静默用旧 zip)—— 抛出由外层 splash.showError // LobbyZipUpgrader.onProgress 由 URLSession delegate 后台队列调度, // 必须显式 hop 到 MainActor 才能动 splash UI。 @@ -450,12 +456,15 @@ public final class WebContainerViewController: UIViewController { bridge.call("getnetwork", data: .string("\(code)"), callback: nil) } - // §3.2 [4] appservice — App 前后台切换(命名错位沿用历史) + // §3.2 [4] appservice — App 前后台切换(值错位沿用历史 msext WKWebView 路径) + // 真·进入后台 → "2",真·回到前台 → "1"。参 daoqi/msext NewRootVC.m:1773/1782、 + // gameController.m:1323/1332(AppDelegate post 的 NSNotification 名也是错位的, + // 净结果是 H5 收到的桥值与"自然语义"相反,必须照抄)。 AppLifecycleObserver.shared.onBackground = { - bridge.call("appservice", data: .string("1"), callback: nil) + bridge.call("appservice", data: .string("2"), callback: nil) } AppLifecycleObserver.shared.onForeground = { - bridge.call("appservice", data: .string("2"), callback: nil) + bridge.call("appservice", data: .string("1"), callback: nil) } // §3.2 [12] getWebdata — 反向把 backgameData / 弹层 data 入参透传给大厅 H5。