Android之UiAutomator测试框架源码分析(第八篇:UiWatcher在框架中的自动触发过程分析)

(注意:本文基于UI Automator测试框架版本为2.2.0)  

    UiWatcher是UI Automator测试框架提供的一个接口,所有实现该接口的类所产生的对象,表示具备一种监控Ui的能力,通过创建实现UiWatcher接口的对象来处理Ui自动化功能测试中随时可能出现的,非常影响测试用例执行的两种意外情况:

1、随机弹出的系统弹窗:系统升级、没电、权限、耗电提示、Google服务挂掉等等对话框

2、应用内的业务弹窗:签到、IM到达等等与需求有关的对话框

通过在实现UiWatcher接口的自定义类中重写UiWatcher接口中规范的checkForCondition()方法来进行处理,在重写的checkForCondition()方法中一般有两种解决思路 

1、checkForCondition()方法只负责发现意外情况,而不进行任何处理,简单说就是只发现不处理(后面单独处理这种情况)

2、另一种思路则是发现意外情况后,直接在checkForCondition()方法中就地处理,简单的说就是即发现也做处理。

每一个创建的UiWatcher对象都需要通过UiDevice对象的registerWatcher(String,UiWatcher)方法进行注册,注册后的UiWatcher对象会在UI Automator测试框架中被自动触发,所谓的触发,实际触发的是UiWatcher的checkForCondition()方法。

UiDevice对象提供了几个与UiWatcher相关的方法,它们是:

1、注册UiWatcher对象的功能

2、反注册UiWatcher对象的功能

3、runWatchers()方法将直接触发所有已注册的UiWatcher对象

等等

既然runWatchers()方法可以触发所有已经注册的UiWatcher,那么只要找到所有调用runWatchers()方法的位置,就可以获知在UI Automator测试框架中,哪些情况下会导致UiWatcher被自动触发!以下截图为UiDevice中所有与UiWatcher有关的方法!

 

UiWatcher接口的定义

public interface UiWatcher {
   public boolean checkForCondition();
}

    UiWather接口定义在androidx.text.uiautomator包中,它的内部只声明了一个checkForCondition()抽象方法,该抽象方法表示UI监控器对于条件的检查,返回值为boolean类型,当返回true时表示条件匹配,表示注册的UI监控器的已经被触发,false则表示条件不匹配,注册的UI监控器并没有被触发。当我们在checkForCondition()方法中自己实现一个检查逻辑时,一般是检查界面上(当前Window持有的View树)是否出现符合某个条件的控件。每个UiWatcher对象被触发时,真正被触发的是UiWatcher中的checkForCondition()方法,在UI Automator测试框架中,有两种触发方式:

1、一种是自动触发(UI Automator测试框架中自动触发)

2、另一种是手动触发

无论是框架自动触发、还是你通过代码手动触发,均需要调用UiDevice的runWatchers()方法进行触发,继续向下学代码,学习UI Automator测试框架中哪些情况会自动触发UiWatcher!

 

UiWatcher自动触发的代码位置

    通过阅读代码,UiDevice的runWatchers()方法会导致每个已注册的UiWatcher对象的checkForCondition()方法被回调,那么UiDevice的runWatchers()方法在UI Automator测试框架中的哪些位置进行了调用呢?目前找到3个调用runWatchers()方法的位置,这3个地方都会导致所有已注册的UiWatcher的checkForCondition()方法被自动触发!

1、第一个位置:在ByMatcher类中的findMatches方法中,它的内部会调用runWatchers()方法

2、第二个位置:在UiObject类中的findAccessibilityNodeInfo()方法中,会调用runWatchers()方法

发现调用findAccessibilityNodeInfo()方法的位置有32处

3、第三个位置:在UiObject2类中的getAccessibilityNodeInfo方法中,会调用runWatchers()方法

发现getAccessibilityNodeInfo()方法被调用的位置有25处

 

说明:UiObject与UiObject2虽然都表示View树中的一个控件,而当我们使用这两个对象时,它们两个对于UiWatcher对象的触发时机是各自独立的。

说明:共计59处的代码都可能会导致UiWatcher的触发……这还没算再上层的代码……

UiObject、UiObject2触发UiWatcher的时机也是未来学习的一个坑点,毕竟他俩的触发逻辑都是各自独立的………在此之前还是先详细研究一下导致UiWatcher被触发的方法,就是UiDevice中的runWatchers()方法!

 

导致UiWatcher被触发的runWatchers()方法分析

    public void runWatchers() {
        Tracer.trace();
        if (mInWatcherContext) {
            return;
        }

        for (String watcherName : mWatchers.keySet()) {
            UiWatcher watcher = mWatchers.get(watcherName);
            if (watcher != null) {
                try {
                    mInWatcherContext = true;
                    if (watcher.checkForCondition()) {
                        setWatcherTriggered(watcherName);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exceuting watcher: " + watcherName, e);
                } finally {
                    mInWatcherContext = false;
                }
            }
        }
    }

位于UiDevice类中的runWatchers()方法,该方法用于触发所有已注册的UiWatcher,UiWatcher的整体设计采用的是观察者模式!通过注册一个一个的作为观察者的UiWathcer对象,当作为被观察者的UiDevice对象发生变化时,可以通知所有注册的UiWatcher对象执行checkForCondition()方法!

1、收集线程堆栈,用于Debug

Tracer的静态方法trace(),用于线程堆栈信息的收集

2、防止重复触发UiWatcher

判断UiDevice对象持有的mInWatcherContext,mInWatcherContext是一个boolean类型的变量,它表示是否UiDevice对象是否正处于Watcher的上下文中,mInWatcherContext两个赋值的地方,都在当前runWatchers()方法中,当mInWatcherContext为true表示正处于触发UiWatcher中,这可以防止当前的runWatchers()被多次调用,因为此时会直接通过return语句中断方法的执行

3、遍历每个UiWatcher

获取UiDevice对象持有的mWatchers,mWatchers是一个Map类型的对象,具体类型是HashMap,它持有所有已注册的UiWatcher对象,通过调用HashMap的keySet()方法获得一个由所有的Key对象组成的Set集合对象,接着就是就这个Key对象为元素组成的Set对象进行遍历,在遍历的代码块中,每次调用HashMap的get方法,get方法需要传入Set集合对象取出来的Key对象,这样就可以从HashMap中取出每一个UiWatcher对象,作者还是对UiWatcher做了不为null的保护,接下来详细看看try catch finally代码块中干了点啥

4、触发UiWatcher、标记触发结果

在try代码块中,先是将标志位mInWatcherContext赋值为true,表示UiDevice正处于UiWatcher的业务逻辑上下文中,接着回调UiWatcher对象的checkForCondition()方法,checkForCondition()方法的返回值会影响setWatcherTriggered()方法的调用(见本文下方的知识点),这个setWatcherTriggered()方法是标记一个UiWatcher对象的条件是否被匹配,它会影响后面的逻辑!在catch代码块中则是捕获一切抛出的异常,处理方式是打印一行日志……在finally代码中则是恢复标志位mInWatcherContext为false,表示UiWatcher的业务逻辑上下文已经完全结束!

 

setWatcherTriggered()方法分析

    private void setWatcherTriggered(String watcherName) {
        Tracer.trace(watcherName);
        if (!hasWatcherTriggered(watcherName)) {
            mWatchersTriggers.add(watcherName);
        }
    }

    传入的参数watcherName,表示UiWatcher的名字,显示调用堆栈把这个watcherName传入到的调用堆栈中,接着做了一个容错,通过调用hasWatcherTriggered方法,判断传入的watcherName是否已经标记过了,这是为了防止重复标记,被记录的UiWatcher的名字watcherName最后都会存储到mWatchersTriggers中,mWatchersTriggers是UiDevice对象持有的一个List对象,具体类型是ArrayList,它负责保存每个条件匹配的UiWatcher对象的名字!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值