深度探索Uiautomator2(ATX)原理 (附含源码解析)

目录

前言

背景

前置条件

浅谈工作原理

深度探索工作原理

HTTP的请求和返回结构

HTTP命令手机端如何进行传递

atx-agent如何启动

具体调用过程

如何停止atx-agent

atx-agent main.go文件的主要任务

atx-agent如何拉起app-uiautomator-test.apk并进行数据交互

具体调用过程

Python端

​atx-agent端

如何停止uiautomator

app-uiautomator-test.apk中Stub.java文件解析

结束


前言

最近在使用Uiautomator2进行UI的自动化测试,想了解下他的原理和appium有什么不一样,发现一篇浅谈Uiautomator2的原理(浅谈自动化测试工具 python-uiautomator2 · TesterHome)写的不错,就想着要不从代码层面深入的研究下他的原理

背景

UiAutomator是Google提供的用来做安卓自动化测试的一个Java库,基于Accessibility服务。功能很强,可以对第三方App进行测试,获取屏幕上任意一个APP的任意一个控件属性,并对其进行任意操作,但有两个缺点:1. 测试脚本只能使用Java语言 2. 测试脚本要打包成jar或者apk包上传到设备上才能运行。

我们希望测试逻辑能够用Python编写,能够在电脑上运行的时候就控制手机。这里要非常感谢 Xiaocong He (@xiaocong),他将这个想法实现了出来(见xiaocong/uiautomator),原理是在手机上运行了一个http rpc服务,将uiautomator中的功能开放出来,然后再将这些http接口封装成Python库。 因为xiaocong/uiautomator这个库,已经很久不见更新。所以我们直接fork了一个版本,为了方便做区分我们就在后面加了个2 openatx/uiautomator2

相关链接:https://gitcode.net/mirrors/openatx/uiautomator2

前置条件

  • 安装Python3
  • 安装pycharm
  • pip install -U uiautomator2
  • 运行python -m uiautomator2 init(执行作用具体可以参见《python uiautomator2 init 作用这篇文章)

注意:在过去的版本中,这一步是必须执行的,但是从1.3.0之后的版本,当运行python代码u2.connect()时就会自动推送这些文件了

浅谈工作原理

如图所示,python-uiautomator2 主要分为两个部分,python 客户端,移动设备

  • python 端: 运行脚本,并向移动设备发送 HTTP 请求
  • 移动设备:移动设备上运行了封装了 uiautomator2 的 HTTP 服务,解析收到的请求,并转化成 uiautomator2 的代码。

整个过程

  1. 在移动设备上安装atx-agent(守护进程), 随后atx-agent启动 uiautomator2 服务 (默认 7912 端口) 进行监听
  2. 在 PC 上编写测试脚本并执行(相当于发送 HTTP 请求到移动设备的 server 端)
  3. 移动设备通过 WIFI 或 USB 接收到 PC 上发来的 HTTP 请求,执行制定的操作

 以上部分引用自:浅谈自动化测试工具 python-uiautomator2 · TesterHome

深度探索工作原理

HTTP的请求和返回结构

如上图所示,以查找 text为“蓝牙”是否存在为例,通过对uiautomator2源码的断点调试(如何对源码进行调试详见“Pycharm Debug(断点调试)超详细攻略_爱吃甜食的程序员的博客-CSDN博客”),发现请求使用JSON-RPC 轻量级的远程过程调用协议,进行命令和参数的传递,知道传输的HTTP请求体之后,我们要了解下HTTP请求是如何在手机端进行传递的

HTTP命令手机端如何进行传递

如上图所示:手机通过7912端口发送HTTP请求后给atx-agent,atx-agent收到请求后通过9008端口发送给app-uiautomator-test.apk中的AutomatorHttpServer,AutomatorHttpServer根据传递的method参数调用Android 自带的Uiautomator,并将结果进行返回

简单总结下:

7912端口用于Python端和手机端之前的数据交互

9008端口用于atx-agent和Android 自带的Uiautomato的数据交互

知道了HTTP命令是如何在手机端进行传递后,我们逐个的分析下atx-agent和app-uiautomator-test.apk是如何被拉起并进行工作的

atx-agent如何启动

在浅谈工作原理中有说atx-agent 是一个守护进程,那这个守护进程是如何被拉起进行守护的呢?

答案:Python客户端发送adb命令拉起atx-agent

self.shell(self.atx_agent_path, 'server', '--nouia', '-d', "--addr", self.__atx_listen_addr)

`server --nouia`:表示启动 atx-agent,不启动uiautomator。

`-d`:表示将 atx-agent 作为后台进程运行。

`--addr 127.0.0.1:7912`:设定 atx-agent 的监听 IP 地址和端口。

注意:这里只是启动了atx-agent并没有启动uiautomator,uiautomator的启动我们文章的后面会说到详见“atx-agent如何拉起app-uiautomator-test.apk并进行数据交互”和“app-uiautomator-test.apk中Stub.java文件解析”这两个章节

具体调用过程

Python端在发送请求之前会检查atx-agent 是否启动,如果没有就会重新拉起atx-agent,详细的调用路径如下,感兴趣的同学可以自己扒拉代码看下,代码太多我就不全贴了:

uiautomator2._AgentRequestSession.request发送HTTP请求时调用

uiautomator2._BaseClient._prepare_atx_agent()方法,当方法抛出异常时调用

uiautomator2._BaseClient._setup_atx_agent()方法,最终调用

uiautomator2.init.Initer.setup_atx_agent()的方法启动atx-agent

从上面的调用是不是就可以看出来只要我们运行Python代码,这个atx-agent就会一直在,不在就把他拉起

如何停止atx-agent

专门说这个主要是方便大家验证atx-agent被停止后怎么被拉起的

方法一:在ATX.apk中点击“停止ATXAGENT”

方法二:直接使用adb命令停止

adb  shell /data/local/tmp/atx-agent server --stop

启动atx-agent成功后,我们来看看他主要是做什么的,他的任务是什么

atx-agent main.go文件的主要任务

涉及代码:https://github.com/openatx/atx-agent

  • 添加一个反向代理对象,将客户端接收的http请求,转发给127.0.0.1:9008的服务器进行处理
uiautomatorProxy = &httputil.ReverseProxy{
		Director: func(req *http.Request) {
			req.URL.RawQuery = "" // ignore http query
			req.URL.Scheme = "http"
			req.URL.Host = "127.0.0.1:9008"

			if req.URL.Path == "/jsonrpc/0" {
				uiautomatorTimer.Reset()
			}
		},
		Transport: &http.Transport{
			// Ref: https://golang.org/pkg/net/http/#RoundTripper
			Dial: func(network, addr string) (net.Conn, error) {
				conn, err := (&net.Dialer{
					Timeout:   5 * time.Second,
					KeepAlive: 30 * time.Second,
					DualStack: true,
				}).Dial(network, addr)
				return conn, err
			},
			MaxIdleConns:          100,
			IdleConnTimeout:       180 * time.Second,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	}
  • 初始化命令控制功能(cmdctrl.go )并使用关键词添加映射关系:

当远程用户发送命令时,将接收到命令并按照指定的格式进行解析。经过解析后,该文件将会在后台启动一个线程来处理命令

service  = cmdctrl.New()
service.Add("uiautomator", cmdctrl.CommandInfo{
		Args: []string{"am", "instrument", "-w", "-r",
			"-e", "debug", "false",
			"-e", "class", "com.github.uiautomator.stub.Stub",
			"com.github.uiautomator.test/androidx.test.runner.AndroidJUnitRunner"}, // update for android-uiautomator-server.apk>=2.3.2
		//"com.github.uiautomator.test/android.support.test.runner.AndroidJUnitRunner"},
		Stdout:          os.Stdout,
		Stderr:          os.Stderr,
		MaxRetries:      1, // only once
		RecoverDuration: 30 * time.Second,
		StopSignal:      os.Interrupt,
		OnStart: func() error {
			uiautomatorTimer.Reset()
			// log.Println("service uiautomator: startservice com.github.uiautomator/.Service")
			// runShell("am", "startservice", "-n", "com.github.uiautomator/.Service")
			return nil
		},
		OnStop: func() {
			uiautomatorTimer.Stop()
			// log.Println("service uiautomator: stopservice com.github.uiautomator/.Service")
			// runShell("am", "stopservice", "-n", "com.github.uiautomator/.Service")
			// runShell("am", "force-stop", "com.github.uiautomator")
		},
	})
  • 创建TCP端口为7912的监听,用于获取客户端传递的HTTP请求
listener, err := net.Listen("tcp", listenAddr)
  • 使用mux.NewRouter()添加路由器对象,用于处理 HTTP 请求和相应(调用httpserver.go文件中的NewServer()方法)
m := mux.NewRouter()
m.Handle("/jsonrpc/0", uiautomatorProxy)
  • 创建一个 JSON-RPC 客户端用于接收9008端口响应(httpserver.go文件中的NewServer()方法)
	rpcc := jsonrpc.NewClient("http://127.0.0.1:9008/jsonrpc/0")
	rpcc.ErrorCallback = func() error {
		service.Restart("uiautomator")
		// if !service.Running("uiautomator") {
		// 	service.Start("uiautomator")
		// }
		return nil
	}
	rpcc.ErrorFixTimeout = 40 * time.Second
	rpcc.ServerOK = func() bool {
		return service.Running("uiautomator")
	}

atx-agent如何拉起app-uiautomator-test.apk并进行数据交互

还记得我们之前留下的疑问吗?atx-agent是如何启动Uiautomator的?看章节名我们可能会有点蒙圈,不是启动Uiautomator吗怎么启动一个apk了?让我们带着问题继续往下看

首先我们先看下怎么启动这个app-uiautomator-test.apk的

答案:客户端发送命令:'http://127.0.0.1:51392/services/uiautomator'

具体调用过程

Python端

Python端在发送请求时,返回502异常(GatewayError(<Response [502]>, 'gateway error, time used 0.0s'))捕获异常后调用

uiautomator2._BaseClient.reset_uiautomator()方法进行重试在调用

uiautomator2._BaseClient._force_reset_uiautomator_v2()方法在调用

uiautomator2._BaseClient.uiautomator()方法通过uiautomator2._Service来发送请求

atx-agent端

atx-agent 的7912端口监听到命令后cmdctrl.go文件进行解析,使用adb命令拉起app-uiautomator-test包下Stub.java文件:

adb shell am instrument -w -r -e debug false -e class com.github.uiautomator.stub.Stub com.github.uiautomator.test/android.support.test.runner.AndroidJUnitRunner

	m.HandleFunc("/uiautomator", func(w http.ResponseWriter, r *http.Request) {
		err := service.Start("uiautomator")
		if err == nil {
			io.WriteString(w, "Successfully started")
		} else if err == cmdctrl.ErrAlreadyRunning {
			io.WriteString(w, "Already started")
		} else {
			http.Error(w, err.Error(), 500)
		}
	}).Methods("POST")
service.Add("uiautomator", cmdctrl.CommandInfo{
		Args: []string{"am", "instrument", "-w", "-r",
			"-e", "debug", "false",
			"-e", "class", "com.github.uiautomator.stub.Stub",
			"com.github.uiautomator.test/androidx.test.runner.AndroidJUnitRunner"}, // update for android-uiautomator-server.apk>=2.3.2
		//"com.github.uiautomator.test/android.support.test.runner.AndroidJUnitRunner"},
		Stdout:          os.Stdout,
		Stderr:          os.Stderr,
		MaxRetries:      1, // only once
		RecoverDuration: 30 * time.Second,
		StopSignal:      os.Interrupt,
		OnStart: func() error {
			uiautomatorTimer.Reset()
			// log.Println("service uiautomator: startservice com.github.uiautomator/.Service")
			// runShell("am", "startservice", "-n", "com.github.uiautomator/.Service")
			return nil
		},
		OnStop: func() {
			uiautomatorTimer.Stop()
			// log.Println("service uiautomator: stopservice com.github.uiautomator/.Service")
			// runShell("am", "stopservice", "-n", "com.github.uiautomator/.Service")
			// runShell("am", "force-stop", "com.github.uiautomator")
		},
	})

如何停止uiautomator

打开atx.apk点击“停止UIAUTOMATOR”

以上我们就完成了app-uiautomator-test.apk的启动,好像还有一个问题等着我们解答咋调用Android 自带的UIautomator的,继续往下看

app-uiautomator-test.apk中Stub.java文件解析

首先Stub.java是一个 Java 单元测试文件,主要作用是启动一个JsonRpcServer的服务器并监听9008端口,通过AutomatorServiceImpl类对客户端的请求进行响应

@SdkSuppress(minSdkVersion = 18)
@RunWith(AndroidJUnit4.class)
public class Stub {
    private static final int CUSTOM_ERROR_CODE = -32001;
    private static final int LAUNCH_TIMEOUT = 5000;
    int PORT = 9008;
    private final String TAG = "UIAUTOMATOR";
    AutomatorHttpServer server = new AutomatorHttpServer(this.PORT);

    @Before
    public void setUp() throws Exception {
        launchService();
        JsonRpcServer jrs = new JsonRpcServer(new ObjectMapper(), new AutomatorServiceImpl(), AutomatorService.class);
        jrs.setShouldLogInvocationErrors(true);
        jrs.setErrorResolver(new ErrorResolver() {
            /* class com.github.uiautomator.stub.Stub.AnonymousClass1 */

            @Override // com.googlecode.jsonrpc4j.ErrorResolver
            public ErrorResolver.JsonError resolveError(Throwable throwable, Method method, List<JsonNode> list) {
                String data = throwable.getMessage();
                if (!throwable.getClass().equals(UiObjectNotFoundException.class)) {
                    throwable.printStackTrace();
                    StringWriter sw = new StringWriter();
                    throwable.printStackTrace(new PrintWriter(sw));
                    data = sw.toString();
                }
                return new ErrorResolver.JsonError(Stub.CUSTOM_ERROR_CODE, throwable.getClass().getName(), data);
            }
        });
        this.server.route("/jsonrpc/0", jrs);
        this.server.start();
    }
}
  • 重要代码解析

JsonRpcServer jrs = new JsonRpcServer(new ObjectMapper(), new AutomatorServiceImpl(), AutomatorService.class);

- `new ObjectMapper()` 创建了一个 Jackson 序列化/反序列化工具的实例,用于处理 JSON 数据和 Java 对象之间的转换。

Jackson 是一个 Java 序列化工具,其能够自动将 Java 对象序列化为 JSON 格式的数据,并支持将 JSON 数据反序列化为 Java 对象。

- `new AutomatorServiceImpl()` 创建了一个 AutomatorServiceImpl 对象,AutomatorServiceImpl 是 JsonRpcServer 所会调用的具体服务实现类,它实现了 AutomatorService 接口,并调用Android自带的UIautomator,提供了一些通过 JSON-RPC 调用的服务。

    @Override // com.github.uiautomator.stub.AutomatorService
    public boolean exist(String obj) {
        try {
            return getUiObject(obj).exists();
        } catch (UiObjectNotFoundException e) {
            return false;
        }
    }

看红色框框的部分是不是就是Android 已经实现的Uiautomator,到这里我们是不是就可以理解为什么之前说怎么调用Uiautomator的时候我们说的是如何拉起app-uiautomator-test.apk的,因为调用Uiautomator的方法是在app-uiautomator-test.apk这个apk里面的方法实现哒,大家是不是就明白了

- `AutomatorService.class` 是 AutomatorService 接口的定义,它规定了 AutomatorServiceImpl 需要实现的服务方法列表

@JsonRpcErrors({@JsonRpcError(code = -32002, exception = UiObjectNotFoundException.class)})
    boolean exist(String str);

简单总结:

通过以上代码,我们可以创建一台监听 JSON-RPC 请求的服务器,当客户端向该服务器发送JSON-RPC 请求时,服务器会自动将请求反序列化成服务方法的输入参数,并调用 AutomatorServiceImpl 实现相应的服务。服务返回结果会被自动序列化成 JSON 数据,并发送给客户

结束

以上是个人对 uiautomator2 原理的理解,大家有什么不同的看法可以私信我或者评论区留言

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
安装uiautomator2的步骤如下: 1. 首先,确保设备已经连接到电脑,并且可以通过运行命令`adb devices`来发现该设备。这是为了确保设备能够被识别和连接。 2. 使用以下命令来自动安装uiautomator2所需的设备端程序:uiautomator-server,atx-agent,openstf/minicap,openstf/minitouch。 ``` python -m uiautomator2 init ``` 这个命令会在所有通过USB连接到电脑上的手机上安装uiautomator2。 3. 如果你想指定手机安装uiautomator2,并且嫌弃安装过程较慢,你可以使用下面的命令并加上`--mirror`参数: ``` python -m uiautomator2 init --mirror --serial $SERIAL ``` 其中`$SERIAL`是指设备的序列号。 4. 安装uiautomator2库和可选的截图库pillow。你可以使用pip进行安装: ``` pip install --pre uiautomator2 pip install pillow ``` 5. 最后,设备需要安装atx-agent,它可以接收PC端的命令,并对手机的UI进行操作。 这就是安装uiautomator2的步骤。希望对你有所帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [python UIAutomator2使用教程](https://blog.csdn.net/fuhanghang/article/details/123086044)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [uiautomator2 安装使用教程](https://blog.csdn.net/qq_42778518/article/details/126879392)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值