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:
joywayer
2026-06-22 07:28:34 +08:00
co-authored by Claude Opus 4.7
parent 82bad8a85d
commit c6221d30ca
+202
View File
@@ -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.swiftPhase 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 12AudioServicesDisposeSystemSoundID(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 多发起方派发(关键设计)
#### 问题背景 #### 问题背景