跳转至

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 框架:

  1. @midscene/android-playground 发现 ADB 设备、创建 AndroidDevice/AndroidAgent,并声明 scrcpy 预览。
  2. @midscene/playground 提供 Express Server、会话状态、执行锁、AI 配置、Recorder、报告和 REST API。
  3. @midscene/playground-app 提供 React UI、远程 SDK、会话表单、对话执行、实时预览和手动控制。
  4. ScrcpyServer 是 sidecar,只负责设备列表和 H.264 实时画面,不负责 AI 规划。
  5. 真正执行自然语言任务的是 AndroidAgent 和 Core TaskExecutor;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 入口:

  1. 创建 ScrcpyServer 实例,但暂不监听端口。
  2. 调用 androidPlaygroundPlatform.prepare()
  3. 把 prepared platform 交给 launchPreparedPlaygroundPlatform()
  4. Playground Server 启动后输出 URL 和 server id。
  5. 使用系统默认浏览器打开 Playground。

3.2 prepare 阶段

androidPlaygroundPlatformdefinePlaygroundPlatform() 定义的平台描述器。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 /executecurrentTaskId 保证一个 Playground session 同一时间只运行一个任务。已有任务时返回 409 和当前 id。

只有请求带 requestId 时才注册进度 slot 和 onDumpUpdate。任务完成后无论成功失败都删除 slot 并释放锁。

6.2 动作分发

Server 读取 Agent 的动态 actionSpace(),然后把 typepromptparams 交给通用 executeAction()。常见类型可对应:

  • AI Act/自然语言规划。
  • AI Query/Assert/Extract。
  • action space 中的结构化动作。

Playground 不自己理解自然语言,也不根据按钮坐标直接执行;这些逻辑属于 Core 和 Android Agent。

6.3 执行结果

/execute 返回:

1
2
3
4
5
result      动作本身的返回值
dump        ReportActionDump
error       格式化后的执行错误,成功时为 null
reportHTML  当前执行的可独立打开 HTML 字符串
requestId   任务 id

Server 使用 inline screenshot 生成 response,便于前端直接展示,不依赖 Server 本地报告目录。Agent 自身仍会按配置把正式报告写入 midscene_run/report

完整报告生成见 report-generation-pipeline.md

7. AI 配置生命周期

前端模型设置由控制器调用 applyPlaygroundAiConfig(),最终请求 POST /config

Server 的处理策略:

  1. 空配置不覆盖现有环境变量。
  2. 对配置做稳定序列化,完全相同时不重复更新。
  3. 更新全局模型配置后立即执行校验,使错误尽早返回 UI。
  4. 已有 Agent 时标记 _configDirty=true
  5. 下次 /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 任一步失败时会:

  1. 销毁已经创建的 Agent。
  2. 停止已启动的 session sidecars。
  3. 恢复 prepared platform 的 base state。
  4. 返回 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 平台描述器:

  1. 用户和项目鉴权。
  2. 设备注册、心跳、标签、健康度和排他租约。
  3. 运行队列、并发策略、超时和取消状态。
  4. Git 仓库拉取、构建缓存和 APK 安装。
  5. 报告索引、长期存储和权限。
  6. Playground/scrcpy 的反向代理和 WebSocket 路由。

当前 CORS 和私网 Origin 校验只能降低误访问风险,不能替代平台认证。

12. 测试覆盖与调试入口

相关单元测试:

排障顺序建议:

  1. GET /status:Server 是否存活。
  2. GET /session/setup:ADB 设备是否出现在 targets。
  3. GET /session/runtime-info:Agent/preview 是否已连接。
  4. GET /interface-info:size 和 actionTypes 是否正常。
  5. scrcpy sidecar /api/devices:视频服务是否看到同一设备。
  6. GET /screenshot:绕过实时串流验证 Agent 截图。
  7. POST /interact:绕过 AI 验证设备动作。
  8. /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 左右面板和预览组合