上一篇文章《UIAutomator源码分析之启动和运行》我们描述了uitautomator从命令行运行到加载测试用例运行测试的整个流程,过程中我们也描述了UiAutomatorBridge这个类的重要性,说它相当于UiAutomation的代理(我们都知道UiAutomator是通过UiAutomation和AccessibilityService进行连接然后获取界面空间信息和注入事件的).那么今天开始我们就围绕这个类以及跟它有关系的类进行进一步的分析。
1. UiAutomatorBridge框架
这一章节我们会先看UiAutomatorBridge的整体框架,往后我会编写其他文章通过一些具体的例子把它们串起来。因为我的mackbook pro上没有安装类图软件,所以下图是手画的
往下我们就去初步描述下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;
- /* */
- /* */ 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();
- /* */ }
- /* */ private final UiAutomatorBridge mUiAutomatorBridge;
- /* */ 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界面等
- /* */ 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;
- /* */ }
-
上一篇文章《UiAutomator源码分析之UiAutomatorBridge框架》中我们把UiAutomatorBridge以及它相关的类进行的描述,往下我们会尝试根据两个实例将这些类给串联起来,我准备做的是用如下两个很有代表性的实例:
- 注入事件
- 获取控件
这一篇文章我们会通过分析UiDevice的pressHome这个方法来分析UiAutomator是如何注入事件的,下一篇文章会描述如何获取控件,敬请期待。
1. UiObject.pressHome顺序图
首先我们看一下我手画的非规范的顺序图,从中我们可以看到pressHome这个动作究竟需要和多少个类进行交互,以及它们是怎么交互的。
2.这些类是什么时候初始化的
在我们编写测试用例脚本的时候我们不会对以上所有的类进行初始化,包括UiObject对象都是通过直接在脚本中调用父类UiAutomationTestCase的getUiDevice()这个方法来获得的。其实这些都是在uiautomator运行时由RunTestCommand类的start()这个方法进行初始化的,具体请看《 UIAutomator源码分析之启动和运行》的 3.6章节“初始化UiDevice和UiAutomationBridge“,这里就不做累述。我们这里会看下在初始化UiAutomatorBridge的时候是如何把QuneryControoler和InteractionController一并初始化了的,具体请看UiAutomatorBridge的构造函数:- /* */ UiAutomatorBridge(UiAutomation uiAutomation)
- /* */ {
- /* 48 */ this.mUiAutomation = uiAutomation;
- /* 49 */ this.mInteractionController = new InteractionController(this);
- /* 50 */ this.mQueryController = new QueryController(this);
- /* */ }
3. 代码跟踪
首先看UiDevice的pressHome方法:- public boolean pressHome() {
- 218 Tracer.trace();
- 219 waitForIdle();
- 220 return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
- 221 KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
- 222 KEY_PRESS_EVENT_TIMEOUT);
- 223 }
220行:- 获得UiDevice对象保存的UiAutomatorBridge对象。着两个对象都是在运行时初始化的,不清楚的话请翻看上面提到的文章
- 通过UiAutomatorBridge对象获得上面章节初始化的InteractionController对象
- 调用InteractionController对象的sendKeyAndWaitForEvent方法,里面参数关键是第一个keycode和第二个eventType
- keycode:代表我们要注入的是按下哪个按键的事件,比如这里我们是KEYCODE_HOME
- eventType:代表我们注射了该事件后预期会获得窗口返回来的哪种AccessibilityEvent类型,比如我们这里是TYPE_WINDOW_CONTENT_CHANGE
- /* */ public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState, int eventType, long timeout)
- /* */ {
- /* 188 */ Runnable command = new Runnable()
- /* */ {
- /* */ public void run() {
- /* 191 */ long eventTime = SystemClock.uptimeMillis();
- /* 192 */ KeyEvent downEvent = new KeyEvent(eventTime, eventTime, 0, keyCode, 0, metaState, -1, 0, 0, 257);
- /* */
- /* */
- /* 195 */ if (InteractionController.this.injectEventSync(downEvent)) {
- /* 196 */ KeyEvent upEvent = new KeyEvent(eventTime, eventTime, 1, keyCode, 0, metaState, -1, 0, 0, 257);
- /* */
- /* */
- /* 199 */ InteractionController.this.injectEventSync(upEvent);
- /* */ }
- /* */
- /* */ }
- /* 203 */ };
- /* 204 */ return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout) != null;
- /* */ }
- /* */ private boolean injectEventSync(InputEvent event) {
- /* 655 */ return this.mUiAutomatorBridge.injectInputEvent(event, true);
- /* */ }
- /* */ public boolean injectInputEvent(InputEvent event, boolean sync) {
- /* 70 */ return this.mUiAutomation.injectInputEvent(event, sync);
- /* */ }
我们继续看InteractionController中真正执行注入事件线程的runAndWaitForEvents方法:- /* */ private AccessibilityEvent runAndWaitForEvents(Runnable command, UiAutomation.AccessibilityEventFilter filter, long timeout)
- /* */ {
- /* */ try
- /* */ {
- /* 161 */ return this.mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command, filter, timeout);
- /* */ }
- /* */ catch (TimeoutException e) {
- /* 164 */ Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events");
- /* 165 */ return null;
- /* */ } catch (Exception e) {
- /* 167 */ Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e); }
- /* 168 */ return null;
- /* */ }
- /* */ public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command, UiAutomation.AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException
- /* */ {
- /* 104 */ return this.mUiAutomation.executeAndWaitForEvent(command, filter, timeoutMillis);
- /* */ }