(注意:本文基于UI Automator测试框架版本为2.2.0)
UiDevice简介
创建的UiDevice对象代表1个Android设备,可能是手机、电视、手表、车载设备等等,凡是安装Android系统的都属于Android设备……当然所谓的代表也是抽象的现实世界。
UiDevice的构造方法是包内访问权限(default修饰),UiDevice类位于androidx.test.uiautomator包中,与我们自己写的测试代码不在同一个包中,所以我们不能通过正常方式调用UiDevice的构造方法创建UiDevice对象,无法使用new的去创建UiDevice对象。
我们怎么创建UiDevice对象呢?这时候的代码往往会提供另外一种创建对象的方式,Google在UiDevice类中提供了一个静态工厂方法getInstance()用于创建UiDevice对象,其中一个无参的静态方法getInstance()已被废弃,另一个重载的静态方法getInstance()则需要传入一个Instrumentation对象。
学习UiDevice各种功能(API)的实现原理,可以帮助我们提高编写Ui自动化程序的能力,构建出更稳定、好维护、健壮性更好的大型Ui自动化项目。
学习如何创建Device对象的代码,再温习一下UiDevice提供了哪些功能…………(第三篇文章已有介绍,这里是刻意温习的)
UiDevice提供了哪些功能呢?
UiDevice提供的常用功能可以分为下面几类(根据API):
1、操作设备
打开通知栏、按下back键、为设备截图
2、查找控件
获取表示View树中某一个具体的控件的对象(UiObject对象、UiObject2对象等等)
3、等待功能
插桩测试暂时停止运行,比如某个控件的可见性是由网络响应返回的某个字段来控制的,此时的插桩测试需要等待网络请求返回,才能再去获取指定的控件,UiDevice可以模拟这个等待功能
4、管理UiWatcher
包括注册、反注册、状态信息,使用UiWatcher对象可以大大提高测试代码的健壮性!
5、执行Android系统的命令
静态方法getInstance()分析
public static UiDevice getInstance(Instrumentation instrumentation) {
if (sInstance == null) {
sInstance = new UiDevice(instrumentation);
}
return sInstance;
}
getInstance()位于UiDevice类中,需要传入一个Instrumentation对象,返回值为创建的UiDevice对象
如何获取到一个Instrumentation对象?
答:使用InstrumentationRegistry类的静态方法getInstrumentation()获取,Instrmentation对象的知识点非常重要,这里暂时知道需要传入一个Instrmentation对象即可(它是Android系统App框架层中的知识点)
静态方法getInstance()方法的方法体主要有两个步骤
1、判断UiDevice对象是否已经创建,若没有创建则执行创建UiDevice对象
首先判断UiDevice类持有的sInstance(static修饰)是否为null,当sInstance为null,则说明UiDevice对象并没有创建,此时执行if代码块中的new语句创建一个UiDevice对象,在当前静态方法getInstance()传入的Instrumentation对象会继续再传入到UiDevice的一个参数的构造方法中,UiDevice对象创建完成后,会将自己的引用赋值给UiDevice类持有的sInsance
2、向调用者返回创建的UiDevice对象
最终该方法会将UiDevice类持有的sInstance指向的UiDevice对象的引用返回给调用者,这就是创建UiDevice对象的过程,可见UiDevice对象是以单例对象形式存在的,内存中只有一个UiDevice对象!
接下来我们一起研究UiDevice的1个参数的构造方法是如何实现的?
UiDevice单个参数的构造方法分析
UiDevice(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mQueryController = new QueryController(instrumentation);
mInteractionController = new InteractionController(instrumentation);
// Enable multi-window support for API level 21 and up
if (UiDevice.API_LEVEL_ACTUAL >= Build.VERSION_CODES.LOLLIPOP) {
// Subscribe to window information
AccessibilityServiceInfo info = getUiAutomation().getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
getUiAutomation().setServiceInfo(info);
}
}
用于创建UiDevice对象的构造方法,必须指定一个Instrumentation对象
在方法的内部,执行了这些逻辑
1、UiDevice对象持有传入的Instrumentation对象
首先将传入的Instrumentation对象赋值给UiDevice对象持有的实例变量mInstrumentation,即UiDevice对象持有一个Instrumentation对象
2、为UiDevice对象持有的QueryController对象进行初始化
创建QueryController对象,且必须指定传入的Instrumentation对象。UiDevice对象持有的实例变量mQueryController负责保存创建好的QueryController对象。UiDevice对象持有的QueryController对象有什么用呢?这个留个关子,继续往下看代码……
3、为UiDeivce对象持有的InteractionController对象进行初始化
接下来创建InteractionController对象,InteractionController对象也需要一个Instrumentation对象,这里同样将传入的Instrumentation传给InteractionController。UiDevice对象持有的mInteractionController指向创建的InteractionController对象。此时UiDevice对象持有的InteractionController对象有什么用呢?继续看看代码……
PS:停下来,思考一下代码……
疑问1、UiDevice对象持有的3个对象,Instrumentation对象、QueryController对象、还有InteractionController对象,这3个对象有什么用?它们有什么功能呢?
疑问2、为什么UiDevice对象、QueryController对象、InteractionController对象都持有同一个从UiDevice构造方法中传入的Instrumentation对象呢?这个Instrumentation对象就这么牛逼吗?
4、多窗口的逻辑(API 21及以上版本支持多窗口)
常量LOLLIPOP位于Build.VERSION_CODES类中,它的值是21,表示Android API等级是21,API 21对应的Android版本是5.0!当UiDevice的API版本大于等于21时(Android5.0),调用getUiAutomation()方法得到一个UiAutomation对象,再通过UiAutomation对象的getServiceInfo()方法得到一个AccessibilityServiceInfo对象,并由局部变量info负责存储。接着将局部变量info保存的AccessibilityServiceInfo对象的flags属性执行一个位或运算,运算结果再赋值给它的flags属性,接下来又一次使用getUiAutomation()方法获取UiAutomation对象,然后调用UiAutomation对象的setServiceInfo()方法将修改过的flags属性的AccessibilityServiceInfo对象再次由UiAutomation对象持有!
PS:停下来,再次思考代码……
疑问3、这个UiAutomation对象有什么作用呢?(备注:用于和AccessbilityManagerService建立连接)
重要字段介绍
1、静态变量(UiDevice类持有)
API_LEVEL_ACTUAL:用于表示实际的Android API版本
KEY_PRESS_EVENT_TIMEOUT:输入事件的超时时间,默认值为1s
LOG_TAG:常量,用于调试时,使用的TAG_NAME
sInstance:UiDevice持有的,负责保存创建的UiDevice单例对象
2、实例变量(UiDevice对象持有)
mInstrumentation:UiDevice对象持有的Instrumentation对象,该对象提供的功能用于在App框架中使用
mInteractionController:UiDevice对象持有的InteractionController对象,该对象是干啥的?还不知道……囧……
mInWatcherContext:UiDevice对象持有的一个标志位,用于记录是否正在执行UiWatcher的业务逻辑
mQueryController:UiDevice对象持有的QueryController对象,该对象有什么功能呢?囧……
mWaitMixin:UiDevice对象持有的WaitMixin对象,该对象提供了各种根据条件让插桩线程暂停运行的等待功能
mWatchers:UiDevice对象持有的一个HashMap对象,此HashMap对象负责保存注册的每个UiWatcher对象
mWatchersTriggers:UiDevice对象持有的一个ArrayList对象,此ArrayList对象负责保存哪些已经被触发的UiWatcher对象的名字
总结
1、UiDevice对象在插桩测试项目中是作为单例对象存在的,通过UiDevice类的静态方法getInstance(Instramentation)即可获得UiDevice单例对象
2、UiDevice对象持有的Instrumentation对象、QueryController对象、InteractionController对象、以及并没有直接持有的,且使用的Uiautomation对象,它们各自都有什么用途?跟随我的脚步,你一定会知道所有……继续学习源码
3、Instrumentation对象非常重要,通过他可以获取到的一个UiAutomation对象,而通过UiAutomation对象才可以操作Window中的View树
4、Instrumentation对象,在以API M作为版本分隔中,获取方式还是不同的……
5、当我们创建UiDevice对象,UiDevice持有的一些对象也得到了创建,包括QueryConroller对象等等