Android之UiAutomator测试框架源码分析(第十六篇:WaitMixin停顿功能分析)

(本文基于:'androidx.test.uiautomator:uiautomator:2.2.0')

    在UI Automator测试框架中,等待功能非常重要,如果没有等待功能,测试框架几乎将不能使用,因为不同设备、不同网络环境下,同一个设备的资源状态(内存、CPU)不同时,应用中的控件出现时机就会不一样,专业点说,渲染完成的耗时是完全不同的!举个例子,以打开微信朋友圈为例,高端手机可能仅需100ms即可打开微信朋友圈,而低端手机可能800ms才打开微信朋友圈,时间上差了8倍!此时我们使用的是同一套程序代码,如果没有使用等待功能,最可能出现的问题是一会儿能查找到控件,一会儿又查找控件失败,定位控件完全靠运气(线程的调度)。如果该问题不解决,插桩测试根本无法继续进行,为了确保控件在任何设备环境下都可以被查找到,也就完成了保障测试用例在不同手机上顺利执行的第一步!此时我们必须使用显式的等待功能,等待功能可以指定一个时间范围(比如10秒),在该时间范围内,插桩测试的线程会不断的去查询指定的条件是否已经达到,每次查询的默认间隔时间是1秒,达到条件插桩测试线程就会继续运行,不符合条件的要求插桩测试线程就会进行休眠,这样使用同一套代码就可以适配不同环境所带来的问题,比如指定的时间范围是10秒,那么10秒内,任何设备界面上的控件肯定已经被渲染出来了,除非发生其他意外,这样就解决了因环境而引起的时间差问题,解决该问题的就是今天的主角---->WaitMixin类,它位于androidx.test.uiautomator包中,封装了可以让插桩线程等待运行的功能,简称等待功能。在UI Automator测试框架中的UiDevice对象、UiObject2对象所提供的等待功能,底层使用的正是WaitMixin的等待功能。UiDevice对象、UiObject2对象各自通过持有一个WaitMixin对象,来使用WaitMixin的实现的等待功能。这篇文章就是分析WaitMixin如何实现的等待功能!

WaitMixin类结构介绍

class WaitMixin<T> {}

    WaitMixin是一个范型类,内部定义了两个重载的wait实例方法(基类的不计算在内),正是这两个wait方法实现的等待功能,他们是源码分析的重点!!另外可以看到WaitMixin类还持有了一个常量DEFAULT_POLL_INTERVAL,常量值是1000,这个常量表示插桩线程在等待过程中,每次进行的重试的间隔时间(轮询时间),当任意一次重试,发现条件已经满足,插桩线程才会继续运行!图例中最下面的mObject则是WaitMixin对象持有的一个参数类型指定的对象,通过源码分析,我们将对这两个字段留下更深刻的印象!

 

WaitMixin的构造方法,接受一个类型参数指定的对象

    public WaitMixin(T instance) {
        mObject = instance;
    }

mObject的类型是由类型参数T指定的,mObject是WaitMixin对象持有的实例变量,它负责持有传入的参数类型的对象instance,比如UiDevice对象持有WaitMixin对象时,UiDevice为此类型参数T传入的是UiDevice对象自身,那么此时mObject指向的就是一个UiDevice对象!接下来学习一下拥有两个参数的wait()方法

 

wait()方法,接受两个参数,一个参数是Condition对象,另一个参数是long

    public <R> R wait(Condition<? super T, R> condition, long timeout) {
        return wait(condition, timeout, DEFAULT_POLL_INTERVAL);
    }

第一个传入的参数为Condition对象(SearchCondition继承了Condition),类型参数限制必须为T、R的父类对象或自身,T是WaitMixin的类型参数,R是wait方法作为泛型方法定义的类型参数,比如UiDevice持有并使用一个WaitMixin对象的功能时,此时WaitMixin对象通过mObject持有的是UiDevice对象,那么此时T则为UiDevice对象(<? super T, R>这个知识点,后面再补一下),在当前wait方法的内部,又将传入的condition、timeout两个局部变量和一个常量DEFAULT_POLL_INTERVAL(值为1000,代表轮询的间隔时间)同时传入到另一个重载的3个参数的wait方法中),最后该方法的返回值则由3个参数的wait方法的返回值来决定,接下来我们继续学习3个参数的wait方法是如何实现等待功能的,接下来这个方法是重点!

 

重载的wait()方法,接受三个参数,第一个参数是Condition对象,第二个参数是long类型,第三个参数也是long类型

    public <R> R wait(Condition<? super T, R> condition, long timeout, long interval) {
        long startTime = SystemClock.uptimeMillis();

        R result = condition.apply(mObject);
        for (long elapsedTime = 0; result == null || result.equals(false);
                elapsedTime = SystemClock.uptimeMillis() - startTime) {

            if (elapsedTime >= timeout) {
                break;
            }

            SystemClock.sleep(interval);
            result = condition.apply(mObject);
        }
        return result;
    }

第一个参数Condition对象表示需要插装线程处于等待状态的一个条件对象(若不满足该条件对象的要求,插装线程就会处于等待并重试的状态),第二个参数long表示最大的等待时间,第三个参数long则为轮询重试的间隔时间

1、获取时间戳

首先定义一个局部变量startTime,它保存的是系统从开机起到现在的活跃时间戳(SystemClock.uptimeMillis())

2、接着调用传入的Condition对象中的apply方法,该apply方法被调用时,需要传入WaitMixin对象持有的mObject对象,此时mObject保存的是UiDevice对象的引用(注意上下文:UiDevice对象持有一个WaitMixin对象时,WaitMixin对象持有的mObject指向的为UiDevice对象),apply方法的返回值由局部变量result保存。Condition是抽象类、SearchCondition继承了Condition,SearchCondition也是抽象类,我们要找一个SearchCondition的具体类的对象传到该wait方法中,而Until类的静态工厂方法findObject就可以创建一个匿名的SearchCondition对象,那么该匿名的SearchCondition对象中的apply方法是如何实现的呢?(见本文3号知识点)。在Until中的findObject方法中实现的SerarchCondition的apply方法返回的是一个UiObject2对象,此时局部变量result保存着该UiObject2对象的引用

3、循环判断条件

接下来是for循环,先定义的局部变量elapsedTime初始值为0(表示渡过的时间),然后判断局部变量result持有的值是否为null、如果不为null就继续调用result的equals()方法(见本文下方)并为其传入一个false,只要任意一个条件为true,for循环语句就会继续执行循环体,for循环代码块中,使用当前时间戳减去startTime存储的时间戳,计算出的差值正是当前线程的已运行时间,他由局部变量elapsed保存,当已运行时间elapsed超过最大的等待时间timeout,for循环就会通过break结束掉,在每轮循环中,会通过SystemClock的sleep()方法(见本文下方),实现当前线程的休眠(处于TIMED_WAITED),为sleep传入的常量DEFAULT_POLL_INTERVAL,它的值是1000,表示1000毫秒,当前线程执行一次循环语句,就会休眠1000毫秒,这里表示的就是每间隔1秒去做一次轮询,这会再次调用Condition对象的apply方法(见本文上方),apply方法的返回值会赋值给result

4、返回条件判断方法返回的结果

循环结束后(大于超时时间或者已经符合等待条件对象的要求,循环判断条件的过程即会结束),最后整个方法返回局部变量result保存的对象,此时按照我在文章中描述的上下文,返回的就是UiObject2对象

 

位于Until类中的findObject方法,返回一个SearchCondition对象

    public static SearchCondition<UiObject2> findObject(final BySelector selector) {
        return new SearchCondition<UiObject2>() {
            @Override
            UiObject2 apply(Searchable container) {
                return container.findObject(selector);
            }
        };
    }

当我们调用Until的findObject方法时,仅需传入一个BySelector对象,在被回调的apply方法中,传入的container指向的是一个UiDevice对象(UiDevice实现了Serchable接口),通过UiDevice对象的findObject方法可以得到一个UiObject2对象!该方法的返回值是一个SearchCondition对象,该对象代表等待条件对象,实际要求的等待条件是:需要查找到一个匹配的UiObject2对象,等待行为才会停止 

 

UiObject2的equals方法,接受一个Object对象

    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object == null) {
            return false;
        }
        if (getClass() != object.getClass()) {
            return false; //本上文的代码只会走到这里,毕竟传入false的Class对象一定不与UiObject2相同
        }
        try {
            UiObject2 other = (UiObject2)object;
            return getAccessibilityNodeInfo().equals(other.getAccessibilityNodeInfo());
        } catch (StaleObjectException e) {
            return false;
        }
    }

UiObject2重写的equals方法,它用于比较UiObject2对象是否为同一个对象

 

位于SystemClock类中的sleep方法,接受一个long型参数表示要求休眠的毫秒数

 public static void sleep(long ms)
    {
        long start = uptimeMillis();
        long duration = ms;
        boolean interrupted = false;
        do {
            try {
                Thread.sleep(duration);
            }
            catch (InterruptedException e) {
                interrupted = true;
            }
            duration = start + ms - uptimeMillis();
        } while (duration > 0);

        if (interrupted) {
            // Important: we don't want to quietly eat an interrupt() event,
            // so we make sure to re-interrupt the thread so that the next
            // call to Thread.sleep() or Object.wait() will be interrupted.
            Thread.currentThread().interrupt();
        }
    }

线程休眠的方法,可以看到调用了Thread的sleep()方法!SystemClock是Android提供的工具类!

 

总结

    UI Automator测试框架的等待功能的底层都是通过调用Thread的sleep方法实现的,而这个Thread就是插桩线程,每当它的sleep()方法被调用,就会导致当前插桩线程进入TIMED_WAIT状态!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值