启动文字提示,h5调试加入
This commit is contained in:
@@ -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` 时触发)
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user