Design §8.4.1:补 DeviceKit 9 项接口实现骨架
覆盖 §3.1 异步 handler 6 项 + §3.2 反向 callback 3 项,含本轮全文 audit
新发现的 3 项反向 callback(前一轮 audit 漏列):
§3.1 异步 handler 6 项(DeviceHandlers.register):
- 【10】vibrator — AudioServicesPlaySystemSound + cb "vibrator"
- 【11】repeatvibrator — 同 vibrator(msext RootVC.m:1820 沿用历史)
- 【12】canclevibrator — AudioServicesDisposeSystemSoundID + cb "canclevibrator"
- 【13】gamepastetext — UIPasteboard.general.string → cb 返回内容
- 【14】gameCopytext — UIPasteboard.general.string = data + cb "gameCopytext"
- 【21】getphoneInfo — cb "getphoneInfo"(大写 I)+ bridge.call("getphoneinfo"
小写 i!,data: 表 A 6 字段)
※ 大小写 i 不一致是 msext 历史契约,严格保持
§3.2 反向 callback 3 项(DeviceHandlers.bindReverseCallbacks):
- 【2】 getBattery — UIDevice.batteryLevelDidChangeNotification
data: String(format: "%.2f", level) 0~1
- 【3】 getnetwork — NWPath 变化 data: "1"无网/"2"WiFi/"3"蜂窝
- 【4】 appservice — App 前后台 data: "1"=后台/"2"=前台(命名错位沿用)
辨析 polyfill 与 callback 同名问题:契约 §3.4.1 polyfill 的同步 getter
`getbattery()` / `getnetwork()` 与 §3.2 反向 `getBattery` / `getnetwork`
名字重合但语义不同(前者 H5 同步问当前值,后者 Native 事件推变化),
两条路径并存互不矛盾。在 §8.4.1 新增辨析段澄清。
支撑组件骨架:BatteryMonitor / AppLifecycleMonitor(NetworkMonitor 已在
§8.4 现有);§8.4.2 与 msext 6 维度差异表。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
82bad8a85d
commit
c6221d30ca
@@ -2139,6 +2139,208 @@ public final class NetworkMonitor {
|
|||||||
|
|
||||||
用 `Network.framework` 替换 `AFNetworkReachabilityManager`。
|
用 `Network.framework` 替换 `AFNetworkReachabilityManager`。
|
||||||
|
|
||||||
|
#### 8.4.1 H5 device handlers + 系统事件反向 callback 实现骨架
|
||||||
|
|
||||||
|
DeviceKit 共承载 **9 项契约接口**:
|
||||||
|
- §3.1 异步 handler 6 项:`vibrator` / `repeatvibrator` / `canclevibrator` / `gamepastetext` / `gameCopytext` / `getphoneInfo`
|
||||||
|
- §3.2 反向 callback 3 项:`getBattery`(电量变化推送)/ `getnetwork`(网络变化推送)/ `appservice`(前后台切换推送)
|
||||||
|
|
||||||
|
注:契约 §3.2 [2][3]的反向 `getBattery` / `getnetwork` 名字与 §3.4.1 polyfill 的同步 getter `getbattery()` / `getnetwork()` 重合但语义不同:
|
||||||
|
- **polyfill** = H5 同步问"当前是什么"(一次性快照,注入到 `window.settings`)
|
||||||
|
- **callback** = Native 异步推"刚刚变成了什么"(事件驱动,通过 `bridge.call(...)` 推给 H5)
|
||||||
|
- 两条路径并存、互不矛盾,H5 根据使用场景选其一
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Source/Bridge/Handlers/DeviceHandlers.swift
|
||||||
|
import UIKit
|
||||||
|
import AudioToolbox
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public struct DeviceHandlers {
|
||||||
|
|
||||||
|
let bridge: BridgeProtocol
|
||||||
|
let pasteboard: UIPasteboard = .general
|
||||||
|
// BatteryMonitor / NetworkMonitor / AppLifecycleMonitor 在 register() 内启动
|
||||||
|
// 并把回调钩到 bridge.call(...),构成"事件 → callback"的反向链路
|
||||||
|
|
||||||
|
let batteryMonitor: BatteryMonitor
|
||||||
|
let networkMonitor: NetworkMonitor
|
||||||
|
let appLifecycle: AppLifecycleMonitor
|
||||||
|
|
||||||
|
public func register() {
|
||||||
|
// ─── §3.1 异步 handler ──────────────────────────────
|
||||||
|
bridge.register("vibrator", handler: vibrator)
|
||||||
|
bridge.register("repeatvibrator", handler: repeatVibrator)
|
||||||
|
bridge.register("canclevibrator", handler: cancelVibrator)
|
||||||
|
bridge.register("gamepastetext", handler: gamePasteText)
|
||||||
|
bridge.register("gameCopytext", handler: gameCopyText)
|
||||||
|
bridge.register("getphoneInfo", handler: getPhoneInfo)
|
||||||
|
|
||||||
|
// ─── §3.2 反向 callback 钩子 ────────────────────────
|
||||||
|
bindReverseCallbacks()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【10】 vibrator — 单次振动
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [10]:入参忽略 → AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||||
|
// responseCallback: "vibrator"(注意:vibrator 系列的 callback 是
|
||||||
|
// handler 名字本身,不是 "Response from xxx",
|
||||||
|
// 与 audio 系列约定不同;参考 msext Bridge.m 约定)
|
||||||
|
//
|
||||||
|
// 当前实现见 Source/Bridge/Handlers/VibratorHandler.swift(Phase 1.15 已落地,
|
||||||
|
// 本节是把它放进 DeviceHandlers 聚合时的等价骨架)
|
||||||
|
|
||||||
|
private func vibrator(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||||
|
cb?(.string("vibrator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【11】 repeatvibrator — 重复振动(实际同 vibrator)
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [11]:入参 int 但被忽略,原 msext RootVC.m:1820 实现就是单次 vibrate
|
||||||
|
// 沿用历史,不真的循环
|
||||||
|
|
||||||
|
private func repeatVibrator(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
|
||||||
|
cb?(.string("repeatvibrator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【12】 canclevibrator — 释放系统振动音
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [12]:AudioServicesDisposeSystemSoundID(kSystemSoundID_Vibrate)
|
||||||
|
// 实际意义有限(kSystemSoundID_Vibrate 是系统常量,dispose 也不会真的"取消"),
|
||||||
|
// 但 H5 仍会调用,必须 noop 实现 + callback 维持契约
|
||||||
|
|
||||||
|
private func cancelVibrator(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
AudioServicesDisposeSystemSoundID(kSystemSoundID_Vibrate)
|
||||||
|
cb?(.string("canclevibrator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【13】 gamepastetext — 读剪贴板
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [13]:
|
||||||
|
// 入参 : 忽略
|
||||||
|
// responseCallback: 剪贴板字符串内容(不是 "gamepastetext"),nil 时回空串
|
||||||
|
//
|
||||||
|
// 注意:iOS 14+ 读剪贴板会弹"已粘贴自 XX"系统提示,无法关闭,是平台行为
|
||||||
|
// 不算契约违反;如未来 H5 业务希望静默读,需 H5 端配合改用 UIPasteboard 检测 API
|
||||||
|
|
||||||
|
private func gamePasteText(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
let text = pasteboard.string ?? ""
|
||||||
|
cb?(.string(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【14】 gameCopytext — 写剪贴板
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [14]:
|
||||||
|
// 入参 data : string 要写入剪贴板的文本
|
||||||
|
// responseCallback: "gameCopytext"
|
||||||
|
|
||||||
|
private func gameCopyText(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
if let text = data?.asString {
|
||||||
|
pasteboard.string = text
|
||||||
|
}
|
||||||
|
cb?(.string("gameCopytext"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 【21】 getphoneInfo(大写 I)— 请求设备信息
|
||||||
|
//
|
||||||
|
// 契约 §3.1 [21]:
|
||||||
|
// 入参 : 忽略
|
||||||
|
// responseCallback: "getphoneInfo"(注意大写 I)
|
||||||
|
// 反向调用: bridge.call("getphoneinfo"(小写 i!), data: 表 A, callback: nil)
|
||||||
|
//
|
||||||
|
// 大小写 i 不一致是 msext 历史遗留契约,必须严格保持:
|
||||||
|
// - H5 调原生时用大写:bridge.callHandler("getphoneInfo", ...)
|
||||||
|
// - 原生回调 H5 时用小写:bridge.callHandler("getphoneinfo", ...)
|
||||||
|
//
|
||||||
|
// 表 A 字段名固定(PhoneAdresseMAC / PhoneDeviceBrand / PhoneIMEI / PhoneModel /
|
||||||
|
// PhoneProvidersName / PhoneVersion),值统一为 string,详见 Contract §3.2 表 A
|
||||||
|
|
||||||
|
private func getPhoneInfo(_ data: BridgeData?, _ cb: BridgeCallback?) async {
|
||||||
|
cb?(.string("getphoneInfo"))
|
||||||
|
// 反向 push 设备信息快照(注意 handler 名小写 i)
|
||||||
|
let snapshot = DeviceInfo.snapshot
|
||||||
|
bridge.call("getphoneinfo",
|
||||||
|
data: .object(snapshot.mapValues { .string($0) }),
|
||||||
|
callback: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - §3.2 反向 callback 钩子(事件驱动)
|
||||||
|
|
||||||
|
private func bindReverseCallbacks() {
|
||||||
|
// 【2】 getBattery — UIDeviceBatteryLevelDidChangeNotification
|
||||||
|
// 数据:字符串 "%.2f" 形式的小数电量(0~1)
|
||||||
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||||
|
batteryMonitor.onChange = { [weak bridge] level in
|
||||||
|
let str = String(format: "%.2f", level)
|
||||||
|
bridge?.call("getBattery", data: .string(str), callback: nil)
|
||||||
|
}
|
||||||
|
batteryMonitor.start()
|
||||||
|
|
||||||
|
// 【3】 getnetwork — NWPath 变化
|
||||||
|
// 数据:字符串 "1"=无网 / "2"=WiFi / "3"=蜂窝
|
||||||
|
networkMonitor.stateDidChange = { [weak bridge] state in
|
||||||
|
bridge?.call("getnetwork", data: .string(state.rawValue), callback: nil)
|
||||||
|
}
|
||||||
|
networkMonitor.start()
|
||||||
|
|
||||||
|
// 【4】 appservice — App 前后台切换
|
||||||
|
// 数据:字符串 "1"=进入后台 / "2"=回到前台(命名错位,沿用历史)
|
||||||
|
appLifecycle.onBackground = { [weak bridge] in
|
||||||
|
bridge?.call("appservice", data: .string("1"), callback: nil)
|
||||||
|
}
|
||||||
|
appLifecycle.onForeground = { [weak bridge] in
|
||||||
|
bridge?.call("appservice", data: .string("2"), callback: nil)
|
||||||
|
}
|
||||||
|
appLifecycle.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 支撑监控器(Source/DeviceKit/)
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public final class BatteryMonitor {
|
||||||
|
public var onChange: ((Float) -> Void)?
|
||||||
|
public func start() {
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
forName: UIDevice.batteryLevelDidChangeNotification,
|
||||||
|
object: nil, queue: .main
|
||||||
|
) { [weak self] _ in
|
||||||
|
self?.onChange?(UIDevice.current.batteryLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public final class AppLifecycleMonitor {
|
||||||
|
public var onBackground: (() -> Void)?
|
||||||
|
public var onForeground: (() -> Void)?
|
||||||
|
public func start() {
|
||||||
|
let nc = NotificationCenter.default
|
||||||
|
nc.addObserver(forName: UIApplication.didEnterBackgroundNotification,
|
||||||
|
object: nil, queue: .main) { [weak self] _ in
|
||||||
|
self?.onBackground?()
|
||||||
|
}
|
||||||
|
nc.addObserver(forName: UIApplication.willEnterForegroundNotification,
|
||||||
|
object: nil, queue: .main) { [weak self] _ in
|
||||||
|
self?.onForeground?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8.4.2 与原 msext 的差异
|
||||||
|
|
||||||
|
| 维度 | msext 现状(不要照抄) | 新外壳决策 |
|
||||||
|
|------|--------------------|----------|
|
||||||
|
| 振动取消 | `AudioServicesDisposeSystemSoundID(kSystemSoundID_Vibrate)` 同款 | 同款(契约硬约束,结构性意义有限但 H5 仍依赖 callback) |
|
||||||
|
| 剪贴板 | `[UIPasteboard generalPasteboard].string` | `UIPasteboard.general.string`(行为等价;iOS 14+ "已粘贴"提示是平台行为,不算契约违反) |
|
||||||
|
| 电量监听 | `UIDeviceBatteryLevelDidChangeNotification` + `[NSString stringWithFormat:@"%.2f"]` | `Notification.batteryLevelDidChangeNotification` + `String(format:)`(等价) |
|
||||||
|
| 网络监听 | `AFNetworkReachabilityManager` | `Network.framework` `NWPathMonitor`(ADR-006,无 AF 依赖) |
|
||||||
|
| 前后台 | `UIApplicationDidEnterBackgroundNotification` + `UIApplicationWillEnterForegroundNotification` 各自挂大厅 VC | `AppLifecycleMonitor` 集中处理,多 VC 复用 |
|
||||||
|
| getphoneInfo 大小写 | 大写 I 入 / 小写 i 出 | 严格保持(契约硬约束,msext Bridge.m 沿用) |
|
||||||
|
|
||||||
### 8.5 微信 / QQ 多发起方派发(关键设计)
|
### 8.5 微信 / QQ 多发起方派发(关键设计)
|
||||||
|
|
||||||
#### 问题背景
|
#### 问题背景
|
||||||
|
|||||||
Reference in New Issue
Block a user