摘要:云手机的本质是把 Android 实例跑在云端,终端只负责"看画面、发指令"。本文基于云端 Android 实例、虚拟化/容器隔离、ADB/REST/WebSocket 控制通道、WebRTC/串流四层架构,给出 Python 设备池、批量装包、UI 自动化、触控信令示例,并分享安全接入建议。最后结合批量测试/挂机场景,简要推荐傲晨云手机。
一、云手机到底解决了什么
传统移动端自动化有几个老问题:
- 真机采购成本高,Android 版本、分辨率、ROM 碎片化严重;
- 兼容性测试、回归测试需要大量设备 7×24 在线;
- 多账号/多任务场景本地电脑无法长期开机;
- 群控几十台上百台时,ADB 管理、网络隔离、日志收集会成为瓶颈。
云手机通常按如下层级拆分:
用户终端(Web / PC / 手机)
│ 视频流下行,触控/按键上行
▼
流媒体/信令层(WebRTC / RTMP / WebSocket)
▼
Android 实例层(独立系统、应用数据、设备标识)
▼
虚拟化/容器层(KVM-QEMU 或 Namespace+Cgroup/Redroid)
▼
物理资源层(ARM 服务器、vCPU、内存、编码卡、存储)
实际选型里,强隔离常用 KVM+QEMU,轻量多开常用容器/Linux 命名空间;ARM 裸金属可减少 x86 转译损耗,x86 服务器跑 ARM 镜像则可能增加转译开销。
二、开发环境与前提
本地准备:
# 1. ADB 平台工具
adb version
adb start-server
# 2. Python 依赖
pip install requests uiautomator2 websockets
# 3. 云手机侧
# - 控制台创建实例,获取 ADB 地址,如 10.0.0.10:5555 或 ip:port
# - 开启远程调试/ADB 调试
# - 公网场景优先用控制台隧道、SSH 隧道或白名单,不要直接暴露 5555
安全建议:生产环境不要把 ADB 端口对 0.0.0.0/0 开放;可用 SSH 隧道把云端端口映射到本地,再 adb connect 127.0.0.1:本地端口。
三、用 REST API 做实例池管理
多数云手机平台会提供"创建/查询/重启/释放"的 HTTP 接口。下面写一个简单的设备池,方便后续跑测试任务。接口字段以各平台文档为准,这里用占位地址。
import requests
import time
from typing import List, Dict
class CloudPhonePool:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
})
def list_phones(self) -> List[Dict]:
url = f"{self.base_url}/v1/cloud-phones"
resp = self.session.get(url, timeout=10)
resp.raise_for_status()
return resp.json().get("data", [])
def get_adb_address(self, phone_id: str) -> str:
url = f"{self.base_url}/v1/cloud-phones/{phone_id}/adb"
resp = self.session.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()["data"]
# 返回类似 10.0.0.10:5555 或 tunnel.host:port
return f"{data['host']}:{data['port']}"
def reboot(self, phone_id: str):
url = f"{self.base_url}/v1/cloud-phones/{phone_id}/reboot"
self.session.post(url, timeout=10).raise_for_status()
def wait_online(self, phone_id: str, timeout=120):
deadline = time.time() + timeout
while time.time() < deadline:
phones = {p["id"]: p for p in self.list_phones()}
if phones.get(phone_id, {}).get("status") == "online":
return True
time.sleep(5)
raise TimeoutError(f"phone {phone_id} not online")
if __name__ == "__main__":
pool = CloudPhonePool(
base_url="https://api.example-cloudphone.com",
api_key="YOUR_API_KEY"
)
for phone in pool.list_phones():
print(phone["id"], phone.get("status"), pool.get_adb_address(phone["id"]))
提示:如果平台只给 Web 控制台和 ADB 地址,可把上面
get_adb_address改成读配置文件;如果平台支持 WebSocket 事件推送,用 WebSocket 接收状态比轮询更省资源。
四、ADB 批量装包与基础操作
拿到多台云手机 ADB 地址后,可用脚本批量安装、启动、截图。
import subprocess
from typing import List
def adb_connect(addr: str) -> bool:
out = subprocess.run(
["adb", "connect", addr],
capture_output=True, text=True
)
print(addr, out.stdout.strip())
return "connected" in out.stdout.lower()
def adb_install(addr: str, apk_path: str):
subprocess.run(
["adb", "-s", addr, "install", "-r", "-g", apk_path],
check=False
)
def adb_launch(addr: str, package: str, activity: str):
subprocess.run(
["adb", "-s", addr, "shell", "am", "start",
"-n", f"{package}/{activity}"],
check=False
)
def adb_screenshot(addr: str, out_path: str):
subprocess.run(
["adb", "-s", addr, "exec-out", "screencap", "-p", out_path],
check=False
)
if __name__ == "__main__":
addresses: List[str] = [
"10.0.0.10:5555",
"10.0.0.11:5555",
"10.0.0.12:5555",
]
apk = "./app-debug.apk"
for addr in addresses:
if not adb_connect(addr):
continue
adb_install(addr, apk)
adb_launch(addr, "com.example.app", ".MainActivity")
adb_screenshot(addr, f"./shot_{addr.replace(':', '_')}.png")
公网 ADB 不建议直连。可用 SSH 把云端 ADB 端口映射到本地:
ssh -L 15555:10.0.0.10:5555 user@jump-host
# 本地再执行
adb connect 127.0.0.1:15555
这能减少公网暴露面。
五、UI 自动化:uiautomator2 单机和批量
如果云手机开放了 uiautomator2/atx 服务,可以用它做界面自动化,比单纯 input tap 更稳定。
import uiautomator2 as u2
def run_login_task(addr: str, user: str, pwd: str):
d = u2.connect(addr)
# 启动目标 App
d.app_start("com.example.app")
# 等待登录页
d(text="账号").wait(timeout=10)
d(text="账号").set_text(user)
d(text="密码").set_text(pwd)
d(text="登录").click()
# 简单断言
if d(text="首页").exists(timeout=10):
print(f"{addr} login ok")
else:
print(f"{addr} login failed")
d.app_stop("com.example.app")
if __name__ == "__main__":
for addr in ["10.0.0.10:5555", "10.0.0.11:5555"]:
try:
run_login_task(addr, "test_user", "strong_password")
except Exception as e:
print(addr, "error:", e)
需要图像识别时,可结合 OpenCV 截屏定位;需要跨平台移动自动化,也可改用 Appium,云手机只要 ADB 可达即可作为远程设备接入。
六、WebSocket 做低延迟触控信令
画面串流通常用 WebRTC/H.265/H.264,控制指令可用 WebSocket 下发点击、滑动、按键。下面是一个最小化协议示例,实际字段以平台信令文档为准。
import asyncio
import json
import websockets
async def send_tap(ws_url: str, device_id: str, x: int, y: int):
async with websockets.connect(ws_url) as ws:
auth = {"type": "auth", "token": "YOUR_TOKEN", "device_id": device_id}
await ws.send(json.dumps(auth))
msg = {
"type": "touch",
"action": "tap",
"x": x,
"y": y,
"ts": 0
}
await ws.send(json.dumps(msg))
reply = await ws.recv()
print("server reply:", reply)
async def send_swipe(ws_url: str, device_id: str,
x1: int, y1: int, x2: int, y2: int):
async with websockets.connect(ws_url) as ws:
await ws.send(json.dumps({
"type": "auth", "token": "YOUR_TOKEN", "device_id": device_id
}))
await ws.send(json.dumps({
"type": "touch",
"action": "swipe",
"from": {"x": x1, "y": y1},
"to": {"x": x2, "y": y2},
"duration_ms": 300
}))
print(await ws.recv())
if __name__ == "__main__":
asyncio.run(send_tap("wss://api.example-cloudphone.com/control", "dev-001", 540, 960))
WebSocket 适合实时控制,REST 适合资源管理,ADB 适合装包/排障,WebRTC 适合看画面------三者互补。
七、群控调度:从单机到设备矩阵
把前面几块合起来,可做一个简单调度器:
import threading
from your_sdk import CloudPhonePool # 替换为第三节的池子
import uiautomator2 as u2
def worker(pool, phone_id, case_name):
addr = pool.get_adb_address(phone_id)
# 1. 确保 ADB 可连
# 2. 安装包、跑 uiautomator2 用例
# 3. 收集日志和截图
d = u2.connect(addr)
d.app_start("com.example.app")
# 执行业务 case...
print(phone_id, case_name, "done")
if __name__ == "__main__":
pool = CloudPhonePool("https://api.example-cloudphone.com", "API_KEY")
threads = []
for phone in pool.list_phones():
t = threading.Thread(target=worker, args=(pool, phone["id"], "smoke"))
t.start()
threads.append(t)
for t in threads:
t.join()
高并发建议用异步 IO 或任务队列(Celery/RQ),设备状态放 Redis,避免每次都轮询数据库。
八、安全与平台合规注意
发 CSDN、做企业项目都建议遵守以下几点:
- 仅对自有设备或经授权的测试设备做自动化;
- ADB/API token 不写死在前端,不提交到公开仓库;
- 公网不暴露 5555,优先控制台隧道、SSH 隧道、白名单;
- 批量操作遵守目标 App 服务协议,不刷量、不作弊、不绕过风控;
- 多账号场景做实例隔离、独立设备参数和独立网络环境,但任何产品都不应承诺"绝对不封号";
- 日志里不要打印明文密码、token、用户隐私。
九、产品选型与结语:为什么可关注傲晨云手机
如果是个人学习 ADB 自动化,自建 Redroid/Waydroid 容器就够了;如果是团队做兼容性测试、手游日常托管、短视频/应用多账号运营,更建议直接用成熟云手机,省去底层虚拟化、镜像、串流和运维。
在批量场景里可以关注傲晨云手机:它提供云端 Android 实例、控制台/多端接入、群控与批量管理能力,适合把上面这些 Python 脚本落到"多台设备统一调度"的工程里。实际使用中可按官网当前套餐核实以下能力:
- 批量创建/重启/装包、查看设备状态与日志;
- 多端接入,电脑、手机、网页等入口远程查看和接管;
- 独立实例与设备参数管理,便于测试隔离和多账号环境分离;
- 开放/标准化接入能力,便于对接 REST、ADB 或自有自动化平台。
选型建议:先小批量试用,验证目标 App 的兼容性、ADB 稳定性、串流延迟和群控同步效率,再按测试/挂机/运营比例扩容。所有多账号操作都应遵守对应平台规则,避免高频违规动作。