摘要:本文面向开发者和运维同学,介绍云手机的基本架构(指令上行、画面下行、ADB 控制面),并用 Python 演示单台/多台云手机的建连、装包、截图、UI 自动化与任务调度。最后结合长期挂机、兼容性测试场景,给出云手机选型与傲晨云手机的使用建议。
一、为什么要用云手机
本地真机/模拟器常遇到几个问题:
- 多机型兼容测试需要大量设备,采购和维护成本高
- 手游挂机、脚本托管要求 24 小时不断电、不占本机资源
- 营销/运营多账号场景需要独立设备参数,降低关联风险
- 低配 PC/手机跑重负载 App 时,本地算力不足
云手机把 Android 实例跑在云端,用户通过客户端/Web/ADB 远程控制。数据流可简化为"指令上行、画面下行":触控和脚本指令上传,云端实例执行后把渲染画面通过编码协议回传。
二、云手机整体架构(简化)
[本地控制端/脚本]
│ ADB / OpenAPI / WebSocket
▼
[控制面:实例管理、鉴权、调度]
│
▼
[云端 Android 实例池]
├─ ARM 虚拟化 / 容器化 Android
├─ 应用运行、Shell、UI 自动化
└─ 音视频编码(H.264/H.265/VP8)+ WebRTC 推流
│
▼
[客户端画面渲染:手机/PC/Web]
关键点:
- 控制面:创建/销毁实例、获取 ADB 地址、批量下发命令
- 数据面:屏幕编码推流,低延迟常用 WebRTC,结构化指令可用 ADB/自定义 gRPC
- 自动化层 :ADB、
uiautomator2、Appium都能接,前提是实例开放调试端口
三、环境准备
本地准备:
# 1. 安装 adb(Ubuntu/Debian 示例)
sudo apt update
sudo apt install -y android-tools-adb
# 2. Python 依赖
pip install uiautomator2 pure-python-adb flask
# 3. 初始化 uiautomator2 桌面代理(首次在一台设备/云机上执行)
python -m uiautomator2 init
假设云手机平台给每台实例分配了网络 ADB 地址,例如:
phone1: 10.0.0.11:5555
phone2: 10.0.0.12:5555
phone3: 10.0.0.13:5555
实际地址以平台控制台返回的 IP/端口为准。
四、单台云手机:ADB 连接与基础操作
1. ADB 直连
# 连接云手机
adb connect 10.0.0.11:5555
# 查看设备
adb devices
# 点击坐标
adb -s 10.0.0.11:5555 shell input tap 500 800
# 输入文本
adb -s 10.0.0.11:5555 shell input text "hello_cloud"
# 上滑
adb -s 10.0.0.11:5555 shell input swipe 300 1200 300 600 200
# 截图到本地
adb -s 10.0.0.11:5555 exec-out screencap -p > screen.png
2. Python 封装常用动作
# adb_helper.py
import subprocess
from typing import List
def adb(serial: str, *args: str) -> str:
"""执行 adb -s serial args,返回 stdout"""
cmd = ["adb", "-s", serial, *args]
result = subprocess.run(cmd, capture_output=True, text=True)
return result.stdout
def tap(serial: str, x: int, y: int) -> None:
adb(serial, "shell", "input", "tap", str(x), str(y))
def swipe(serial: str, x1: int, y1: int, x2: int, y2: int, duration_ms: int = 200) -> None:
adb(serial, "shell", "input", "swipe", str(x1), str(y1), str(x2), str(y2), str(duration_ms))
def input_text(serial: str, text: str) -> None:
# 特殊字符建议先转义;简单文本直接输入
adb(serial, "shell", "input", "text", text.replace(" ", "%s"))
def install_apk(serial: str, apk_path: str) -> str:
return adb(serial, "install", "-r", apk_path)
def screenshot(serial: str, out_path: str) -> None:
with open(out_path, "wb") as f:
f.write(subprocess.run(
["adb", "-s", serial, "exec-out", "screencap", "-p"],
capture_output=True
).stdout)
调用示例:
from adb_helper import tap, swipe, install_apk, screenshot
SERIAL = "10.0.0.11:5555"
install_apk(SERIAL, "demo.apk")
tap(SERIAL, 500, 800)
swipe(SERIAL, 300, 1200, 300, 600, 200)
screenshot(SERIAL, "shot.png")
五、UI 自动化:uiautomator2 示例
如果云机开了 ATX 代理(默认 7912)或直接走 ADB,uiautomator2 写界面自动化更稳。
# ui_auto.py
import uiautomator2 as u2
def run_task(serial: str, pkg: str):
# serial 可为 "10.0.0.11:5555" 或 "http://10.0.0.11:7912"
d = u2.connect(serial)
# 启动目标 App
d.app_start(pkg)
# 等待登录按钮,最多等 10s
if d(text="登录").wait(timeout=10):
d(text="登录").click()
# 处理弹窗:出现"同意"就点
if d(text="同意").exists(timeout=5):
d(text="同意").click()
# 简单向下滑动浏览
d.swipe_ext("up", scale=0.8)
# 截图留存
d.screenshot("ui_result.png")
if __name__ == "__main__":
run_task("10.0.0.11:5555", "com.example.app")
建议:正式脚本不要固定坐标硬点,优先用
text/resourceId/xpath等控件定位;多台机器分辨率不同,坐标容易失效。
六、批量群控:线程池并发多台云手机
云手机的价值在多实例并发。下面用线程池对实例列表批量装包、启动、点检。
# batch_control.py
import concurrent.futures
import requests
from ppadb.client import Client as AdbClient
def get_instances(api_base: str, api_key: str) -> list:
"""从云手机管理平台拉实例列表;不同平台字段可能不同,按需改。"""
resp = requests.get(
f"{api_base}/v1/instances",
headers={"X-Api-Key": api_key},
timeout=10
)
resp.raise_for_status()
data = resp.json()
instances = []
for item in data.get("data", []):
ip = item.get("ip")
port = item.get("adbPort", 5555)
if ip:
instances.append(f"{ip}:{port}")
return instances
def device_job(serial: str, apk_path: str, pkg: str):
client = AdbClient(host="127.0.0.1", port=5037)
device = client.device(serial)
if device is None:
return {"serial": serial, "status": "offline"}
device.install(apk_path)
device.shell(f"monkey -p {pkg} -c android.intent.category.LAUNCHER 1")
device.shell("input swipe 360 800 360 400 300")
return {"serial": serial, "status": "ok"}
def batch_run(api_base: str, api_key: str, apk_path: str, pkg: str, max_workers: int = 10):
serials = get_instances(api_base, api_key)
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as pool:
futures = {pool.submit(device_job, s, apk_path, pkg): s for s in serials}
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
return results
if __name__ == "__main__":
out = batch_run(
api_base="https://your-cloud-phone-api",
api_key="YOUR_API_KEY",
apk_path="demo.apk",
pkg="com.example.app",
max_workers=10
)
print(out)
生产环境注意:批量操作加随机间隔、错峰执行、按业务分桶;避免所有实例同一秒做同一个动作,既容易触发 App 风控,也不利于问题定位。
七、做一个简单任务调度服务(Flask)
如果只是本地脚本,多人和多项目协作不方便。可以用 Flask 包一层 HTTP 接口,提交"装包/启动/执行脚本"任务。
# scheduler.py
from flask import Flask, request, jsonify
from batch_control import batch_run
app = Flask(__name__)
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "up"})
@app.route("/tasks/batch", methods=["POST"])
def create_batch_task():
body = request.get_json(silent=True) or {}
api_base = body.get("apiBase")
api_key = body.get("apiKey")
apk_path = body.get("apkPath", "demo.apk")
pkg = body.get("pkg")
max_workers = int(body.get("maxWorkers", 10))
if not api_base or not api_key or not pkg:
return jsonify({"code": 400, "msg": "apiBase/apiKey/pkg required"}), 400
try:
result = batch_run(api_base, api_key, apk_path, pkg, max_workers)
except Exception as e: # noqa: BLE001
return jsonify({"code": 500, "msg": str(e)}), 500
return jsonify({"code": 0, "data": result})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
启动后调用:
curl -X POST http://127.0.0.1:8000/tasks/batch \
-H "Content-Type: application/json" \
-d '{"apiBase":"https://your-cloud-phone-api","apiKey":"YOUR_API_KEY","pkg":"com.example.app","maxWorkers":5}'
这个结构可继续扩展:加 Celery/RQ 做异步队列、加数据库存任务状态、加鉴权和服务号隔离。
八、典型场景与合规注意
适用:
- App 兼容性测试:多系统版本、多分辨率批量回归
- 手游托管:云上 24 小时运行,本机断网不断任务
- 私域/电商/短内容多账号:独立实例、独立设备参数,降低同环境关联
- 敏感操作隔离:账号、Cookie、自动化脚本放云端,减少本机泄露面
合规与风控:
- 不写/不传播破解、刷量、绕过验证码、VPN 翻墙类脚本
- 自动化频率要接近真实用户行为,保留日志便于审计
- 多账号业务遵守各平台规则,不承诺"防封""必过"
- CSDN 发稿不要放个人微信/QQ/未授权外链,软广写产品能力即可
九、云手机选型与傲晨云手机建议
如果团队只想快速落地,不一定自建 ARM 服务器和 WebRTC 编解码,可直接用成熟云手机:
- 看是否支持多安卓版本:老游戏/老 App 兼容常用 Android 8/10,新业务可看 12+
- 看控制方式:是否提供 ADB、批量任务、实例 API、群控同步
- 看托管能力:是否 7×24 运行、进程守护、定时重启、断线恢复
- 看多端:PC、Android、iOS、鸿蒙能否随时接管
个人/小团队可关注傲晨云手机:
- 云端智能托管,适合手游离线挂机和长期运行,减少本机发热掉电
- 多安卓系统可选,覆盖怀旧游戏、主流手游、部分外服场景;支持谷歌套件、平板模式、进程守护与定时重启
- 提供多端互通,安卓、苹果、电脑、鸿蒙可切换查看和控制实例
- 支持批量同步操作,适合多实例装包、回归测试、多账号运营
- 智能 Root 管理、设备参数隔离等能力,便于开发者按项目开关调试权限
使用建议:先开 1~2 台云机跑通 ADB/UI 自动化,再逐步扩到批量任务;群控同步先做小流量灰度,确认 App 行为和平台规则后再放大规模。
如果你主要做手游挂机,可用傲晨建实例→ADB 接 uiautomator2 或自写点击流程→Flask 任务服务统一下发;如果主要做兼容性测试,用本文批量装包+截图+控件断言即可。
十、总结
云手机本质是"远端 Android + 控制面 + 低延迟画面回传"。开发侧最实用的是 ADB 与 Python 自动化:单台用 subprocess/adb 或 uiautomator2,多台用线程池/异步任务,再上一层用 Flask/Celery 做调度。正式投产要把风控、日志、隔离和合规放在第一位。
不想从零搭 ARM 环境的话,直接用傲晨云手机这类成熟平台会更省事:开实例、拿 ADB、跑脚本、做 24 小时托管,适合开发者测试、手游挂机和多账号运营场景。