作者介绍:胡嘉椿,来自货拉拉/技术中心/质量保障部,专注于移动测试效能方向。
一、背景
随着货拉拉在同城、跨城、企业、搬家、零担等业务以及全球化的高速发展,移动端测试团队在质量保障体系建设上也日趋完善,我们在功能、性能、兼容、稳定、埋点、安全等测试方向上都有了更深入的探索与实践。因此,为了更好的支撑公司移动端的质量建设,移动云真机平台应运而生,不仅更好的支持移动端自动化测试,同时也满足了各地手机资源的共享需求。本文主要介绍我们在云真机平台的建设过程中能力建设与落地实践上的一些经验。
二、云真机的架构演进之路
货拉拉在移动云真机平台的建设上经历了2个阶段,主要是基于不同时期业务的实际使用要求逐步发展变迁。在初期设备数量不多,我们采用的是无线ADB模式,后来由于业务的飞速发展,对设备需求量大幅增长,为了支持大规模应用,我们使用了分布式集群多模部署架构。整个演进过程可以分为:
1.0时代: 目标是把各业务线的分散设备集中于平台来调度,平台提供基础的远程服务。测试机白天由测试人员进行手工测试,晚上接入云真机平台执行自动化测试。
2.0时代: 目标是打造公司级标准真机实验室,平台需要考虑机房基建、远程真机访问时的使用体验是否流畅,有统一的设备调度管理系统,无人值守自动化运维等。
1、1.0时代
在1.0时期,各地可接入的设备数量并不多,大部分都是测试人员手头用于业务测试的测试机,只有晚上或者空闲时才会接入自动化测试或共享使用,因此在这个背景与需求下,我们衍生出了货拉拉云真机1.0的架构。
1.1 基于无线ADB的方案
-
客户端(client) :用于发送命令,客户端在开发机器上运行。我们可以通过发出 adb 命令从命令行终端调用客户端。
-
服务器(server) :用于管理客户端与守护程序之间的通信。服务器在开发机器上作为后台进程运行来管理设备。
-
守护程序 (adbd) :用于在设备上运行命令,守护程序在每个设备上作为后台进程运行,也是作为指令接收者和给JVM消息的传递者。
从工作图可以知道,其实ADB工作是可以通过有线数据线(USB)和WIFI(TCP)两种方式来传输的数据。无论是通过哪种模式传输,都必须通过server来管理连接的设备。货拉拉云真机1.0基于ADB的WIFI模式,设计出完全脱离数据线的云真机平台,方便手机资源的快速共享。
1.2 部署拓扑图
从设备部署的拓扑图可以了解到,每个职场的手机通过WIFI ADB的方式共享出来,同时手机内启动一个类似Agent服务,负责图像流和控制流的传输给各地办公网的用户,各地用户通过浏览器(Http、WS)直连手机的Agent服务,以此达到实时投屏、操控的目的。
1.0的方案架构虽然简单,但特别适合少量机器部署及本地带宽足够大的场景,其优势主要有:
-
减少了很多硬件成本(主机、USB hub等);
-
用户端和手机直连,用户和手机在同网段时不涉及流量转发,中间流程少;
-
手机共享方便,可以通过脚本自动在手机内部署Agent;
-
手机可以不用长时间连接数据线,最大程度防止锂电池过充。
但随着业务的飞速发展,需要适配的设备机型和数量也与日俱增,云真机1.0的方案的缺点也逐步暴露出来:
-
数据传输过于依赖WIFI,单局域网内AP负载过重,容易造成断流;
-
手机充当Agent服务,负载过重,性能损耗过大,真机使用时体验不稳定;
-
单台设备游离状态,没有形成体系化统一运维管理,维护成本随着设备增加而不断变大。
2、2.0时代
为了彻底解决云真机1.0遇到的问题,经过一年的打磨,我们又重新规划和建设了云真机2.0平台,并取名为天宫系统。
天宫系统的核心能力主要有:
-
保留1.0的WIFI模式,分散在各业务线的手机,也可随时接入系统共享。
-
为了保证全球各地用户远程操控云真机投屏流畅度,从通信模式上采用Web和Agent直连方式,底层技术方面,运用直播投屏技术,减少对本地带宽依赖。
-
为了能高效地批量管理、维护真机设备,保证设备的高可用,设计运维服务中心,由Agent分管设备资源,Master统一维护指令,并建设无人值守体系来提升维护效率和设备的稳定性。
-
为了能充分和高效地利用好真机池,设计设备的任务调度中心,目前已经加入预约机制,任务排队机制、分组动态扩容,设备推荐等业务定制化功能,后续也将加入智能化调度,合理的利用好真机池。
2.1 天宫系统架构解析
基于1.0架构的优势和不足,天宫系统完成对1.0架构的服务剥离和升级,主要是衍生出Master Service和Agent Service两大服务体系。Master和Agent各就其职,系统架构上相互解耦,数据相互依赖,解耦是剥离出1.0的Server相关内容,依赖是真机的相关数据从Agent的心跳上报至Master,让1.0处于游离状态的手机统一收归Master管理及维护,并由Master分发调度到各业务方。
2.1.1 Master Service
Master Service的架构体系如上图所示:
-
访问层:通过暴露的接口鉴权及获取真机的使用权限;
-
前端:云真机远程调试,远程操控和投屏使用;
-
MTC:移动云测MTC平台(自研移动端一站式测试平台)自动化占用,包括各种自动化测试场景:Monkey、性能测试、UI自动化、兼容性测试等;
-
Agent:通过Agent 的Websocket Client于Server传递消息;
-
<
-
服务层:
-
设备大盘:机房设备大图,展示设备调度情况、设备在线、设备分布等;
-
消息服务:负责监听Agent心跳消息及设备层指令的消息传递;
-
<
-
运维服务:设备维护中心,手机信息维护、Agent维护、设备上架、下架,设备异常监控、巡检、告警等,提供无人值守自动化运维的能力;
例如增加设备监控,发现故障后实时生成告警信息,触发对应的自愈流程,保证设备的高可用。
-
调度中心:设备调度服务中心,管控设备预约、分发,所有设备使用唯一出口;
例如预约功能,按任务优先级确定单台设备的调度顺序,优先保证高优先级的任务先行。
2.1.2 Agent Service
了解Agent架构图,Agent集成了Android和iOS两大主流平台的云真机能力,即使后续有其它第三大平台,依托Agent的架构优势,定制方案后也可快速接入。目前Agent主要功能是图像的编码推流及控制层消息转发及响应。Agent作为手机图像输出和控制输入的桥梁,以数据线作为媒介,高速且高效为用户提供服务。当前用户通过Master Service的鉴权和调度后,通过浏览器直连Agent的控制流和图像流,以此完成实时投屏及实时控制。
Agent Service部署我们采用Docker的方式,对比其它部署方式:
-
依赖资源包管控比较容易,所有依赖全部配置在dockerfile文件内;
-
实现更轻量级的虚拟化,方便快速构建和部署,全球各集群版本升级方便;
容器启动时,需要主动挂载系统USB:
Android:/dev/bus/usb:/dev/bus/usb
iPhone:/var/run/usbmuxd:/var/run/usbmuxd
2.1.3 部署拓扑图
天宫系统架构下的云真机,Agent Service 完全从1.0时期的手机服务中剥离,部署在宿主机中。单个Agent管理N台手机,可以批量管理资源的初始化。深圳集群沿用1.0架构下的WIFI接入模式,其它区域无需部署Agent也可以快速接入;全球各地的职场,如果有条件可以单独部署Agent甚至是多台形成集群加入到Master Service集中管理及运维,各地职场可以访问各个集群的云真机。云真机2.0 的接入和中国天宫空间站一样,可以以搭积木的方式对接各个国家的载人航天飞船,以此达到资源共享。Master Service类比中国空间站核心舱,Agent Service类比各国的载人飞船,这也是云真机2.0取名为“天宫”的寓意:接入快捷,资源共享。
三、核心解决方案
1 、视频流技术带来的流畅体验
针对于Android云真机的投屏技术,目前业界有视频流为代表的Scrcpy和图片流为代表的STF minicap两种方案,货拉拉云真机2.0架构下,经过实验和数据对比,综合考虑下,保留了两种投屏方案。
1.1 安全及高效的视频流
Android MediaCodec是低层编解码接口,同时支持音视频的编码和解码。能够将视频编解码成H.264、H.265等视频格式。我们采用业界通用的视频流方案Scrcpy,通过MediaCodec获取到Surface作为视频输入源,这样效率更高,因为Surface使用的更底层的视频数据,不会映射到ByteBuffer缓冲区,减少了缓冲的过程。MediaCodec的初始化的同时,可以设置视频的mime类型、帧率、码率等,可以根据当前网络情况自动调整视频流的质量,从而给用户带来不一样的体验。
configure(codec, format);
surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
codec.start();
alive = encode(codec, fd);
codec.stop();
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US);
MediaCodec将视频编码成H.264格式的数据后,通过LocalSocket将编码完成的视频流源源不断的推送至Agent,由Agent启动的WS Server端转发至用户浏览器Client端并实时播放;我们没有采用1.0方案(手机与用户直连的方式)原因是:
-
避免和用户操作手机时产生的流量和图像流、控制流的资源抢占,数据传输不稳导致视频卡顿;
-
数据线传输对比无线方式,速率快并且稳定,USB3.0的接口最大传输带宽高达5.0Gbps(500MB/s),不存在延迟问题或者几乎可以忽略;
1.2 视频流和图片流的对比
对比STF minicap的图像传输方式,MediaCodec的视频流有以下几个优势:
-
MediaCodec作为官方提供的主流的推流接口,版本升级后产生的适配工作小,一般Android大版本升级并不影响,除非一些极少数厂商的定制Rom需要适配。而minicap是采用调用Android底层基于NDK开发C++的API,需要在不同Android版本源码环境下编译出不同CPU架构的对应so包才能使用,对于普通开发者来说,适配成本比较大。
-
相等清晰度下,视频流的H.264帧数据对比minicap的JPEG,编码效率及压缩质量更高;H.264最大能达1:100的压缩比。相对来讲,每帧数据量越大,同等帧率下,消耗的带宽是更大的,而本地带宽资源是有限的,对用户来讲,即使设备支持了60帧率的流推送,但是由于带宽限制无法在单位时间内传输完并渲染,造成的结果就是卡顿;而视频流极大的减少单帧的大小,同时码率可以控制视频质量,进而单位时间内传输效率就更高,用户的感官就流畅丝滑。
视频流帧数据的局部传输:
图片流帧数据:
在实际使用中,我们采用两种方案播放相同视频,视频流对比图片流,节约了80%的带宽,极大降低了对用户本地网络带宽的依赖。
实验数据:
方案 | 播放视频时长 | 参数 | 结果 |
---|---|---|---|
Scrcpy视频流 | 1分钟 | Bit Rate:3000000,FPS:60 | 7.8MB |
minicap图片流 | 1分钟 | Quality:80,FPS:60 | 40MB |
当然,minicap对比视频流对设备的性能消耗较小,但是为了保证视觉的流畅度,而且用户本地带宽的不确定性情况下,目前视频流技术还是综合考量下的最优方案。由于iOS系统的限制,还是采用基于WDA的远控方案,目前还没有切换视频流,但是由于视频流的编码优势,后续尝试将图片编码成视频流来传输。
2、InputManager带来的顺滑体感
云真机的主要是完成两个功能,一个是把真机屏幕实时投屏到浏览器,另一个就是把用户在浏览器的操控实时的发送到手机并完成事件响应。光是屏幕的丝滑还不能够,必须同时满足:操控的实时和屏幕的实时。屏幕的实时我们已经通过视频流技术完成升级,而怎么把用户在浏览器操控实时传给手机并响应呢?
2.1 ADB的input方案
Usage: input [<source>] [-d DISPLAY_ID] <command> [<arg>...]
The sources are:
touchnavigation
touchscreen
joystick
stylus
touchpad
gamepad
dpad
mouse
keyboard
trackball
-d: specify the display ID.
(Default: -1 for key event, 0 for motion event if not specified.)
The commands and default sources are:
text <string> (Default: touchscreen)
keyevent [--longpress|--doubletap] <key code number or name> ... (Default: keyboard)
tap <x> <y> (Default: touchscreen)
swipe <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
draganddrop <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
press (Default: trackball)
roll <dx> <dy> (Default: trackball)
motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)
看下adb input的使用说明,支持基本的点击、滑动、拖拽、长按,足够满足了我们基本的操控要求。
-
tap<x><y>
-
swipe<x1><y1><x2><y2>[duration(ms)]
-
draganddrop<x1><y1><x2><y2>[duration(ms)]
但是实验得知通过adb方案直接操控手机,由于adb的通信原理(client-server-adbd)流程过长,所以每个action都会有比较大的延迟,这对于云真机对实时响应的高要求下并不是最佳方案。
2.2 STF Minitouch方案
STF Minitouch方案很棒,通过forward端口转发的形式把用户的action实时同步到手机的minitouch服务,由minitouch完成事件的注入。
adb forward tcp:1111 localabstract:minitouch
但是,minitouch和minicap一样是基于Android NDK开发,适配成本大,而且Android Q因为USB 输入权限问题已经不支持使用了;但是minitouch的通信方案和实现思路我们可以借鉴。
2.3 基于InputManager的高速点击方案
InputManager是Android提供事件注入接口,我们常用的UI自动化测试工具Uiautomator和稳定性测试工具Monkey都是通过InputManger完成的事件注入;效率高且稳定安全,基本不用考虑新版本适配的问题。我们可以参考minitouch的事件源定义我们需要的事件类型:
-
点击
d 0 10 10 50
c
u 0
c
-
长按
d 0 10 10 50
c
<wait in your own code>
u 0
c
-
滑动
d 0 0 0 50
c
<wait in your own code> //需要拖拽加上等待时间
m 0 20 0 50
c
m 0 40 0 50
c
m 0 60 0 50
c
m 0 80 0 50
c
m 0 100 0 50
c
u 0
c
然后根据事件源的类型完成对应的事件注入,这里是Monkey的事件源和事件注入方式,可以参考使用:
@Override
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
MotionEvent me = getEvent();
if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
StringBuilder msg = new StringBuilder(":Sending ");
msg.append(getTypeLabel()).append(" (");
switch (me.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
msg.append("ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
msg.append("ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
msg.append("ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
msg.append("ACTION_CANCEL");
break;
case MotionEvent.ACTION_POINTER_DOWN:
msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));
break;
case MotionEvent.ACTION_POINTER_UP:
msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));
break;
default:
msg.append(me.getAction());
break;
}
msg.append("):");
int pointerCount = me.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
msg.append(" ").append(me.getPointerId(i));
msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");
}
Logger.out.println(msg.toString());
}
try {
if (!InputManager.getInstance().injectInputEvent(me,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
return MonkeyEvent.INJECT_FAIL;
}
} finally {
me.recycle();
}
return MonkeyEvent.INJECT_SUCCESS;
}
3、实际体验
四、效果实践
1、标准机房建设
一个完整的云真机体系,有软件层面演进优化,也必不能少硬件方面的基建改造和升级。
a. 机房建设方面,公司相关专业部门给我们出谋划策,网络、AP、强电、弱电、安防等硬件设施都是按照业界标准建设,甚至连机柜摆放都得到了专业指导,在货拉拉各部门的通力协作下,一个24h恒温、恒电的机房建设完成。
b. 安防方面,也正在接入公司的安全监控系统,配合云真机体系的无人值守运维能力保证设备的高可用,打造安全、专业的国际化机房。
c. 设备方面,已经具备了丰富的真机资源,今年已部署上线200+设备,手机品牌包括苹果、华为、小米、OPPO、vivo、Honor、魅族、realme等主流品牌及其他小众品牌共10+,覆盖Android6~Android13,iOS 10 ~iOS 16,鸿蒙2.0 ~3.0。同时,我们每个月都会统计各业务线的Top机型及系统版本,持续更新和丰富设备池,我们预计在2023年设备数量将达到400+。
2、应用推广
云真机平台建设的初心是与移动云测平台的能力结合,将其功能、性能、兼容性、稳定性、埋点等自动化测试能力基于云真机更稳定高效的去执行,为移动App的质量赋能,提升测试效率及节省测试人力。随着天宫系统核心能力及操作体验持续升级,我们也获得了研发、产品、运营等角色的青睐,帮助他们在代码调试、需求验收及与对外运营等使用场景下,通过云真机更高效的完成工作。
当前天宫系统已有10+业务线接入,覆盖公司全球各地的技术团队,远程真机调用总时长近12w小时,每天支持1000+次数的设备调度任务。
五、未来展望
天宫系统除了以上核心能力建设外,还在设备调度管理、自动化运维等方面也有一些实践,这次由于篇幅关系没有展开来讲,后续有机会我们会再详细介绍。对于未来,天宫系统会在智能化和精细化方向持续优化打磨:
-
基于设备调度任务的数据分析,采用智能化技术更合理、更精准地调度设备;
-
深入研究iOS的体验优化,例如优化iOS的投屏的方案来提升iOS的操作体验;
-
进一步完善无人值守体系,多维度拓展自动化运维的能力,最大程度减少人工维护的成本。
本篇文章来源于微信公众号:货拉拉技术
本文来自投稿,不代表TakinTalks稳定性技术交流平台立场,如若转载,请联系原作者。