这台云手机什么时候能建好?上周同事问我的时候,我正对着一排实体Android设备发呆。他那边有个移动端业务问题需要反复复现,人工点手机不仅慢,界面状态还会被搞乱。我当时的回答就是:别用真机了,直接用 Open-AutoGLM 配合 Redroid 云手机,把整套自动化控制跑在 Ubuntu 22.04 服务器上。折腾了几天之后,这套方案已经稳定跑通,所以把完整的部署过程和中间踩过的坑整理出来。
这套玩法的基本思路很简单:宿主机装 Ubuntu 22.04,Docker 里跑一个 Redroid 容器作为云手机,Open-AutoGLM 通过 ADB 连接这台云手机,用视觉语言模型理解屏幕截图,再自动生成点击、滑动、输入等操作。它解决的痛点很明确:不受实体硬件限制、可以快速批量创建实例、随时清空恢复初始状态。如果你正在做 App 自动化回归、AI 手机 Agent 实验,或者想把企业应用的移动端操作路径自动记录下来,这套环境值得一试。
1. 这套方案到底怎么运转的:Open-AutoGLM 和 Redroid 的分工
1.1 为什么要把自动化控制搬到云手机上
我最早做移动端自动化时,用的都是真机。真机的第一个问题是设备分散,团队里可能有小米、华为、三星,Android 版本从 10 到 14 都有,分辨率密度五花八门,同一个自动化脚本在不同机型上表现完全不同。第二个问题是状态管理困难,跑完一轮测试之后手机界面可能停在某个弹窗上,要么手动复位,要么重新刷机,时间全耗在恢复环境上。第三个问题是没法规模化,想同时验证 5 个账号、5 条业务路径,就得有 5 台设备。
Redroid 这类云手机把这些问题都绕开了。它本质上是一个跑在 Docker 容器里的 Android 系统,没有屏幕、没有实体电池、没有基带,但进程管理、界面渲染、应用安装、ADB 调试这些核心能力都和真机一致。容器可以随时删除重建,重建之后就是一台完全干净的新手机。这点对 Open-AutoGLM 尤其重要,因为 AI Agent 在操作过程中可能会把页面状态搞乱,也有可能点错按钮进入一个奇怪的界面,真机状态下要人工恢复,云手机状态下直接删容器重启,几十秒又是一台干净的设备。
1.2 Open-AutoGLM 在中间扮演什么角色
传统自动化脚本靠的是硬编码坐标和控件 ID,页面只要改版,脚本就废了。Open-AutoGLM 不一样,它是一个“看屏决策”的智能体流程:先对屏幕截图,让多模态视觉模型理解当前界面内容,然后根据用户目标生成下一步动作,比如点击某个文字、输入一段内容、滑动列表,执行完动作之后再截图,形成闭环。
它和 Android 设备的通信走的是 ADB,所以对于 Open-AutoGLM 来说,连接一台 Redroid 云手机和连接一台真机没有任何区别。这也是为什么两者搭配起来非常自然:Open-AutoGLM 只关心设备是否能被 ADB 访问、屏幕是否能正常截图、输入事件是否能注入,至于设备是实体机还是容器,它完全无感。换句话说,Redroid 解决的是“设备从哪来、状态怎么恢复”的问题,Open-AutoGLM 解决的是“怎么自动操作系统”的问题,两者分工非常清晰。
1.3 为什么选 Ubuntu 22.04 LTS 做宿主机
选择 Ubuntu 22.04 LTS 不是随便定的,我对比过其他发行版,最后锁在这一版上。原因有三个:第一,内核版本与 redroid 官方预编译的 binder/ashmem 模块兼容性比较好,Ubuntu 22.04 默认内核在 x86 平台下基本能找到对应模块,省去了自己编译内核模块的麻烦;第二,Docker、NVIDIA 驱动、Python 3.10 这些基础设施在 22.04 下都有成熟稳定的安装路径,Open-AutoGLM 需要 Python 环境,Redroid 需要 Docker 环境,两者都能在同一个系统里平滑共存;第三,Ubuntu 22.04 的维护周期到 2027 年,适合长期跑服务,不用担心系统被废弃。
另外补充一点,Redroid 也可以跑在 ARM 开发板上,比如 RK3588 这类平台,无人机地面站 QGC、激光雷达 SDK 这些行业软件在 Ubuntu 22.04 下也经常出现。也就是说,你用来跑 Open-AutoGLM 的宿主机,完全可以同时承担其他嵌入式开发任务,一套环境多个用途,对做机器人和物联网的人来说很划算。不过本文后面的所有命令和路径都以 x86 Ubuntu 22.04 为准,ARM 平台请另外参考官方说明。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Ubuntu 22.04 需要提前解决的两个硬问题
2.1 GPU 驱动:决定云手机的渲染方式
Redroid 是一个完整的 Android 系统,界面渲染绕不开 GPU 能力。部署之前必须先想清楚宿主机有没有可用的 NVIDIA 显卡,这直接决定了云手机的渲染模式。
如果宿主机有 NVIDIA 显卡,优先做 GPU 直通,渲染速度和流畅度接近真机。安装步骤大致是这样:
bash复制sudo apt update
sudo apt install ubuntu-drivers-common
ubuntu-drivers devices
执行完 ubuntu-drivers devices 之后,系统会列出推荐安装的驱动版本,比如 nvidia-driver-535,然后直接安装并重启:
bash复制sudo apt install nvidia-driver-535
sudo reboot
重启之后用 nvidia-smi 验证驱动是否正常工作。如果能看到显卡信息,说明驱动没问题。后续如果要让容器内直接使用宿主机 GPU,还需要安装 NVIDIA Container Toolkit,并配置 Docker 的 runtime,这一步在启动 Redroid 容器时会用到 --gpus all 参数。
如果宿主机没有独立显卡,或者显卡太老驱动不支持,也不代表不能跑。Redroid 支持 Android 内部的软件渲染方案 SwiftShader,本质是用 CPU 模拟 GPU 的渲染能力。这种情况下不需要安装任何显卡驱动,只需要在启动容器时把 GPU 模式指定为 guest。代价是 CPU 占用会明显升高,低配服务器上界面操作会有延迟感,但用于 Open-AutoGLM 这种以截图和操作为主的场景,完全够用。
2.2 Binder/ashmem 内核模块:Redroid 启动的前提
这是整套部署里最容易卡住的一步。Android 和普通 Linux 最大的差异之一就是进程间通信机制。Android 应用之间不能直接共享内存,所有跨进程通信都要经过 Binder,而匿名共享内存则需要 ashmem。普通 Linux 内核默认不加载这两个模块,Redroid 容器启动时如果访问不到 /dev/binder 和 /dev/ashmem,就会反复重启,日志里全是错误。
在 Ubuntu 22.04 上,先确认内核版本:
bash复制uname -r
然后加载模块:
bash复制sudo modprobe binder_linux devices=binder,hwbinder,vndbinder
sudo modprobe ashmem_linux
加载之后检查设备节点是否存在:
bash复制ls -l /dev/binder* /dev/ashmem
如果能看到 /dev/binder、/dev/hwbinder、/dev/vndbinder 和 /dev/ashmem,说明模块已经就绪。为了让重启后依然生效,需要写两个配置文件。一个放在 /etc/modules-load.d/redroid.conf,内容只有两行:
code复制binder_linux
ashmem_linux
另一个放在 /etc/modprobe.d/redroid.conf,用来给 binder 传递设备参数:
code复制options binder_linux devices=binder,hwbinder,vndbinder
如果你的内核版本比较新,找不到现成的模块,那就需要从 redroid-modules 项目下载对应内核版本的预编译包,或者用 DKMS 从源码编译。我自己的经验是:安装模块包时留意内核 headers 是否齐全,先装 linux-headers-$(uname -r),再编译,否则会报一堆头文件缺失的错误。
3. Redroid 云手机容器:从拉镜像到 adb 能连通
3.1 Docker 环境准备
Redroid 以 Docker 容器方式运行,所以宿主机必须先装好 Docker。Ubuntu 22.04 仓库里自带的 docker.io 版本足够用,不需要额外添加 Docker 官方源。
bash复制sudo apt install docker.io -y
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
执行完 usermod 之后要退出重新登录,让当前用户获得 docker 组权限。如果不想退出,也可以用 newgrp docker 临时切换。后续所有 docker 命令都需要这个权限,否则每次都要 sudo,很影响操作体验。
3.2 宿主目录与容器数据
Redroid 容器默认把 Android 的 /data 分区放在容器内部,一旦删除容器,所有数据都会消失。为了让自动化任务之间有持续状态,或者为了预装一些常用工具,最好把数据目录挂载到宿主机上。
bash复制mkdir -p ~/redroid/data
这个目录会映射到容器内的 /data,是 Android 应用的数据分区。以后不管容器怎么重建,只要挂载目录不变,已安装的应用和登录态都还在。
3.3 启动容器:一条命令里的每个参数
Redroid 官方提供了多个 Android 版本的镜像,我用的比较多的是 Android 11 系列,稳定性很好。启动命令如下:
bash复制docker run -d \
--name redroid11 \
--privileged \
-p 5555:5555 \
-v ~/redroid/data:/data \
redroid/redroid:11.0.0_latest \
androidboot.redroid_gpu_mode=host
如果你没有 NVIDIA 显卡,把最后的 androidboot.redroid_gpu_mode=host 改成 androidboot.redroid_gpu_mode=guest 就行。如果有 N 卡并且配好了 NVIDIA Container Toolkit,可以在 -p 5555:5555 后面加一行 --gpus all,让容器直接使用 GPU 渲染。
每个参数背后的逻辑:
| 参数 | 作用 | 注意事项 |
|---|---|---|
--privileged |
容器获得宿主机内核设备访问权限 | Redroid 需要访问 binder 设备,不能省略 |
-p 5555:5555 |
将容器的 5555 端口暴露给宿主机 | 这是 ADB 连接端口,必须保留 |
-v ~/redroid/data:/data |
持久化 Android 数据分区 | 删除容器后数据依然保留 |
androidboot.redroid_gpu_mode=host |
使用宿主机 GPU 渲染 | 无 GPU 环境改为 guest |
androidboot.redroid_fps=30 |
限制屏幕刷新率 | 可选,降低 CPU 负载 |
容器启动后,先看日志确认没有异常:
bash复制docker logs -f redroid11
看到 Android 系统正常 boot 的日志输出,说明容器起来了。这个时候 Redroid 已经是一台等待 ADB 连接的云手机了。
3.4 用 adb 建立第一层连接
宿主机需要安装 adb 工具:
bash复制sudo apt install adb -y
然后连接云手机:
bash复制adb connect localhost:5555
adb devices
正常情况下会看到一个 localhost:5555 的设备,状态是 device。如果状态是 offline,先不要慌,通常是 adb server 版本或端口冲突问题,后面专门讲。进一步确认系统信息:
bash复制adb -s localhost:5555 shell getprop ro.build.version.release
adb -s localhost:5555 shell getprop ro.product.model
能返回 Android 版本号和机型信息,说明云手机已经可以正常接受控制。
3.5 固定成可重复使用的测试环境
Redroid 和真机有一个很大的区别:它默认没有 Google 服务框架。国内 App 大多不依赖 GMS,所以影响不大,但如果你要跑的自动化任务涉及 Google 系应用,就需要额外处理。另外,Redroid 每次重建容器之后,恢复到的是挂载数据目录里的状态。我习惯在跑重要任务之前先删除旧容器,重新执行一次启动命令,确保环境从干净状态开始。
bash复制docker rm -f redroid11
# 然后重新执行 docker run
如果你需要预装 APK,可以把 APK 文件放在宿主机某个固定目录,启动容器后批量安装:
bash复制adb -s localhost:5555 install /path/to/app.apk
这一步建议写进部署脚本里,做成一个“一键重建云手机”的流程,后面配合 Open-AutoGLM 跑任务时会非常省事。
4. Open-AutoGLM 初始化:模型配置、输入法与权限
4.1 源码安装和 Python 环境
Open-AutoGLM 是开源项目,直接从仓库拉取源码安装。我的推荐流程是:
bash复制git clone <Open-AutoGLM 仓库地址>
cd Open-AutoGLM
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Python 虚拟环境这一步非常重要,因为 Open-AutoGLM 的依赖项比较多,直接装到系统 Python 里容易和别的项目冲突。我用的是 Python 3.10,Ubuntu 22.04 默认就是 3.10,不需要额外处理。具体安装细节以官方仓库 README 为准,因为项目迭代较快,不同版本依赖可能略有变化。
4.2 视觉语言模型的两种接法
Open-AutoGLM 需要多模态模型来“看屏幕”,这一步决定了整个 Agent 的智能程度。目前有两种接法。
第一种是 API 模式,在配置里填上 API Key、模型名称和 Base URL,让它直接调用云端的多模态模型。这种方式最省事,适合自己有密钥、网络连接稳定的场景。模型名称一般用类似 glm-4v 的视觉模型。
第二种是本地模型模式,在服务器上自己部署一套视觉语言模型推理服务,然后让 Open-AutoGLM 调本地接口。这种方式适合截图不想出内网、或者数据敏感的团队,但需要额外维护一套推理服务,对 GPU 显存也有要求。
不管是哪种模式,都要注意截图的分辨率。截图太大,模型容易超 token 限制;截图太小,模型看不清界面文字。Redroid 默认分辨率可能偏高,我建议先用 adb 固定一个通用分辨率:
bash复制adb -s localhost:5555 shell wm size 1080x2340
adb -s localhost:5555 shell wm density 420
固定分辨率的好处不只是让模型看得清,还能让多次运行的截图保持同一布局,任务成功率会更高。
4.3 把 Redroid 设备交给 Open-AutoGLM
Open-AutoGLM 连接设备是走 ADB 的,所以必须先让设备出现在 adb devices 里。前面已经连接过 localhost:5555,在 Open-AutoGLM 的配置文件中,把设备 ID 指定为 localhost:5555 即可。
启动之前可以用这个命令再确认一次连接状态:
bash复制adb -s localhost:5555 shell echo ok
如果输出 ok,说明设备通道正常。Open-AutoGLM 在运行时会接管这个设备的截图和输入注入,所以不要让多个自动化进程同时连接同一台设备,会发生抢设备的问题。我一般是一个任务对应一台云手机,互不干扰。
4.4 输入法与权限(最容易漏)
这是整个部署过程中最容易被忽略的环节。Open-AutoGLM 在输入文本时,不是直接用 adb shell input text,因为那种方式对中文支持非常差。它会通过一个配套的输入法进程完成文本注入,所以必须在云手机里启用并切换到这个输入法。
具体操作大致是:
bash复制adb -s localhost:5555 shell ime enable <输入法包名>/<输入法类名>
adb -s localhost:5555 shell ime set <输入法包名>/<输入法类名>
包名和类名以项目文档为准,不同版本的 Open-AutoGLM 可能不同。如果这一层没配置好,表现就是英文输入正常,中文全部乱码或丢失。
另外,被自动化的应用本身也需要授权。有些权限比如通知权限、悬浮窗权限、无障碍服务权限,可以先用 adb 直接授予。Redroid 是类 AOSP 系统,权限模型比较开放,很多情况下可以直接用 pm grant 授权,省去在界面上手工点击。开发者选项和 USB 调试权限在 Redroid 里默认是开启的,这是它比真机方便的地方。
5. 让 AutoGLM 真正控制云手机的联动工作流
5.1 先跑一个不算太简单的任务
环境都准备好之后,不要一上来就挑战复杂流程,先跑一个能覆盖完整链路的小任务。我推荐的任务是:打开系统设置,把屏幕超时改为 10 分钟,然后返回桌面。
这个任务不复杂,但它需要 Agent 做到几件事:找到设置图标并点击,理解设置页面的结构,进入显示选项,找到屏幕超时,选择 10 分钟,最后返回桌面。任何一个环节出问题,日志里都能看到具体卡在哪一步,非常方便定位是环境问题还是模型理解问题。
5.2 任务执行时的内部闭环
Open-AutoGLM 的执行过程不是一个一次性指令,而是一个循环。正确的理解方式是:
- 通过 ADB 截取当前屏幕:
adb exec-out screencap -p > screen.png - 把截图交给视觉语言模型,模型理解当前界面元素和状态
- 根据用户目标和界面状态,模型输出下一个动作,比如点击某个坐标、输入文字、滑动屏幕
- Open-AutoGLM 把动作转换成 ADB 输入指令或者输入法注入指令
- 等待 1 到 2 秒,让界面稳定下来
- 再次截图,进入下一轮循环
这个闭环里的关键参数是最大步数限制。如果不限制,模型可能会在一个错误状态里反复尝试。我一般设置最大步数为 10 到 15 步,超出了就自动终止并输出当前截图,方便人工判断到底卡在哪。
5.3 命令行调用和批量延伸
Open-AutoGLM 的具体调用方式会随版本变化,但大致结构是这样的:
bash复制python main.py \
--device localhost:5555 \
--task "打开设置,把屏幕超时改为10分钟" \
--model glm-4v \
--max-steps 15
运行之后,日志里会打印每一步的截图路径、模型输出的动作以及执行结果。如果你需要批量验证多个任务,可以把任务列表写进一个脚本,每个任务跑完后删除并重建 Redroid 容器,再跑下一个任务。这样可以避免上一个任务的状态污染下一个任务,也是这套方案相比真机最大的优势。
5.4 提升任务成功率的操作习惯
同一个任务,不同状态下跑出来的成功率差别很大。根据我的实测,下面几个操作习惯能明显提升成功率。
第一,关闭系统动画。动画会让截图抓到中间过渡帧,模型很容易误解当前界面。执行:
bash复制adb -s localhost:5555 shell settings put global window_animation_scale 0
adb -s localhost:5555 shell settings put global transition_animation_scale 0
adb -s localhost:5555 shell settings put global animator_duration_scale 0
第二,固定分辨率。前面已经提过,固定 wm size 和 wm density 能让模型看到的界面布局保持稳定。
第三,任务描述要有明确终点。比如“把屏幕超时改为 10 分钟”,而不是“帮我调整一下设置”。模型对这种含清晰终点的任务理解更准确。
第四,如果模型反复点错位置,优先检查 Open-AutoGLM 是否有物理分辨率和显示分辨率映射的参数。有些情况下截图分辨率和实际注入坐标不是一一对应的,需要调整映射参数,否则坐标会整体偏移。
6. 部署过程中最容易踩的雷
6.1 容器无限重启
这是 Redroid 最常见的问题。现象是 docker run 之后,容器的状态一直显示 restarting,docker logs 里反复出现 binder 相关的错误。
排查思路:先确认 /dev/binder、/dev/hwbinder、/dev/vndbinder、/dev/ashmem 是否存在。如果不存在,大概率是内核模块没有加载成功。再确认启动命令里有没有 --privileged。如果模块正常、权限也有,但还是重启,就检查 modprobe.d 里的 binder 设备参数是不是包含了 hwbinder 和 vndbinder,缺少这两个设备节点同样会导致启动失败。
6.2 adb 连接后一直 offline
adb devices 显示设备存在,但状态是 offline。这个问题通常和 adb server 有关。我遇到过的原因有两个:一个是宿主机 adb 版本和容器内 adbd 版本不兼容,另一个是 localhost:5555 端口被其他进程占用。
处理顺序三板斧:
bash复制adb kill-server
adb start-server
adb disconnect localhost:5555
adb connect localhost:5555
如果还不行,重启 Redroid 容器,并确认 -p 5555:5555 端口映射没有被其他容器占用。
6.3 黑屏或画面异常
容器能启动,adb 也能连上,但截图是黑屏或者花屏。这个问题基本集中在 GPU 模式配置上。有 N 卡但没配好 NVIDIA Container Toolkit,却强行用 host 模式,就可能出现渲染异常;无 GPU 环境用 host 模式也会出问题。
我建议无 GPU 环境老老实实用 androidboot.redroid_gpu_mode=guest,有 GPU 环境先单独验证 GPU 容器能不能用,再启动 Redroid。用 scrcpy 这类工具直接观察云手机画面,可以快速判断渲染是否正常。
6.4 截图时序和黑图
Open-AutoGLM 反馈“看到黑图”或者“画面还没加载完”。这种情况多数不是 GPU 渲染问题,而是截图时序问题。界面切换动画还在播放的时候,截图抓到的就是中间帧,视觉模型自然看不懂。
解决方法就是前面提到过的:关闭系统动画,并在每步动作之后加一个短暂等待。如果任务涉及页面跳转比较重的操作,等待时间可以适当延长到 2 到 3 秒,给应用绘制时间。
6.5 中文输入乱码
Agent 执行输入操作时,英文正常、中文乱码。这个坑的根因就是输入法没有切换对。先检查云手机里当前启用的是哪个输入法:
bash复制adb -s localhost:5555 shell ime list -s
adb -s localhost:5555 shell settings get secure default_input_method
如果默认输入法不是 Open-AutoGLM 配套的输入法,就重新执行 ime enable 和 ime set。另外确认输入法的 APK 确实安装进了 /data 分区,因为如果它只存在容器镜像层里,重建容器后没有重新安装,就会找不到输入法。
6.6 数据脏与重建策略
Redroid 的 /data 分区长期使用后会越来越大,各种缓存、日志、临时文件都堆在里面。自动化任务如果是高频执行,建议不要指望一个容器跑到底。我自己的策略是:每个任务跑完,先备份真正需要保留的数据,比如
