系统架构
一句话版本
text
WMPF 运行时(渲染小程序)
│ 业务 CGI / V8 inspector / Cronet
▼
App 内的运行时桥接(调试通道 / 网络记录 / 插件)
▼
主进程网关 :9400 ← WebSocket → Chrome DevTools改动都在 APK 自己身上,系统分区没动,所以不需要 root。
四层结构
text
PC Chrome DevTools / 浏览器控制台
│ adb forward tcp:9400
GATEWAY 主进程 · 本地网关 :9400
│ 同设备内的本地通道
APP APK · 内置运行时 + 调试网关 + 本地服务
│ 同进程内
RUNTIME :container · WMPF 运行时
V8 逻辑层 · XWeb 渲染层 · Cronet双进程模型
| 进程 | 角色 | 职责 |
|---|---|---|
com.ruan.wmpfclone | 网关进程 | 绑定 9400,提供 CDP / REST,管理 target 表 |
com.ruan.wmpfclone:container | 运行时进程 | 运行小程序、V8、XWeb、网络捕获 |
为什么分两个进程:
- 小程序运行时必须在
:container里(isolate 在那儿); - 对外端口只能一个进程绑,网关放主进程更稳;
- 两个进程用 App 内的本地通道通信,不暴露到网络。
网关只由主进程提供;运行时进程只有在确认小程序运行后,才承担调试转发。
target 表
对外就是 CDP 的 /json/list:
| target id | type | 说明 |
|---|---|---|
appservice | app_service | 逻辑层,固定 id,换小程序不变 |
R<hash>-<pageId> | page | 渲染层,每个 WebView 一个 |
逻辑层只有一个小程序 isolate,所以只有一个 target;渲染层是「每个 WebView 一个」,可能有当前页、空壳、web-view 多个。
三条链路
| 段 | 载体 | 说明 |
|---|---|---|
| PC ⇄ 网关 | WebSocket | CDP JSON / REST |
| 网关 ⇄ 运行时 | App 内本地通道 | 行分隔 JSON |
| AGENT ⇄ V8 | Java 方法 | inspector 上下行 tee |
时序:一次断点命中
- Chrome 发出
Debugger.setBreakpoint; - 网关把命令转发给运行时;
- 运行时交给 V8,记录断点;
- 业务代码执行到该行,暂停;
- 暂停事件沿原路返回;
- Chrome 显示调用栈、作用域与变量。
单步、跳出、恢复都走同一条通道。
时序:一次 wx.request 变成 Network 事件
text
业务代码 wx.request(opt)
│
├─(1) 捕获点把 {t:'req', url, m, h, d, st(Error.stack)}
│ 压进 globalThis.__wmpfNetQ
│
├─(2) 宿主每 250ms 用 Runtime.evaluate 抽空队列
│ 只对确认有 wx 的上下文求值;暂停态跳过
│
├─(3) NetCapture 翻译成标准事件
│ requestWillBeSent / responseReceived / loadingFinished
│ initiator.callFrames ← Error.stack + url→scriptId 索引
│
└─(4) 广播给 Chrome 与 App 内的网络页时序:渲染层 target 发现
text
RenderBridge.refresh()(每 2s,节流)
├ 扫描本进程 pid,试连 @webview_devtools_remote_<pid>
├ GET /json/list 拿 pageId / wsPath
├ 对新 target 求值:location.href / 当前路由 / 是否有 wx-page-meta
├ 打分,每个 appId 取最高分标 :VISIBLE
└ 对外输出 target(真实 url + appId + 显示标题)探测在独立线程,不阻塞 HTTP。
时序:Native 捕获
text
小程序 JSAPI(wx.getLocation / login / …)
│
├─ js-operatewxdata 进入 native 中转
│ └─ 运行时桥接(请求前置 / 响应后置)
│ ├─ 插件链:nativeReq 可改写请求
│ ├─ 响应:key 提取 + nativeResp 可改写
│ └─ 记录最近 400 条 + 实时推送
│
└─ App 的 Native 页按 seq 配对请求/响应单插件超时 260ms,单请求总预算 900ms,超时用当前结果继续。
Network 脚本
脚本不是独立引擎,而是运行在逻辑层捕获通道里的回调:
text
Network 页保存脚本
→ 加载到捕获通道(IIFE,收集 onRequest / onResponse)
→ wx.request 壳:
onRequest(o) → 可改 url / method / header / data
原生请求
onResponse(r, url) → 可改 statusCode / header / data
→ 业务回调拿到改写后的结果脚本约 1 秒热更新;重写规则走 native 层(Cronet 边界),和脚本互不依赖。
插件加载
text
插件 HTML
├─ 面板:宿主 WebView 加载 /ui/plugin-panel.html,插件在 iframe 里
├─ 逻辑层:通过 CDP 加载到 appservice 上下文
└─ 渲染层:通过页面 target 加载到 XWeb 上下文
│
└─ 三层共用 /v1/plugins/bus(WebSocket 总线)总线做消息路由、超时、帧大小限制;插件的存储按命名空间隔离,网络规则按 owner 隔离。
端口
| 项 | 默认 | 改法 |
|---|---|---|
| 网关端口 | 9400 | 开发配置 / POST /v1/config |
| 绑定地址 | 0.0.0.0 | 同上,可改 127.0.0.1 |
| agent 端口 | 9429 | 引擎按 extInfo 连接,不建议改 |
关键设计取舍
| 决策 | 原因 |
|---|---|
| 不修改系统分区 | 所有能力随 App 安装,卸载即恢复 |
| 网络捕获不依赖系统代理或证书 | 只在 App 内部记录,不影响其它应用 |
| 每个小程序共用一个端口 | 一个小程序进程只有一个逻辑层,按 target 区分 |
| 渲染层不直接信任页面自报信息 | 通过求值探测确认真实页面 |
| 上下文用 RPC 补齐 | 事件只发给当时的会话 |