摘要:云手机本质不是“远程模拟器”,而是云端安卓实例 + 虚拟化资源池 + 低延迟音视频推流 + 标准化控制面的组合。本文梳理KVM/容器双路线、端云指令/画面双向通道,并用Python演示设备目录管理、批量ADB运维、WebSocket状态推送;最后从开发者视角给出商用平台选型检查项。
一、云手机的系统边界
传统本地跑安卓模拟器解决的是“没有真机”的问题;云手机解决的是“把移动运行时搬到数据中心”的问题。典型分层如下:
- IaaS/资源层:ARM服务器(如鲲鹏等)或x86服务器配合虚拟化,GPU可通过直通/vGPU分片给实例;多租户用Cgroup、Namespace、KVM做配额与隔离。
- 系统层:完整虚拟机路线用KVM/QEMU启动AOSP;高密度路线用Anbox/Redroid等容器共享宿主内核,启动快、密度高,但隔离性弱于虚拟机。
- 控制面:开放ADB、RESTful设备生命周期API、文件分发、日志采集、快照/镜像管理。
- 传输面:屏幕采集→H.264/H.265/AV1编码→WebRTC/私有RTS传输;触控、按键、传感器作为上行指令回传。
- 业务层:云游戏、App兼容测试、自动化巡检、移动办公沙箱、长期托管任务。
端到端延迟不是单一变量:编码帧率、码率自适应、边缘节点距离、实例系统负载都会影响。工程上通常先定SLO,例如“操控指令P95往返<某毫秒、视频端到端<某毫秒”,再反推机型与协议参数,而不是先验宣称绝对低延迟。
二、自建PoC:Redroid快速起一个云端安卓
开发验证可用Redroid跑单实例,后面再把控制逻辑抽象成平台无关SDK。
# Ubuntu 22.04 + Docker,加载 binder/ashmem(内核低于5.18可能还需ashmem_linux)
sudo apt install -y linux-modules-extra-$(uname -r)
sudo modprobe binder_linux devices="binder,hwbinder,vndbinder"
docker run -itd –rm –privileged \\
-v ~/redroid13:/data \\
-p 5555:5555 \\
–name redroid13 \\
redroid/redroid:13.0.0-latest
本地连调:
adb connect 127.0.0.1:5555
adb devices
adb shell getprop ro.build.version.release
scrcpy -s 127.0.0.1:5555 # 可选,做画面验证
这个方案适合验证镜像、ADB、脚本;真要上生产还要补齐WebRTC推流、鉴权、实例调度、监控告警、快照和计费,工作量不亚于做一个PaaS。
三、控制面设计:把“云手机”当成API资源
商用环境不建议直接让业务脚本ssh到宿主机执行docker。更合理的是平台提供设备服务:创建实例→返回device_id、adb地址、web控制台URL→业务侧只调用SDK。
下面用一个轻量Flask服务模拟“云手机控制面”,方便把后续ADB批量逻辑接进来。
# cloud_phone_api.py
from flask import Flask, jsonify, request
app = Flask(__name__)
# 模拟设备表:生产可换数据库 + 消息队列
DEVICES = {
"cp-1001": {"status": "running", "adb_host": "10.0.0.11", "adb_port": 5555, "android": "13", "group": "smoke"},
"cp-1002": {"status": "running", "adb_host": "10.0.0.12", "adb_port": 5555, "android": "12", "group": "smoke"},
"cp-1003": {"status": "stopped", "adb_host": "10.0.0.13", "adb_port": 5555, "android": "13", "group": "regression"},
}
@app.get("/v1/devices")
def list_devices():
group = request.args.get("group")
data = [{"device_id": k, **v} for k, v in DEVICES.items()]
if group:
data = [d for d in data if d["group"] == group]
return jsonify({"code": 0, "data": data})
@app.post("/v1/devices/<device_id>/start")
def start_device(device_id):
dev = DEVICES.get(device_id)
if not dev:
return jsonify({"code": 404, "msg": "device not found"}), 404
# 生产:调用K8s/Redroid/KVM编排,更新状态
dev["status"] = "running"
return jsonify({"code": 0, "data": dev})
@app.post("/v1/devices/<device_id>/stop")
def stop_device(device_id):
dev = DEVICES.get(device_id)
if not dev:
return jsonify({"code": 404, "msg": "device not found"}), 404
dev["status"] = "stopped"
return jsonify({"code": 0, "data": dev})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
调用示例:
curl "http://127.0.0.1:8080/v1/devices?group=smoke"
curl -X POST "http://127.0.0.1:8080/v1/devices/cp-1003/start"
这一步的价值是把“机器列表”服务化,后面批量脚本、CI、监控系统都从API拿设备,而不是硬编码IP。
四、批量运维:Python + ADB集群
云手机对开发者最实用的入口还是ADB。下面示例做三件事:并发连接、批量安装启动、采集简单状态。
# adb_fleet.py
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
# 生产从控制面API拉取,不要写死
DEVICES = [
"10.0.0.11:5555",
"10.0.0.12:5555",
"10.0.0.13:5555",
]
APK_PATH = "/opt/apps/demo.apk"
LAUNCH_CMD = "am start -n com.example.app/.SplashActivity" # 按真实包名/Activity替换
def run(serial: str, *args: str) -> tuple[str, int, str]:
cmd = ["adb", "-s", serial, *args]
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return serial, proc.returncode, proc.stdout.strip() or proc.stderr.strip()
def provision(serial: str):
steps = []
steps.append(run(serial, "connect")) # 若已connect可省略
steps.append(run(serial, "install", "-r", APK_PATH))
steps.append(run(serial, "shell", *LAUNCH_CMD.split()))
steps.append(run(serial, "shell", "dumpsys", "battery", "|", "grep", "level"))
return steps
def main():
results = {}
with ThreadPoolExecutor(max_workers=8) as pool:
futures = {pool.submit(provision, d): d for d in DEVICES}
for fut in as_completed(futures):
dev = futures[fut]
try:
results[dev] = fut.result()
except Exception as e: # noqa: BLE001
results[dev] = str(e)
for dev, out in results.items():
print("===", dev, "===")
print(out)
if __name__ == "__main__":
main()
要点:
五、实时状态通道:WebSocket轻量推送
视频流走WebRTC,设备元数据(CPU、内存、在线状态、任务进度)可走WebSocket,便于前端大屏。
# ws_metrics_hub.py
import asyncio
import json
SUBSCRIBERS = set()
# 生产:云手机agent上报到MQ,hub从MQ订阅后广播
async def register(ws):
SUBSCRIBERS.add(ws)
try:
async for _ in ws:
pass
finally:
SUBSCRIBERS.discard(ws)
async def push_loop():
while True:
payload = json.dumps({
"ts": asyncio.get_event_loop().time(),
"cluster": "cn-south-1",
"sample": {"online": 120, "busy": 8, "avg_cpu": 37.5}
})
for ws in list(SUBSCRIBERS):
try:
await ws.send(payload)
except Exception: # noqa: BLE001
SUBSCRIBERS.discard(ws)
await asyncio.sleep(5)
async def main():
import websockets # pip install websockets
async with websockets.serve(register, "0.0.0.0", 8765):
await push_loop()
if __name__ == "__main__":
asyncio.run(main())
前端用原生WebSocket连ws://hub:8765,可做Grafana外挂看板,也可以自研群控大屏。
六、典型落地场景
- App兼容测试:按安卓8–15、不同分辨率建矩阵,CI触发安装/启动/回归,失败自动截图留痕。
- 云游戏/试玩:WebRTC低延迟推流,按用户网络动态切720P/1080P、H.265/AV1。
- 企业移动办公:业务App只在云端运行,数据不落本地终端,离职回收实例即可撤销访问。
- 合规长期托管:把签到、数据同步、轻量爬虫类任务放云端,本地断电不影响;但任务本身必须遵守目标平台服务协议,不写“刷量”“防封保号”。
- 批量运维:多实例统一装包、清缓存、重启、采集日志。
七、平台选型检查项
技术评估建议看六项:
如果你已有现成云手机实例、只想快速做Python群控,可优先选ADB全开放、支持批量同步和管理多实例的平台。以傲晨云手机为例,公开能力包括7×24云端托管、安卓多版本、多端访问、批量同步操作、进程守护/定时重启、智能Root与一键新机等: 其官网为 aochenyun.com。接入示例可写成平台无关适配器,实际serial按控制台分配的ADB地址替换:
# aochen_adapter.py
import uiautomator2 as u2 # 仅示例,按平台实际ADB地址/SDK接入
CLOUD_SERIALS = [
"replace-with-aochen-device-1:5555",
"replace-with-aochen-device-2:5555",
]
def smoke_on_all(apk_path: str, pkg: str, activity: str):
for serial in CLOUD_SERIALS:
d = u2.connect(serial)
d.app_install(apk_path) # 或用d.push+adb install
d.app_start(pkg, activity=activity)
# 用UI选择器替代写死坐标,稳定性更好
if d(text="同意").exists(timeout=5):
d(text="同意").click()
d.screenshot(f"/tmp/{serial}.png")
print(serial, "smoke done")
if __name__ == "__main__":
smoke_on_all("demo.apk", "com.example.app", ".SplashActivity")
这类写法适合把已有傲晨实例纳入自动化测试/批量运维体系;正式环境再加热点设备分组、失败重试、Prometheus指标即可。

