UiAutomator源码分析之UiAutomatorBridge框架
最后更新于:2022-04-01 19:55:32
上一篇文章《[UIAutomator源码分析之启动和运行](http://blog.csdn.net/zhubaitian/article/details/40535579)》我们描述了uitautomator从命令行运行到加载测试用例运行测试的整个流程,过程中我们也描述了UiAutomatorBridge这个类的重要性,说它相当于UiAutomation的代理(我们都知道UiAutomator是通过UiAutomation和AccessibilityService进行连接然后获取界面空间信息和注入事件的).那么今天开始我们就围绕这个类以及跟它有关系的类进行进一步的分析。
##1. UiAutomatorBridge框架
这一章节我们会先看UiAutomatorBridge的整体框架,往后我会编写其他文章通过一些具体的例子把它们串起来。因为我的mackbook pro上没有安装类图软件,所以下图是手画的
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-12_57ad6e29b3344.jpg)
往下我们就去初步描述下UiAutomatorBridge跟每一个相关的类的关系。
## 2. UiAutomatorBridge与UiAutomation的聚合关系
UiAutomatorBridge拥有一个UiAutomation的成员变量,它们是聚合的关系,注意不是组合,因为UiAutomation不一定只能依赖UiAutomatorBridge而存在,我们上一章节的UiAutomatorTestRunner就拥有一个UiAutomation的成员变量。
一旦UiAutomator工具需要通过UiAutomatorBridge获取界面或者注入事件的时候,就会调用该成员变量.比如下面这个很关键的去获取当前界面的Root Node的方法:
~~~
/* */ public AccessibilityNodeInfo getRootInActiveWindow() {
/* 66 */ return this.mUiAutomation.getRootInActiveWindow();
/* */ }
~~~
## 3. UiAutomatorBridge与QueryController的关联关系
QueryController做的所有事情就是去把UiSelector这个UI控件选择子翻译成真实的适合我们使用的android.view.accessibility.AccessibilityNodeInfo。 UiAutomatorBridge拥有一个成员变量mQueryController保存了QueryController的一个实例:
~~~
/* */ private final QueryController mQueryController;
/* */
~~~
当UiObject需要获取一个UiSelector指定的控件信息时,会去调用UiAutomatorBridge的getQueryController方法来获得这个mQueryController对象来进行相应的操作,如以下的UiObject的方法findAccessibilityNodeInfo就是这样做的:
~~~
/* */ protected AccessibilityNodeInfo findAccessibilityNodeInfo(long timeout)
/* */ {
/* 164 */ AccessibilityNodeInfo node = null;
/* 165 */ long startMills = SystemClock.uptimeMillis();
/* 166 */ long currentMills = 0L;
/* 167 */ while (currentMills <= timeout) {
/* 168 */ node = getQueryController().findAccessibilityNodeInfo(getSelector());
/* 169 */ if (node != null) {
/* */ break;
/* */ }
/* */
/* 173 */ UiDevice.getInstance().runWatchers();
/* */
/* 175 */ currentMills = SystemClock.uptimeMillis() - startMills;
/* 176 */ if (timeout > 0L) {
/* 177 */ SystemClock.sleep(1000L);
/* */ }
/* */ }
/* 180 */ return node;
/* */ }
~~~
该getQueryController方法会去调用UiAutomatorBridge的getQueryController方法:
~~~
/* */ QueryController getQueryController()
/* */ {
/* 100 */ return UiDevice.getInstance().getAutomatorBridge().getQueryController();
/* */ }
~~~
从上面的类图我们可以看到,除了UiAutomatorBridge会调用QueryController做事情外,QueryController又会反过来调用UiAutomatorBridge来做事情,因为如图所描述的,只有UiAutomatorBridge拥有UiAutomation的实例,所以QueryController会持有一个UiAutomatorBridge的实例:
~~~
/* */ private final UiAutomatorBridge mUiAutomatorBridge;
~~~
然后在需要的时候再调用UiAutomatorBridge,如下面的获得Root Node的方法:
~~~
/* */ protected AccessibilityNodeInfo getRootNode()
/* */ {
/* 168 */ int maxRetry = 4;
/* 169 */ long waitInterval = 250L;
/* 170 */ AccessibilityNodeInfo rootNode = null;
/* 171 */ for (int x = 0; x < 4; x++) {
/* 172 */ rootNode = this.mUiAutomatorBridge.getRootInActiveWindow();
/* 173 */ if (rootNode != null) {
/* 174 */ return rootNode;
/* */ }
/* 176 */ if (x < 3) {
/* 177 */ Log.e(LOG_TAG, "Got null root node from accessibility - Retrying...");
/* 178 */ SystemClock.sleep(250L);
/* */ }
/* */ }
/* 181 */ return rootNode;
/* */ }
~~~
## 4. UiAutomatorBridge与InteractionController的关联关系
道理与以上的QueryController一样,只是UiAutomatorBridge需要通过InteractionController做的事情不是去获得控件信息,而是去注入事件。
## 5. UiAutomatorBridge与ShellUiAutomatorBridge的继承关系
UiAutomatorBridge是一个抽象类,里面的方法有以下几个:
- getRootInActiveWindow:通过UiAutomation获取当前窗口控件xml信息的根节点(通过它可以循环获取所有控件)
- injectInputEvent:通过UiAutomation注入事件
-
waitForIdle: 通过UiAutomation睡眠指定时间
-
executeCommandAndWaitForAccessibilityEvent:通过UiAutomation执行指定线程的操作然后等待预期的时间返回
-
takeScreenshot:通过UiAutomation进行截图
-
performGlobalAction: 通过UiAutomation去执行一些全局的动作,如打开最近打开过的app列表,回到home界面等
从中可以看到这些动过都是需要通过UiAutomation来执行的,但也有一些动作是不需要用UiAutomation执行的,所以我相信google是为了代码清晰和可维护性,提供了子类ShellUiAutomatorBridge来专门处理那些不需要用到UiAutomation的情况,比如以下的isScreenOn方法就不需要用到UiAutomation,而是直接用PowerManager服务来判断当前屏幕是否是打开的:
~~~
/* */ public boolean isScreenOn()
/* */ {
/* 111 */ IPowerManager pm = IPowerManager.Stub.asInterface(ServiceManager.getService("power"));
/* */
/* 113 */ boolean ret = false;
/* */ try {
/* 115 */ ret = pm.isScreenOn();
/* */ } catch (RemoteException e) {
/* 117 */ Log.e(LOG_TAG, "Error getting screen status", e);
/* 118 */ throw new RuntimeException(e);
/* */ }
/* 120 */ return ret;
/* */ }
~~~
';
作者 | 自主博客 | 微信 | CSDN |
天地会珠海分舵 | 服务号:TechGoGoGo 扫描码: | http://blog.csdn.net/zhubaitian |