PhoneFast-EN

PhoneFast-EN

Have you ever encountered these issues?

  1. adb keeps failing to select the right element or selects the wrong one, causing Vibe Coding to burn through tokens🔥🔥🔥🔥🔥.
  2. adb dump xml fails, forcing you to rely solely on screenshots for verification — but the model is single-modal😖😖😖😖

phonefast: Precisely cracks the four deadly pain points of Harness Coding in mobile verification — Slow, Inaccurate, Token-burning🔥, Unstable — broken one by one.

Pain PointSolutionEffect
🐢 SlowDaemon resident process + Unix Socket JSON‑RPC<10ms touch latency, 100x faster than ADB shell
🎯 InaccurateAtomic observe, screenshot + UI tree returned in one callCompletely eliminates the race window of “UI changed after screenshot”
🔥 Token burningMCP native ImageContent, multimodal direct outputNo more stuffing dozens of KB of base64 into JSON, tokens halved
🛡️ UnstableThree-level keepalive + auto-reconnect + panic self-heal12-hour stress test, 140k+ operations, 99.99% success rate

📺 Video Comparison

phonefast 4x speed demo

Click to watch the full comparison video: 【PhoneFast vs PhoneMCP】AI Execution Comparison

Installation

1
npx skills add gezihua123/phonefast-skill --skill phonefast-skill

1. Architecture Comparison

phonefast (Go + scrcpy)

flowchart LR
    CLI[phonefast CLI] -->|" Unix Socket JSON-RPC (<1ms)"| DAEMON[Daemon Resident Process]
    DAEMON -->|" TCP (scrcpy protocol)\nPersistent Connection"| SERVER[scrcpy-server\nAndroid Device]

    subgraph DAEMON_INTERNAL[Daemon Internals]
        direction TB
        SESSION[Session Holder]
        CTRL[control socket]
        VIDEO[video socket]
        UI[ui socket]
        SESSION --- CTRL
        SESSION --- VIDEO
        SESSION --- UI
    end

    DAEMON -.-> DAEMON_INTERNAL

    subgraph DEVICE[Android Device]
        direction TB
        VS[video socket]
        CS[control socket]
        US[UI socket]
        SERVER --- VS
        SERVER --- CS
        SERVER --- US
    end
  • Language: Go compiled native binary, startup <10ms
  • Connection: scrcpy protocol, TCP tunnel directly to scrcpy-server on device
  • Daemon: Background resident process, holds persistent device connection, receives commands via Unix Socket
  • Cold start: <10ms (Go native binary)
  • Command latency: daemon mode <1ms socket communication + ~12ms screenshot decoding (astiav CGO in-process) + Android processing

agent-device (TypeScript + ADB)

flowchart LR
    CLI[agent-device CLI] -->|" Startup ~500ms\nNode.js"| NODE[Node.js Process]
    NODE -->|" subprocess\nnew adb process each time"| ADB[adb shell]
    ADB --> DEVICE[Android Device]

    subgraph SESSION_STATE[Session Management]
        direction TB
        STATE["session state persisted to disk\n~/.agent-device/sessions/"]
        OPEN["open → records app state"]
        CMD["command → reuses session context"]
    end

    CLI -.-> SESSION_STATE
  • Language: TypeScript (Node.js CLI), startup ~500ms
  • Connection: Raw ADB commands (adb shell input/keyevent/screencap/uiautomator)
  • Session: App state persisted to disk after opening, session context reused between commands
  • Cold start: ~500ms (Node.js process startup)
  • Command latency: ~450-750ms (Node.js process + adb shell)

adb kill (Python + ADB)

flowchart LR
    CLI[adb kill run] -->|" Cold start ~6-8s\nPyInstaller extraction"| PY[Python Interpreter]
    PY -->|" subprocess\nnew process each time"| ADB[adb shell]
    ADB --> DEVICE[Android Device]

    subgraph COLD_START[Cold Start Overhead]
        direction TB
        UNPACK["① PyInstaller extraction ~1s"]
        IMPORT["② Python module import ~2-3s"]
        DETECT["③ ADB device detection ~1s"]
        UNPACK --> IMPORT --> DETECT
    end

    CLI -.-> COLD_START
  • Language: Python (packaged as single file via PyInstaller, extracted at runtime)
  • Connection: Raw ADB commands (adb shell input/keyevent/screencap/uiautomator)
  • State: Stateless, each command goes through full “start → execute → exit” flow
  • Cold start: ~6-8s (PyInstaller extraction + Python module import + ADB detection)
  • Command latency: ~7-9s (extraction ~1s + import ~2-3s + ADB ~1s + subprocess ~2s + parsing ~0.5s)

2. Speed Comparison

Test Environment: macOS arm64Go 1.26Node.js v22.20agent-device v0.17.6phonefast v1.0.11
Device: TECNO KL8h (USB)Resolution 488×1080Test Date: 2026-07-14

Method: Average of 3 runs per operation, perl -MTime::HiRes full-chain timing; phonefast data from 12-hour stress test (145,843 operations)

Operationphonefast daemonagent-deviceadb killvs agentvs adb
back12ms520ms8,505ms43x709x
home13ms550ms8,864ms42x682x
tap coordinate click13ms748ms8,110ms58x624x
swipe (300ms)318msN/A¹8,200ms26x
type_text1ms32,700ms²7,890ms32,700x7,890x
screenshot28ms2,593ms8,939ms93x319x
UI elements46msFAILED²7,600ms165x
observe (screenshot+UI)28msN/A~15,500ms³554x
launch app1ms782ms⁴8,240ms782x8,240x

Typical AI Agent Interaction Loop

xychart-beta
    title "Observe→Act→Re-observe Single Cycle (seconds)"
    x-axis ["phonefast daemon", "agent-device", "adb kill"]
    y-axis "Time (seconds)" 0 --> 30
    bar [0.4, 3.9, 24.0]
xychart-beta
    title "20 Cycles Total Time (seconds)"
    x-axis ["phonefast daemon", "agent-device", "adb kill"]
    y-axis "Time (seconds)" 0 --> 500
    bar [8, 78, 480]

3. Architectural Dimension Comparison

Dimensionphonefastagent-deviceadb kill
LanguageGo (native binary)TypeScript (Node.js)Python (PyInstaller)
Binary Size11MB~3MB (npm)41MB
Running Memory<62MB RSS~30-50MB RSS~20-40MB (new process each time)
Cold Start<10ms~500ms~7s
Connection Methodscrcpy protocol (TCP tunnel)ADB commandsADB commands
Daemon Mode✅ Resident process + Unix Socket✅ session-state on disk❌ Cold start each time
Command Latency1-13ms450-750ms7-9s
Screenshot Methodscrcpy H.264 keyframe → astiav CGO decode PNGadb screencap → pull PNGadb screencap → pull PNG
UI ParsingUISocketHandler (TCP socket)uiautomator dumpuiautomator dump
UI Stability⭐⭐⭐⭐⭐⭐⭐ (uiautomator often times out)⭐⭐⭐
Persistent Connectionscrcpy server resident on deviceNo persistent connectionNo persistent connection
Session ManagementDaemon in-memoryState persisted to diskStateless
Disconnect RecoveryThree-level keepalive, auto-reconnect in 10sSession state file recoveryStateless
MCP Protocol✅ SSE / STDIO (8019)agent-device mcp✅ SSE / STDIO (8009)
Cross-PlatformAndroid onlyiOS / Android / TV / DesktopAndroid only
ImageContent✅ (MCP native)

4. Feature Comparison

Featurephonefastagent-deviceadb kill
tap coordinate click
swipe custom coordinates❌ (preset directions only)
type_text
screenshot✅ (H.264→PNG)✅ (screencap)✅ (screencap)
UI elements (xml)✅ UISocketHandler
observe (screenshot+UI)✅ (atomic operation)
tap_element✅ (MCP mode)
launch_app
batch executionrun JSONbatchrun JSON
MCP serverserve (8019)mcpserve (8009)
ImageContent✅ (MCP native)
non-ASCII input✅ DEX helper
multi-platform✅ iOS/Android/TV

5. Implementation Principles

5.1 Session Lifecycle

flowchart TD
    C1["Step 1: Deploy scrcpy-server.jar"] --> C2["Step 2: Kill old server instance"]
    C2 --> C3["Step 3: Allocate ports video/UI"]
    C3 --> C4["Step 4: Start scrcpy-server"]
    C4 --> C5["Step 5: ADB forward video socket"]
    C5 --> C6["Step 6: Connect video socket + read dummy byte"]
    C6 --> C7["Step 7: Connect control socket"]
    C7 --> C8["Step 8: Read H.264 video header → resolution"]
    C8 --> C9["Step 9: Wait for UISocketHandler ready 600ms"]
    C9 --> C10["Step 10: ADB forward UI socket + probe"]
    C10 --> C11["Start drainFrames() background goroutine"]

5.2 Screenshot Pipeline (v1.0.11 Architecture)

v1.0.11 refactored the screenshot pipeline from ffmpeg subprocess to astiav CGO in-process decoding, eliminating subprocess creation + pipe I/O overhead, reducing screenshot latency by 3-4x.

The ffmpeg subprocess fallback path is retained (CGO_ENABLED=0 auto-switch).

flowchart TD
    DEVICE["Android device video stream H.264"] -->|TCP| SOCKET["scrcpy video socket"]
    SOCKET -->|drainFrames background goroutine| NAL["NAL unit parsing: SPS/PPS/IDR"]
    NAL --> CACHE["LRU cache latest keyframe"]
    NAL --> REQ["requestKeyframe() when missing\nsend RESET_VIDEO control frame"]

    CACHE -->|"keyframeToPNG()"| CHOICE{"CGO_ENABLED?"}

    CHOICE -->|"Default: yes"| ASTIAV["astiav.Decoder\n(in-process CGO)"]
    ASTIAV --> CTX["CodecContext\nThreadCount=1\npersistent reuse"]
    CTX --> DEC["SendPacket + ReceiveFrame"]
    DEC --> SWSCALE["sws_scale\nH.264→RGBA"]
    SWSCALE --> ENC["astiav encode to PNG bytes"]
    ENC --> MC["MCP ImageContent\n{type:image, data, mimeType}"]

    CHOICE -->|"Fallback: no"| FFMPEG_CLI["ffmpeg CLI subprocess\nexec.CommandContext"]
    FFMPEG_CLI --> STDIN["stdin: -f h264 -i pipe:0"]
    STDIN --> STDOUT["stdout: -vcodec png pipe:1"]
    STDOUT --> MC

Two Path Comparison:

DimensionMain Path (astiav CGO)Fallback Path (ffmpeg CLI)
TriggerCGO_ENABLED=1 (default build)CGO_ENABLED=0 (cross-compile, etc.)
Decode MethodIn-process C function callfork + exec subprocess
Data TransferZero-copy memory pointer passingpipe stdin → stdout (memcpy ×2)
Codec ContextPersistent reuse (DPB retained)New process each time (SPS/PPS re-parsed)
ThreadsThreadCount=1Default multi-thread
Screenshot P5028ms 🚀~100-200ms
Cold Start Screenshot~19ms~167ms
External DependenciesNone (FFmpeg statically linked)System requires ffmpeg installed

Why single thread is faster:

  • 488×1080 single-frame decoding is tiny; multi-thread slicing sync overhead > decode itself
  • Multi-thread causes DPB (Decoded Picture Buffer) double allocation, memory bloat
  • ThreadCount=1 eliminates slice-merge and inter-thread sync, more stable latency

Why persistent context is faster than new:

  • Each SendPacket(nil) flush reinitialization → +55ms (SPS/PPS re-parse + DPB rebuild)
  • Persistent context reuses previous frame’s reference buffer, new IDR naturally overwrites old
  • No flush = zero extra overhead

Why keyframes:

  • I-frames (IDR/Keyframe) contain the complete picture, can be decoded independently
  • P/B-frames only contain delta data, depend on reference frames
  • Screenshots must use I-frames; when missing, RESET_VIDEO triggers device to generate one immediately

5.3 UI Element Retrieval

flowchart TD
    GET["GetUIElements()"] --> FAST{"UISocketHandler available?"}
    FAST -->|"Fast path"| SOCKET["TCP connect UI socket"]
    SOCKET --> SEND["Send dump request"]
    SEND --> XML["Receive XML"]
    XML --> PARSE["Parse UIElement list"]
    FAST -->|"Fallback ADB"| ADB["adb shell uiautomator dump"]
    ADB --> PULL["pull XML file"]
    PULL --> PARSE

phonefast’s UISocketHandler is a custom extension of scrcpy-server, providing UI dump service via abstract socket, approximately 40% faster than uiautomator dump.

5.4 Keepalive & Disconnect Recovery

flowchart TD
    subgraph L1["1. TCP Keepalive"]
        CK["control socket: 15s"]
        VK["video socket: 30s"]
    end

    subgraph L2["2. healthLoop 10s cycle"]
        ALIVE["IsAlive() check"]
        VD["videoDied channel closed?"]
        CE["controlErr is nil?"]
        ALIVE --> VD
        ALIVE --> CE
    end

    subgraph L3["3. Write Failure Detection"]
        WF["Write() fails"]
        MCB["markControlBroken()"]
        WF --> MCB
    end

    L1 --> L2
    L2 -->|"Disconnect detected"| RECONNECT["reconnect() auto-reconnect"]
    L3 -->|"Write failure"| RECONNECT

5.5 Daemon Mode

sequenceDiagram
    participant CLI as phonefast CLI
    participant DS as Unix Socket
    participant DM as Daemon
    participant SESS as Session
    participant DEV as Android Device

    Note over DM: Startup: connect device + create socket + healthLoop

    CLI ->> DM: ensureDaemon() check/start
    CLI ->> DS: JSON-RPC tap(x=540, y=960)
    DS ->> DM: dispatch "tap"
    DM ->> SESS: Tap(540, 960)
    SESS ->> DEV: Touch Down
    SESS ->> DEV: Touch Up
    DEV -->> SESS: ok
    SESS -->> DM: ok
    DM -->> CLI: {"result":"Tapped at (540, 960)"}

5.6 MCP ImageContent Return

1
2
3
4
5
6
{
  "content": [
    {"type": "text",      "text": "Screenshot (1080x2400)"},
    {"type": "image",     "data": "iVBORw0KGgoAAA...", "mimeType": "image/png"}
  ]
}

6. Long-duration Stress Test (v1.0.11 Optimized)

12-hour continuous stress test, 145,843 operations, 100% success rate, zero disconnections.

MetricValue
Duration720 minutes (12 hours)
Total Operations145,843
Successful145,843
Failed0
Success Rate100%
Daemon Disconnects0
Performance Degradation❌ None
RSS Peak<62MB

12 Operation Latency Overview:

OperationCountP50P95P99AvgMax
tap49,94313ms13ms14ms12ms453ms
back16,63913ms13ms14ms12ms474ms
home16,64213ms13ms14ms12ms18ms
press_key16,65013ms13ms14ms12ms18ms
swipe8,384318ms322ms323ms318ms821ms
screenshot4,18528ms126ms128ms49ms132ms
observe4,18828ms126ms129ms51ms134ms
get_ui_elements4,18946ms132ms151ms61ms192ms
type_text4,1881ms1ms2ms1ms6ms
launch_app4,1871ms1ms2ms1ms5ms
status4,1891ms1ms2ms1ms3ms
wait12,45933ms33ms33ms32ms38ms

7. Use Cases

phonefast daemon → AI Agent First Choice

  • High-frequency AI Agent interaction (observe→act→re-observe loop)
  • Requires ultra-low latency (<30ms)
  • Batch automation scripts
  • Requires MCP ImageContent native image return
1
2
3
4
phonefast daemon                              # Start (one-time only)
phonefast --daemon tap 540 960                # Tap (13ms)
phonefast --daemon screenshot /tmp/s.png      # Screenshot (28ms)
phonefast --daemon observe                    # Screenshot+UI (28ms)
1
2
3
4
5
Primary:   phonefast daemon  (Speed King, Android AI Agent First Choice)
           + phonefast serve  (MCP mode, includes tap_element)

Supplemental: agent-device  (when iOS automation / recording replay / performance sampling needed)
              adb kill      (when OCR / non-ASCII input / package search needed)

Why phonefast is More Stable

1
2
3
4
5
6
7
phonefast:  scrcpy-server resident on device, TCP connection sustained for 12+ hours
            daemon in-memory session, zero state reconstruction overhead between commands
            three-level keepalive (TCP keepalive + healthLoop + write failure detection)

agent-device/adb kill: new adb shell subprocess for each command, destroyed after use
                       no persistent connection, read session or cold start each time
                       command failure = error, no auto-recovery

热门标签