从⼀个Uiautomator的官⽅demo说源码
⾸先我们先看⼀个官⽅给的uiautomator2的例⼦
# coding: utf-8
import uiautomator2 as u2
def main():
u = u2.connect_usb()
u.app_start('comease.cloudmusic')
u(text='私⼈FM').click()
u(description='转到上⼀层级').click()
u(text='每⽇歌曲推荐').click()
u(description='转到上⼀层级').click()
u(text='歌单').click()
u(description='转到上⼀层级').click()
u(text='排⾏榜').click()
u(description='转到上⼀层级').click()
if __name__ == '__main__':
main()
由于云⾳乐版本的变化 可能这⾥⾯有些跑不通的情况,不过这个不是重点。从代码上⾄少我们⼤概能明⽩整个流程。 ⾸先连接设备获取到设备对象后。开始对查的⽂本或者描述信息进⾏查后点击。
⾸先我们看这个代码不是解释这个例⼦要怎么写。⽽是想通过这个例⼦来看看uiautomator2背后的过程。
现在我们⼀步步的进⾏分析看看。
⾸先我们先从u = u2.connect_usb()从这⾥能看出来,因为通过这个⽅法会返回⼀个uiautomator的对
象,后续的元素定位等都是通过这个对象进⾏的, 所以我们需要知道具体这个⽅法到底做了什么?
def connect_usb(serial=None):
"""
Args:
serial (str): android device serial
Returns:
UIAutomatorServer
"""
# 实例化了adb的对象,同时做了⼀个端⼝转发将本地某个端⼝(lport)的tcp数据转发到⼿机上的7912端⼝上,⽽⼿机上监听这个端⼝的服务实际上就是atx-age nt。
# 同时调⽤⼀个connect_wifi的⽅法我们看到UIAutomatorServer的实例化对象是由这个⽅法返回的
# 同时判断agent是否已启动,若没有则⼿动拉起agent。
adb = adbutils.Adb(serial)
lport = adb.forward_port(7912)
d = connect_wifi('127.0.0.1:' + str(lport))
if not d.agent_alive:
warnings.warn("backend atx-agent is not alive, start again ...",
RuntimeWarning)
# TODO: /data/local/tmp might not be execuable and atx-agent can be somewhere else
adb.shell("/data/local/tmp/atx-agent", "server", "-d")
deadline = time.time() + 3
while time.time() < deadline:
if d.alive:
break
elif not d.alive:
warnings.warn("backend uiautomator2 is not alive, start again ...",
RuntimeWarning)
return d
def connect_wifi(addr=None):
"""
Args:
addr (str) uiautomator server address.
Returns:
UIAutomatorServer
Examples:
connect_wifi("10.0.0.1")
"""
# 这⾥的代码就⽐较简单了,就是对参数地址做补全,然后实例化了UIAutomatorServer
if '://' not in addr:
addr = '' + addr
if addr.startswith(''):
u = urlparse.urlparse(addr)
host = u.hostname
port = u.port or 7912
return UIAutomatorServer(host, port)
else:
raise RuntimeError("address should start with ")
⽽具体UIAutomatorServer类的构造函数如下:
class UIAutomatorServer(object):
__isfrozen = False
__plugins = {}
# 从构造函数看的话,没有看出具体的内容,只是做了⼀些初始化的⼯作⽽已。但是这⾥⽐较关键的是实例化了Session这个类 def __init__(self, host, port=7912):
"""
Args:
host (str): host address
port (int): port number
Raises:
EnvironmentError
"""
self._host = host
self._port = port
self._reqsess = TimeoutRequestsSession(
) # use requests.Session to enable HTTP Keep-Alive
self._server_url = '{}:{}'.format(host, port)
asp查看源码配置uiself._server_jsonrpc_url = self._server_url + "/jsonrpc/0"
self._default_session = Session(self, None)
self._cached_plugins = {}
self.__devinfo = None
self._hooks = {}
self.platform = None # hot fix for weditor
self.ash = AdbShell(self.shell) # the powerful adb shell
self.wait_timeout = 20.0 # wait element timeout
self.click_post_delay = None # wait after each click
self._freeze() # prevent creating new attrs
# self._atx_agent_check()
我们再来看看Session这个类的构造函数
class Session(object):
__orientation = ( # device orientation
(0, "natural", "n", 0), (1, "left", "l", 90),
(2, "upsidedown", "u", 180), (3, "right", "r", 270))
# 这⾥的server实际上就是uiautomatorServer的实例化对象,相当于在session这个类中完全能够调⽤uiautomatorServer所有暴露的⽅法。 # 另外这⾥⾯的jsonrpc,以及shell其实是两个⽐较关键的点,但是这⾥我们先不介绍因为下⾯具体的执⾏还是会涉及到的
def __init__(self, server, pkg_name=None, pid=None):
self.server = server
self._pkg_name = pkg_name
self._pid = pid
self._jsonrpc = server.jsonrpc
if pid and pkg_name:
jsonrpc_url = server.path2url('/session/%d:%s/jsonrpc/0' %
(pid, pkg_name))
self._jsonrpc = server.setup_jsonrpc(jsonrpc_url)
# hot fix for session missing shell function
self.shell = self.server.shell
另外回到connect_usb函数的逻辑中
判断atx-agent是否在线
@property
def agent_alive(self):
# 通过发送http请求获取axt-agent的版本,若获取失败则 atx-agent启动失败
try:
r = self._(self.path2url('/version'), timeout=2)
return r.status_code == 200
except:
return False
那么我们再看看 u.app_start('comease.cloudmusic')这个⽅法主要做了什么
def app_start(self,
pkg_name,
activity=None,
extras={},
wait=True,
stop=False,
unlock=False):
""" Launch application
Args:
pkg_name (str): package name
activity (str): app activity
stop (bool): Stop app before starting the activity. (require activity)
"""
# 这⾥判断设备是锁屏状态,如果是的话则解锁操作
if unlock:
self.unlock()
# 这⾥判断是否带有activity的参数如果有的话则启动指定的activity
if activity:
# -D: enable debugging
# -W: wait for launch to complete
# -S: force stop the target app before starting the activity
# --user <USER_ID> | current: Specify which user to run as; if not
# specified then run as the current user.
# -e <EXTRA_KEY> <EXTRA_STRING_VALUE>
# --ei <EXTRA_KEY> <EXTRA_INT_VALUE>
# --ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE>
args = ['am', 'start', '-a', 'android.intent.action.MAIN',
'-c', 'android.intent.category.LAUNCHER']
if wait:
args.append('-W')
if stop:
args.append('-S')
args += ['-n', '{}/{}'.format(pkg_name, activity)]
# -e --ez
extra_args = []
for k, v in extras.items():
if isinstance(v, bool):
d(['--ez', k, 'true' if v else 'false'])
elif isinstance(v, int):
d(['--ei', k, str(v)])
else:
d(['-e', k, v])
args += extra_args
# 'am', 'start', '-W', '-n', '{}/{}'.format(pkg_name, activity))
self.shell(args)
else:
# 若没有则通过shell 命令运⾏monkey的命令将对应的app 唤起这个⽅式很有意思
if stop:
self.app_stop(pkg_name)
self.shell([
'monkey', '-p', pkg_name, '-c',
'android.intent.category.LAUNCHER', '1'
])
下来则到了我们的真正重要的内容了 u(text='私⼈FM').click()到这⾥的话我们⾸先要注意⼀个事情,因为前⾯我们有讲过connect_usb返回的是⼀个uiautomator的对象,⽽这⾥的调⽤是将⼀个类对象通过函数传参的⽅式进⾏调⽤了。所以这⾥会涉及到⼀个⽅法叫__call__()
call
所有的函数都是可调⽤对象。⼀个类实例也可以变成⼀个可调⽤对象,只需要实现⼀个特殊⽅法__call__()
所以我们这⾥需要看的是UIAutomatorServer的__call__⽅法的实现了。
# 从这⾥我们⼜可以看到这⾥实际上⼜调⽤了Session这个类的__call__⽅法
def __call__(self, **kwargs):
return self._default_session(**kwargs)
这⾥顺便普及下python中*args和**kwargs
*args表⽰任何多个⽆名参数,它是⼀个tuple;**kwargs表⽰关键字参数,它是⼀个 dict。
所以u(text='私⼈FM')实际上就是个关键字传参,所以kwargs 实际上就是 {text: ‘私⼈FM’}
我们继续往下看
# 嗯这⾥返回了⼀个UiObject的对象回去了。我们暂时就先不看Selecotr这个类了
def __call__(self, **kwargs):
# 这⾥传了两个参数⼀个是session的实例化对象,另外⼀个是Selector的实例化对象
return UiObject(self, Selector(**kwargs))
以下则是UiObject类的构造函数, 以下其实就是⼏个赋值,重点的还是jsonrpc的赋值,其实这个jsonrpc是uiautomatorServer中的jsonrpc,这⼀点我们要注意。
class UiObject(object):
def __init__(self, session, selector):
self.session = session
self.selector = selector
self.jsonrpc = session.jsonrpc
uiobject的对象已经实例化完成了,下来当然就是最后的click()⽅法的内容了
def click(self, timeout=None, offset=None):
"""
Click UI element.
Args:
timeout: seconds wait element show up
offset: (xoff, yoff) default (0.5, 0.5) -> center
The click method does the same logic as java uiautomator does.
1. waitForExists
2. get VisibleBounds center
3. send click event
Raises:
UiObjectNotFoundError
"""
self.must_wait(timeout=timeout)
x, y = (offset=offset)
# ext.htmlreport need to comment bellow code
# if info['clickable']:
# return self.jsonrpc.click(self.selector)
self.session.click(x, y)
delay = self.session.server.click_post_delay
if delay:
time.sleep(delay)
这⾥我们重点先关注self.must_wait(timeout=timeout)因为这个是真正的去查元素的⽅法,我们代码继续往下看。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论