Android Playground 架构与工作流程源码分析¶
分析版本:Midscene 1.10.5,基线提交
cfc7c100分析日期:2026-07-16 范围:Android Playground CLI、平台描述器、通用 Playground Server、React 前端、Android Agent 和 scrcpy sidecar
1. 结论摘要¶
Android Playground 不是单独实现的一套测试引擎,而是把 Android 能力接入通用 Playground 框架:
@midscene/android-playground发现 ADB 设备、创建AndroidDevice/AndroidAgent,并声明 scrcpy 预览。@midscene/playground提供 Express Server、会话状态、执行锁、AI 配置、Recorder、报告和 REST API。@midscene/playground-app提供 React UI、远程 SDK、会话表单、对话执行、实时预览和手动控制。ScrcpyServer是 sidecar,只负责设备列表和 H.264 实时画面,不负责 AI 规划。- 真正执行自然语言任务的是
AndroidAgent和 CoreTaskExecutor;Playground 只是交互式控制面和协议层。
Android Playground 因此可以拆成“平台适配层”和“通用工作台”两部分。后续做多项目、多设备平台时,应优先复用通用协议与 UI,只在平台描述器中替换设备发现、Agent 工厂和 preview descriptor。
2. 四层架构¶
flowchart TB
subgraph Browser["浏览器层 - @midscene/playground-app"]
Controller["usePlaygroundController"]
SDK["PlaygroundSDK"]
Conversation["对话 / 动作面板"]
Preview["PreviewRenderer + 手动控制"]
end
subgraph Server["控制层 - @midscene/playground"]
Express["PlaygroundServer / REST API"]
Session["SessionManager + activeConnection"]
Lock["任务锁 + 进度快照"]
Recorder["Recorder + 报告返回"]
end
subgraph AndroidAdapter["Android 平台层 - @midscene/android-playground"]
Descriptor["androidPlaygroundPlatform"]
Discovery["ADB 设备发现"]
Factory["AndroidDevice + AndroidAgent 工厂"]
Scrcpy["ScrcpyServer sidecar"]
end
subgraph Engine["执行层 - @midscene/android + @midscene/core"]
Agent["AndroidAgent"]
Core["TaskExecutor / AI planning / locate"]
Device["AndroidDevice / ADB"]
Phone["真机或模拟器"]
end
Conversation --> Controller --> SDK --> Express
Preview --> SDK
Express --> Session --> Factory --> Agent
Express --> Lock --> Agent --> Core --> Device --> Phone
Recorder --> Express
Descriptor --> Session
Discovery --> Descriptor
Scrcpy --> Preview
Phone --> Scrcpy
2.1 各包责任¶
| 包 | 主要责任 | 不负责 |
|---|---|---|
@midscene/android-playground |
ADB 设备选择、Android session、scrcpy sidecar | 通用执行 API、AI 模型调用 |
@midscene/playground |
Server、SDK、会话、执行、进度、报告、Recorder | Android ADB 细节、页面 UI |
@midscene/playground-app |
React 工作台、控制器、预览、手动交互 | 设备动作实现、模型执行 |
@midscene/android |
Android Agent、截图、坐标、ADB 输入 | Playground 页面和 HTTP 协议 |
@midscene/core |
AI 规划、定位、TaskRunner、报告 | Android 设备发现和 scrcpy 网页串流 |
3. CLI 启动流程¶
3.1 入口¶
bin.ts 是 Android Playground CLI 入口:
- 创建
ScrcpyServer实例,但暂不监听端口。 - 调用
androidPlaygroundPlatform.prepare()。 - 把 prepared platform 交给
launchPreparedPlaygroundPlatform()。 - Playground Server 启动后输出 URL 和 server id。
- 使用系统默认浏览器打开 Playground。
3.2 prepare 阶段¶
androidPlaygroundPlatform 是 definePlaygroundPlatform() 定义的平台描述器。prepare() 同时寻找空闲的 Playground 端口和 scrcpy 端口,然后构造:
platformId = android。PlaygroundSessionManager。- scrcpy
PlaygroundSidecar。 - 基础 runtime/preview metadata。
- 通用 Server 的 launch options。
sequenceDiagram
participant CLI as android-playground bin
participant Platform as androidPlaygroundPlatform
participant Launcher as platform-launcher
participant Server as PlaygroundServer
participant Browser as Browser
CLI->>CLI: new ScrcpyServer()
CLI->>Platform: prepare(staticDir, scrcpyServer)
Platform->>Platform: 查找 5800 和 scrcpy 空闲端口
Platform-->>CLI: PreparedPlaygroundPlatform
CLI->>Launcher: launchPreparedPlaygroundPlatform(prepared)
Launcher->>Server: new PlaygroundServer(session-managed)
Launcher->>Server: launch(port)
Launcher->>Server: setPreparedPlatform(prepared)
Server-->>CLI: server + port + close()
CLI->>Browser: open(http://localhost:port)
在 session-managed 模式下,launchPreparedPlaygroundPlatform() 不会在 Server 启动前直接拉起 sidecar。prepared platform 先注册到 Server,真正创建 session 时由 applyCreatedSession() 启动 base sidecar。这样未选择设备时不会占用 scrcpy 连接。
3.3 静态页面与端口注入¶
Playground Server 在 API 路由之后提供静态文件。Android 平台通过 configureServer() 把 scrcpyPort 写入 Server;Server 返回 HTML 时注入运行时配置,前端再把它解析成 scrcpy URL。
通用静态页面来自构建后的 Playground App,不需要 Android 包维护另一套业务 UI。
4. 设备发现和会话创建¶
4.1 设备发现¶
getAdbTargets() 调用 getConnectedDevicesWithDetails(),只保留 state === device 的设备,并转换成统一 PlaygroundSessionTarget:
id/label:ADB serial/udid。description:model 和 resolution。isDefault:第一个在线设备。
getSetupSchema() 生成一个 deviceId select 字段。只有一台在线设备时,autoSubmitWhenReady=true,前端可以自动创建 Agent;多台设备时由用户选择。
发现失败不会让 Server 崩溃,而是在 setup schema 中返回 warning notice。
4.2 会话状态机¶
stateDiagram-v2
[*] --> Required: Server 启动且需要选择设备
Required --> Creating: POST /session
Creating --> Ready: AndroidDevice.connect 成功
Creating --> Required: 创建失败并清理 Agent/sidecar
Ready --> Running: POST /execute 获得任务锁
Running --> Ready: 执行完成或错误并释放锁
Running --> Recreating: POST /cancel 或配置变更
Recreating --> Ready: AgentFactory 重建成功
Ready --> Destroying: DELETE /session
Destroying --> Required: Agent/sidecar 已停止
Ready --> Blocked: 平台标记 setup 阻塞
Blocked --> Required: 阻塞条件解除
Server 的活动态集中在 _activeConnection:
session:连接状态、展示名和 metadata。agent:当前AndroidAgent。agentFactory:取消或配置变化时重建 Agent。runtime:平台、preview 和 metadata。executionHooks:执行前后钩子。sidecars:当前 session 使用的辅助服务。
prepared platform 的 base runtime、hooks 和 sidecars 单独保存。销毁 session 后,restoreBaseSessionState() 回到 required 状态,而不是销毁整个 Playground Server。
4.3 创建 Android session¶
sequenceDiagram
participant UI as React Controller
participant SDK as PlaygroundSDK
participant Server as PlaygroundServer
participant SM as Android SessionManager
participant Sidecar as ScrcpyServer
participant Device as AndroidDevice
participant Agent as AndroidAgent
UI->>SDK: getSessionSetup()
SDK->>Server: GET /session/setup
Server->>SM: getSetupSchema + listTargets
SM-->>UI: deviceId 字段和在线设备
UI->>SDK: createSession({deviceId})
SDK->>Server: POST /session
Server->>Server: destroyCurrentSession()
Server->>SM: createSession({deviceId})
SM->>Device: new AndroidDevice(deviceId)
SM->>Device: connect()
SM->>Agent: new AndroidAgent(device)
SM-->>Server: agent + agentFactory + scrcpy preview
Server->>Sidecar: start()
Server->>Server: applyCreatedSession()
Server-->>UI: session + runtimeInfo
Android 的 createSession() 返回当前 Agent 和 connectAgent factory。保留 factory 很重要:取消任务不是中断某个 Promise,而是销毁并重建 Agent;没有 factory 时,取消后下一次执行将无法恢复。
session 的 preview descriptor 声明:
- 主能力:scrcpy live preview。
- 回退能力:screenshot polling。
scrcpyPort:供浏览器连接 sidecar。
5. React Playground 工作方式¶
5.1 控制器¶
usePlaygroundController() 是页面状态中枢,持有:
PlaygroundSDK。- Server 在线状态和 runtime info。
- session setup/schema/form。
- 自动创建设备 session 的去重状态。
- AI 配置应用状态。
- 执行前倒计时。
它每 5 秒刷新 Server/runtime;未连接 session 时也按间隔刷新设备列表。自动创建使用 signature 去重,避免只有一台设备时重复提交同一个 session。
5.2 页面布局¶
PlaygroundApp 使用左右可调整 Panel:
- 左侧:会话 setup 或对话/执行面板。
- 右侧:已连接时显示
PlaygroundPreview,未连接时显示 disconnected 状态。
预览连接类型由 runtime preview descriptor 解析,可选择 screenshot、MJPEG、scrcpy 或 none。Android 默认走 scrcpy,WebCodecs 不可用时走 screenshot。
5.3 SDK 是协议客户端¶
PlaygroundSDK 对前端屏蔽 HTTP 细节,主要方法包括:
| SDK 方法 | Server API | 用途 |
|---|---|---|
checkStatus() |
GET /status |
检查 Server 和读取 id |
getRuntimeInfo() |
GET /runtime-info |
平台、preview、session metadata |
getSessionSetup() |
GET /session/setup |
获取动态设备表单 |
createSession() |
POST /session |
连接设备并创建 Agent |
destroySession() |
DELETE /session |
断开 Agent 和 sidecar |
executeAction() |
POST /execute |
执行 AI 或结构化动作 |
getTaskProgress() |
GET /task-progress/:id |
获取执行快照 |
cancelTask() |
POST /cancel/:id |
取消并重建 Agent |
getScreenshot() |
GET /screenshot |
截图轮询 |
interact() |
POST /interact |
不经过 AI 的直接控制 |
| Recorder 方法 | /recorder/* |
记录人工操作 |
SDK 同时支持 remote execution adapter 和本地 adapter;Android Playground 页面使用 type: remote-execution。
6. 一次自然语言任务如何执行¶
sequenceDiagram
participant UI as Conversation UI
participant SDK as PlaygroundSDK
participant Server as PlaygroundServer
participant Agent as AndroidAgent
participant Core as TaskExecutor
participant Model as AI Model
participant Phone as Android device
UI->>SDK: executeAction(type, prompt, requestId)
SDK->>Server: POST /execute
Server->>Server: 检查 session 和 currentTaskId
Server->>Server: 设置任务锁和 progress slot
Server->>Agent: resetDump()
Server->>Core: executeAction(...)
Core->>Phone: 截图 / 读取界面
Core->>Model: planning / locate
Model-->>Core: 动作和目标
Core->>Phone: ADB Tap/Input/Swipe
loop TaskRunner 快照变化
Core-->>Agent: ExecutionDump
Agent-->>Server: onDumpUpdate
Server->>Server: taskExecutionDumps[requestId] = dump
UI->>Server: GET /task-progress/requestId
Server-->>UI: executionDump
end
Agent-->>Server: result + dump + reportHTML
Server->>Server: 清理 progress slot 和任务锁
Server-->>UI: execute response
6.1 执行锁¶
POST /execute 用 currentTaskId 保证一个 Playground session 同一时间只运行一个任务。已有任务时返回 409 和当前 id。
只有请求带 requestId 时才注册进度 slot 和 onDumpUpdate。任务完成后无论成功失败都删除 slot 并释放锁。
6.2 动作分发¶
Server 读取 Agent 的动态 actionSpace(),然后把 type、prompt、params 交给通用 executeAction()。常见类型可对应:
- AI Act/自然语言规划。
- AI Query/Assert/Extract。
- action space 中的结构化动作。
Playground 不自己理解自然语言,也不根据按钮坐标直接执行;这些逻辑属于 Core 和 Android Agent。
6.3 执行结果¶
/execute 返回:
Server 使用 inline screenshot 生成 response,便于前端直接展示,不依赖 Server 本地报告目录。Agent 自身仍会按配置把正式报告写入 midscene_run/report。
完整报告生成见 report-generation-pipeline.md。
7. AI 配置生命周期¶
前端模型设置由控制器调用 applyPlaygroundAiConfig(),最终请求 POST /config。
Server 的处理策略:
- 空配置不覆盖现有环境变量。
- 对配置做稳定序列化,完全相同时不重复更新。
- 更新全局模型配置后立即执行校验,使错误尽早返回 UI。
- 已有 Agent 时标记
_configDirty=true。 - 下次
/execute前通过agentFactory重建 Agent,使新配置生效。
这种延迟重建避免用户每编辑一个字段就断开设备,但也意味着“配置保存成功”和“当前 Agent 已使用新配置”是两个时间点。
取消任务同样依赖 Agent 重建。对可复用底层页面的平台,取消时可以保留 active stream;Android Agent 重建是否保持预览,取决于独立 scrcpy sidecar 和设备是否仍在线。
8. 实时预览、手动控制和 Recorder¶
Android preview 的数据面与 AI 执行面相互独立:
- scrcpy sidecar:设备 -> H.264 -> Socket.IO -> Canvas。
- 手动控制:浏览器 overlay ->
/interact-> input primitives/action space -> 设备。 - Recorder:监听成功的
/interact,异步采集前后截图和事件语义。 - AI 执行:
/execute-> Core -> Android Agent -> 设备。
这样,AI 执行时预览仍可以持续刷新,手动操作也不需要消耗模型 token。详细链路见 screen-streaming-and-recording.md。
9. Server API 分组¶
| 分组 | API | 说明 |
|---|---|---|
| 健康/运行态 | /status, /runtime-info, /interface-info |
Server、平台、设备能力 |
| 会话 | /session/setup, /session/targets, /session |
动态 setup、连接和断开 |
| AI/动作 | /action-space, /execute, /task-progress/:id, /cancel/:id |
执行和进度 |
| 预览 | /screenshot, /mjpeg |
通用预览;scrcpy 使用 sidecar |
| 人工控制 | /interact |
直接调用设备动作,不经过 AI |
| 操作录制 | /recorder/* |
录制人工操作事件和截图 |
| 配置 | /config, connectivity test |
更新和验证模型配置 |
| 报告上下文 | /playground-with-context, /context/:uuid |
从报告向 Playground 传递上下文 |
Express JSON body 上限为 50 MB,主要用于承载截图、上下文和 inline report 数据。平台化部署时不能把该上限当成上传文件接口,还需要反向代理限流和鉴权。
10. 生命周期与故障恢复¶
10.1 创建失败¶
POST /session 任一步失败时会:
- 销毁已经创建的 Agent。
- 停止已启动的 session sidecars。
- 恢复 prepared platform 的 base state。
- 返回
400和错误消息。
10.2 预览会话关闭¶
截图、MJPEG 或手动操作遇到 “Session closed / target closed” 等可恢复错误时,Server 可以调用 recreateAgent()。恢复成功后当前请求通常提示客户端重试,而不是静默重复可能有副作用的点击。
10.3 取消任务¶
POST /cancel/:requestId 先取得当前 dump/report HTML,再销毁和重建 Agent,最后清理任务锁。取消不是标准 AbortSignal 贯穿整个模型调用,因此 Agent factory 是可靠恢复下一次执行的关键。
10.4 关闭 Server¶
Server 关闭时应停止当前 Agent、session sidecars、HTTP Server 和预览生产者。只关闭浏览器页面不会自动销毁整个 CLI 进程。
11. 平台化扩展建议¶
基于当前架构增加“多项目、多设备测试平台”时,可以保留:
PlaygroundServer作为单设备租约内的执行 API。PlaygroundSDK和现有 Playground App 作为测试控制台。PreparedPlaygroundPlatform作为设备类型接入协议。- 报告 HTML 作为每次运行的可移植产物。
需要额外建设的能力不应塞进 Android 平台描述器:
- 用户和项目鉴权。
- 设备注册、心跳、标签、健康度和排他租约。
- 运行队列、并发策略、超时和取消状态。
- Git 仓库拉取、构建缓存和 APK 安装。
- 报告索引、长期存储和权限。
- Playground/scrcpy 的反向代理和 WebSocket 路由。
当前 CORS 和私网 Origin 校验只能降低误访问风险,不能替代平台认证。
12. 测试覆盖与调试入口¶
相关单元测试:
server-session-manager.test.ts:session schema、创建、销毁和 sidecar。server-interact.test.ts:手动交互和 Recorder。playground.test.ts:执行 API 和 Server 基础行为。multi-platform.test.ts:prepared platform 和多平台元数据。controller-selectors.test.ts:前端 session/controller 状态选择。
排障顺序建议:
GET /status:Server 是否存活。GET /session/setup:ADB 设备是否出现在 targets。GET /session和/runtime-info:Agent/preview 是否已连接。GET /interface-info:size 和 actionTypes 是否正常。- scrcpy sidecar
/api/devices:视频服务是否看到同一设备。 GET /screenshot:绕过实时串流验证 Agent 截图。POST /interact:绕过 AI 验证设备动作。/execute:最后验证模型配置和完整 AI 链路。
13. 关键源码索引¶
| 模块 | 文件 | 责任 |
|---|---|---|
| CLI | bin.ts |
组合平台、Server 和浏览器 |
| Android 平台 | platform.ts |
设备发现、session、preview descriptor |
| scrcpy sidecar | scrcpy-server.ts |
H.264 实时画面和设备列表 |
| 平台启动器 | platform-launcher.ts |
prepared platform -> Server |
| 通用启动器 | launcher.ts |
创建和监听 PlaygroundServer |
| 通用 Server | server.ts |
REST、session、执行、Recorder、静态页 |
| 远程 SDK | sdk/index.ts |
前端到 Server 的协议封装 |
| React 控制器 | usePlaygroundController.ts |
session、配置、在线状态和自动创建 |
| React 页面 | PlaygroundApp.tsx |
左右面板和预览组合 |