uiautomator2 init 做了什么

uiautomator2 入门

想要了解一个开源的项目,最好的方式就是从官方的说明文档开始入手。 我们先看下uiautomator2的由来。

uiautomator2 是一个Android UI自动化框架,支持Python编写测试脚本对设备进行自动化。底层基于Google uiautomator,Google提供的uiautomator库可以获取屏幕上任意一个APP的任意一个控件属性,并对其进行任意操作,但有两个缺点:1. 测试脚本只能使用Java语言 2. 测试脚本必须每次被上传到设备上运行。 我们希望测试能够用一个更脚本化的语言,例如Python编写,同时可以每次所见即所得地修改测试、运行测试。这里要非常感谢 Xiaocong He (@xiaocong),他将这个想法实现了出来(见xiaocong/uiautomator),原理是在手机上运行了一个http服务器,将uiautomator中的功能开放出来,然后再将这些http接口,封装成Python库。 我们的uiautomator2项目是对xiaocong/uiautomator的增强,主要有以下部分:

  1. 设备和开发机可以脱离数据线,通过WiFi互联(基于atx-agent)
  2. 集成了openstf/minicap达到实时屏幕投频,以及实时截图
  3. 集成了openstf/minitouch达到精确实时控制设备
  4. 修复了xiaocong/uiautomator经常性退出的问题
  5. 代码进行了重构和精简,方便维护
  6. Requirements: Android >= 4.4 Python >=2.7 || <= 3.7

从上面的描述我们可以看到部分的一些原理这个原理其实跟appium有点类似,会在手机上运行一个server端由它来去接收对应的消息以及解析处理。

那我们看下要如何去使用 首先当然就是

  1. install uiautomator2了
pip install --upgrade --pre uiautomator2
  1. Install daemons to a device 电脑连接上一个手机或多个手机, 确保adb已经添加到环境变量中,执行下面的命令会自动安装本库所需要的设备端程序:uiautomator-server 、atx-agent、openstf/minicap、openstf/minitouch
python -m uiautomator2 init

上边就是我们这次要讲的重点了。 python -m uiautomator2 init到底发生了什么内容呢?
我们先看下运行的结果
uiautomator2 init 做了什么_第1张图片
从上述的打印信息我们可以看出来, uiautomator2 init的整个流程为以下内容

graph LR
安装minicap-->安装minitouch
安装minitouch-->安装apk/apk-test
安装apk/apk-test-->安装atx-agent
安装atx-agent-->启动atx-agent

那我们还是具体看下代码的实现是如何的

def cmd_init(args):
    if not args.serial:
        for d in adbutils.devices():
            serial = d.get_serial_no()
            if d.get_state() != 'device':
                logger.warning("Skip invalid device: %s %s", serial,
                               d.get_state())
                continue
            logger.info("Init device %s", serial)
            _init_with_serial(serial, args.apk_version, args.agent_version,
                              args.server, args.reinstall)
    else:
        _init_with_serial(args.serial, args.apk_version, args.agent_version,
                          args.server, args.reinstall)

以上主要是根据命令行调用的时候是否带有serial来区别是单台设备的初始化还是说所有设备都进行初始化的操作, 下来我们看下_init_with_serial的内容

def _init_with_serial(serial, apk_version, agent_version, server, reinstall):
    log.info("Device(%s) initialing ...", serial)
    # Installer实例化时获取对应设备的sdk,arch 用于下来的minitouch以及minicap的安装
    ins = Installer(serial)
    ins.server_addr = server
    ins.install_minicap()
    ins.install_minitouch()
    ins.install_uiautomator_apk(apk_version, reinstall)

    exedir = ins.get_executable_dir()
    log.info("atx-agent is already running, force stop")
    ins.shell(exedir + "/atx-agent", "-stop", raise_error=False)
    ins.shell("killall", "atx-agent", raise_error=False)
    ins.shell("rm", "/sdcard/atx-agent.pid", raise_error=False)
    ins.shell("rm", "/sdcard/atx-agent.log.old", raise_error=False)
    if not ins.check_agent_installed(agent_version):
        ins.install_atx_agent(agent_version, reinstall)

    ins.check_apk_installed(apk_version)
    ins.launch_and_check()

以上代码的过程实际就是前面流程图中的内容。
我们分别看下minicap的安装以及apk的安装步骤

minicap的安装

def install_minicap(self):
        # 判断了arch以及sdk用语下载对应的软件版本推送到对应的目录下(/data/local/tmp)并修改为可执行的权限 
        if self.arch == 'x86':
            log.info("skip install minicap on emulator")
            return
        sdk = self.sdk
        if self.pre and self.pre != "0":
            sdk = sdk + self.pre
        base_url = GITHUB_BASEURL + \
            "/stf-binaries/raw/master/node_modules/minicap-prebuilt/prebuilt/"
        log.debug("install minicap.so")
        url = base_url + self.abi + "/lib/android-" + sdk + "/minicap.so"
        path = cache_download(url)
        # 这里的获取可执行目录作者挺有意思的 #分别设置了两个目录(/data/local/tmp以及/data/data/com.android/shell
        # )在对应的目录下创建一个文件叫permtest,进而修改起权限来确认对应的
        #目录是否有可执行的权限
        exedir = self.get_executable_dir()
        minicapdst = "%s/%s" % (exedir, 'minicap.so')
        self.push(path, minicapdst)
        log.info("install minicap")
        url = base_url + self.abi + "/bin/minicap"
        path = cache_download(url)
        self.push(path, exedir + "/minicap", 0o755)

uiautomator apk的安装

def install_uiautomator_apk(self, apk_version, reinstall=False):
        # 通过 adb shell dumpsys package xx 获取到包的信息,通过判断待安装的包与实际包的版本比较来
        #确认是否需要卸载重新安装
        pkg_info = self.package_info('com.github.uiautomator')
        test_pkg_info = self.package_info('com.github.uiautomator.test')
        # For test_pkg_info has no versionName or versionCode
        # Just check if the com.github.uiautomator.test apk is installed
        if not reinstall and pkg_info and pkg_info['version_name'] == apk_version and test_pkg_info:
            log.info("apk(%s) already installed, skip", apk_version)
            return
        if pkg_info or test_pkg_info:
            log.debug("uninstall old apks")
            self.uninstall('com.github.uiautomator')
            self.uninstall('com.github.uiautomator.test')

        (path, pathtest) = self.download_uiautomator_apk(apk_version)
        self.install(path)
        log.debug("app-uiautomator.apk installed")

        self.install(pathtest)
        log.debug("app-uiautomator-test.apk installed")

atx-agent的安装

def install_atx_agent(self, agent_version, reinstall=False):
        # 通过执行atx-agent -v 获取到版本与待安装的版本进行比较,若不同或版本较低则会
        # 获取设备的abi来下载对应的atx-agent,进行解压后推送的设备上,且修改文件的权限
        exedir = self.get_executable_dir()
        agentpath = '%s/%s' % (exedir, 'atx-agent')
        version_output = self.shell(agentpath, '-v', raise_error=False).strip()
        m = re.search(r"\d+\.\d+\.\d+", version_output)
        current_agent_version = m.group(0) if m else None
        if current_agent_version == agent_version:
            log.info("atx-agent(%s) already installed, skip", agent_version)
            return
        if current_agent_version == 'dev' and not reinstall:
            log.warn("atx-agent develop version, skip")
            return
        if current_agent_version:
            log.info("atx-agent(%s) need to update", current_agent_version)
        files = {
            'armeabi-v7a': 'atx-agent_{v}_linux_armv7.tar.gz',
            'arm64-v8a': 'atx-agent_{v}_linux_armv7.tar.gz',
            'armeabi': 'atx-agent_{v}_linux_armv6.tar.gz',
            'x86': 'atx-agent_{v}_linux_386.tar.gz',
        }
        log.info("atx-agent(%s) is installing, please be patient",
                 agent_version)
        abis = self.shell('getprop',
                          'ro.product.cpu.abilist').strip() or self.abi
        name = None
        for abi in abis.split(','):
            name = files.get(abi)
            if name:
                break
        if not name:
            raise Exception(
                "arch(%s) need to be supported yet, please report an issue in github"
                % abis)
        url = GITHUB_BASEURL + '/atx-agent/releases/download/%s/%s' % (
            agent_version, name.format(v=agent_version))
        log.debug("download atx-agent(%s) from github releases", agent_version)
        path = cache_download(url)
        tar = tarfile.open(path, 'r:gz')
        bin_path = os.path.join(os.path.dirname(path), 'atx-agent')
        tar.extract('atx-agent', os.path.dirname(bin_path))
        self.push(bin_path, agentpath, 0o755)
        log.debug("atx-agent installed")

启动和检查

def launch_and_check(self):
        log.info("launch atx-agent daemon")

        # stop first
        self.shell(self.atx_agent_path, "server", "--stop", raise_error=False)
        # start server
        # 这里判断是否有需要连接的服务器(这里应该是设备管理平台), 如果有则启动时增加-t的参数进行连接
        args = [self.atx_agent_path, "server", '-d']
        if self.server_addr:
            args.append('-t')
            args.append(self.server_addr)
        output = self.shell(*args)
        lport = self.forward_port(7912)
        # 这里很有意思 通过 adb forward 后 所有本地对应端口的tcp数据都会被转发到手机设备的7912端口, 虽然没有看atx-agent,但是这里应该能够清楚 7912其实就是atx-agent监听的端口
        logger.debug("forward remote(tcp:7912) -> local(tcp:%d)", lport)
        time.sleep(.5)
        cnt = 0
        while cnt < 3:
            try:
                r = requests.get(
                    'http://localhost:%d/version' % lport, timeout=10)
                r.raise_for_status()
                log.info("atx-agent version: %s", r.text)
                # todo finish the retry logic
                print("atx-agent output:", output.strip())
                # open uiautomator2 github URL
                self.shell("am", "start", "-a", "android.intent.action.VIEW",
                           "-d", "https://github.com/openatx/uiautomator2")
                log.info("success")
                break
            except (requests.exceptions.ConnectionError,
                    requests.exceptions.ReadTimeout,
                    requests.exceptions.HTTPError):
                time.sleep(1.5)
                cnt += 1
        else:
            log.error(
                "Failure, unable to get result from http://localhost:%d/version",
                lport)

以上就是uiautomator init的整个过程了, 其中涉及到了很多内容的安装。下来我们再一步步去看看每一个的作用到底都是什么

你可能感兴趣的:(automator)