UiAutomator源码学习(2)-- UiAutomationBridge
从上一章对UiDevice的学习,可以看出几乎所有的操作都离不开 UiAutomationBridge。重新看一下UIDevice的构造方法:
private UiDevice(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
UiAutomation uiAutomation = instrumentation.getUiAutomation();
mUiAutomationBridge = new InstrumentationUiAutomatorBridge(
instrumentation.getContext(), uiAutomation);
// 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 = uiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
uiAutomation.setServiceInfo(info);
}
}
UiAutomationBridge 是一个抽象类。我们先看UiDevice的构造函数中,UiAutomatorBridge的实现类InstrumentationUiAutomatorBridge。这个类比较简单复写了getRotation和isScreenOn方法。接下来我们看一下这个抽象类的构造方法:
UiAutomatorBridge(UiAutomation uiAutomation) {
mUiAutomation = uiAutomation;
mInteractionController = new InteractionController(this);
mQueryController = new QueryController(this);
}
在这里初始化了 InteractionController和 QueryController这两个类的对象。在学习UiDevice的时候应该还记得,几乎所有的操作都是通过这两个类来完成的。这里是UiDevice里的pressHome方法:
/**
* Simulates a short press on the HOME button.
* @return true if successful, else return false
* @since API Level 16
*/
public boolean pressHome() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
KEY_PRESS_EVENT_TIMEOUT);
}
通过这个方法可以看到,这个InteractionController可以向系统注入事件。那接下来就来看看这个InteractionController到底是怎么向系统注入事件的。还是从构造方法看起:
public InteractionController(UiAutomatorBridge bridge) {
mUiAutomatorBridge = bridge;
}
这个InteractionController持有UiAutomatorBridge的引用。并且在这个类中定义了很多模拟用户的操作方法如,sendKeyAndWaitForEvent, touchDown,touchUp,swipe等,例如uiDevcie里用到的sendKeyAndWaitForEvent。
/**
* Send keys and blocks until the first specified accessibility event.
*
* Most key presses will cause some UI change to occur. If the device is busy, this will
* block until the device begins to process the key press at which point the call returns
* and normal wait for idle processing may begin. If no events are detected for the
* timeout period specified, the call will return anyway with false.
*
* @param keyCode
* @param metaState
* @param eventType
* @param timeout
* @return true if events is received, otherwise false.
*/
public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
final int eventType, long timeout) {
Runnable command = new Runnable() {
@Override
public void run() {
final long eventTime = SystemClock.uptimeMillis();
KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
if (injectEventSync(downEvent)) {
KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
injectEventSync(upEvent);
}
}
};
return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
!= null;
}
Line17,定义一个Runnable对象,Runnable只是一个接口,它里面只有一个run()方法,没有start()方法,所以该对象无法启动线程,必须依托其他类来启动这个线程。
在这个run方法中,定义了一个KeyEvent事件,KeyEnvet对象是android.view.*包下的类,用于报告键和按钮事件。每次按键是通过一系列按键事件来描述的。按键操作以ACTION_DOWN按键事件开始。如果密钥被保持足够长的时间以至于可以重复,则在初始按下后会出现其他具有ACTION_DOWN和getRepeatCount()非零值的密钥事件。最后一个按键事件是用于按键启动的ACTION_UP。如果取消按键,则按键事件将设置FLAG_CANCELED标志。
这个run方法里还有一个if判断条件injectEventSync,通过这个方法名就可以看出这是用来判断同步注入事件是否成功,在injectEventSync方法中,它调用了mUiAutomatorBridge.injectInputEvent(event, true);而mUiAutomatorBridge这个类的injectInputEvent方法里,是调用的mUiAutomation.injectInputEvent(event, sync);而mUiAutomation是Android SDK中 android.app.UiAutomation这个类的对象,我们回过头来看各个函数的构造函数发现,这个UiAutomation来自于UiDevice:
UiAutomation uiAutomation = instrumentation.getUiAutomation();
来看一下这个类中定义的injectInputEvent事件:
/**
* A method for injecting an arbitrary input event.
* <p>
* <strong>Note:</strong> It is caller's responsibility to recycle the event.
* </p>
* @param event The event to inject.
* @param sync Whether to inject the event synchronously.
* @return Whether event injection succeeded.
*/
public boolean injectInputEvent(InputEvent event, boolean sync) {
synchronized (mLock) {
throwIfNotConnectedLocked();
}
try {
if (DEBUG) {
Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
}
// Calling out without a lock held.
return mUiAutomationConnection.injectInputEvent(event, sync);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while injecting input event!", re);
}
return false;
}
看来这里也不是真正做事件注入的地方,mUiAutomationConnection是一个接口对象,这个对象是在UiAutomaton构造函数里初始化的。看他的实现类UiAutomationConnection中的injectInputEvent方法。
@Override
public boolean injectInputEvent(InputEvent event, boolean sync) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
throwIfNotConnectedLocked();
}
final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
: InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
final long identity = Binder.clearCallingIdentity();
try {
return mWindowManager.injectInputAfterTransactionsApplied(event, mode);
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
return false;
}
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Service.WINDOW_SERVICE));
package android.os;
public final class ServiceManager {
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
}
从这里可以看出mWindowManager是一个IBinder对象,通过这个对象调用openSession打开一个Session,实现IPC通信。看一下WindowManagerService里的
injectInputAfterTransactionsApplied方法:
@Override
public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) {
boolean isDown;
boolean isUp; if (ev instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) ev;
isDown = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
isUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
} else {
MotionEvent motionEvent = (MotionEvent) ev;
isDown = motionEvent.getAction() == MotionEvent.ACTION_DOWN;
isUp = motionEvent.getAction() == MotionEvent.ACTION_UP;
}
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE; // For ACTION_DOWN, syncInputTransactions before injecting input.
// For all mouse events, also sync before injecting.
// For ACTION_UP, sync after injecting.
if (isDown || isMouseEvent) {
syncInputTransactions();
}
final boolean result =
LocalServices.getService(InputManagerInternal.class).injectInputEvent(ev, mode);
if (isUp) {
syncInputTransactions();
}
return result;
}
syncInputTransactions()这个方法是同步系统注入事件的事物,对于action up事件是在注入之后同步,其他的事件是在事件注入之前同步。 我们主要看一下事件注入.
LocalServices 的getService方法,返回一个实现了InputManagerInternal类型的Service, InputManagerInternal是一个抽象类,而injectInputEvent也是一个抽象方法。
那接下来我们就看一下这个InputManger类型的service。这是一个系统的服务 SystemService。
/**
* Injects an input event into the event system on behalf of an application.
* The synchronization mode determines whether the method blocks while waiting for
* input injection to proceed.
* <p>
* Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
* windows that are owned by other applications.
* </p><p>
* Make sure you correctly set the event time and input source of the event
* before calling this method.
* </p>
*
* @param event The event to inject.
* @param mode The synchronization mode. One of:
* {@link #INJECT_INPUT_EVENT_MODE_ASYNC},
* {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or
* {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}.
* @return True if input event injection succeeded.
*
* @hide
*/
@UnsupportedAppUsage
public boolean injectInputEvent(InputEvent event, int mode) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
&& mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
&& mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
throw new IllegalArgumentException("mode is invalid");
}
try {
return mIm.injectInputEvent(event, mode);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
Line33,调用的是IInputManager.aidl里的injectInputEvent,通过进程之间的通信,实现了系统的事件注入。到此事件注入的流程分析完毕,先到此为止。再想深入研究就是Native层的逻辑了。
UiAutomator源码学习(2)-- UiAutomationBridge的更多相关文章
- UiAutomator源码学习(1)-- UiDevice
UiDevice提供对设备状态信息的访问. 也可以使用此类来模拟设备上的用户操作,例如按键盘或按Home和Menu按钮.UiDevice类的完整源码 UiDevice.java 废话不多说,我们首先根 ...
- UiAutomator源码分析之注入事件
上一篇文章<UiAutomator源码分析之UiAutomatorBridge框架>中我们把UiAutomatorBridge以及它相关的类进行的描述,往下我们会尝试根据两个实例将这些类给 ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- jQuery源码学习感想
还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
- 我的angularjs源码学习之旅2——依赖注入
依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...
随机推荐
- 5、vue的传值
1-父组件向子组件 -父组件向孙子组件传值(属性传值) //父组件<template> <div id='app'> <headera v-bind:hea=" ...
- refs转发 React.forwardRef
2020-04-01 refs转发 前几天刚总结完ref&DOM之间的关系,并且想通了3种ref的绑定方式 今天总结一下refs转发 这是react中一直困扰我的一个点 示例: 输入: wor ...
- Edge浏览器现已支持Tampermonkey(油猴)
Tampermonkey,Greasemonkey,这种扩展可以让我们的浏览器自动运行我们自己定义的脚本,然后就出现了相关网站(比如https://greasyfork.org/zh-CN)让大家在上 ...
- (九)HttpClient获取cookies
原文链接:https://blog.csdn.net/cheny1p1ng/article/details/90780024 旧版本DefaultHttpClient 使用getCookieStore ...
- JSON案例
原文链接:https://zhuanlan.zhihu.com/p/62763428 json字符串->JSONObject 用JSON.parseObject()方法即可将JSon字符串转化为 ...
- Arduino+sim800C家居安防火灾报警 拨打电话 发送短信例程程序
家居安防报警器,参考程序. 火灾报警 涉及用sim800c发短信,拨打电话通知. 接线: Sim800c 3.3V -> Arduino 3.3V Sim800c GND -> Ardui ...
- Probius:一个功能强大的自定义任务系统
断更的这些日子,我又折腾了一个轮子,文末参考源码 大约在一年半以前写过一篇文章『探秘varian:优雅的发布部署程序』,里边有讲到我们采用类似lego的模块化方式来构建CICD的流程,虽能满足我们的需 ...
- 前后端分离项目 nginx配置实践
新项目采用前后端分离的方式开发,前后端代码打算分开部署(同机器且同域名),但打算支持后端依然可访问静态资源. 搜索nginx配置大部分都通过url前缀进行转发来做前后端分离,不适用目前项目. 说明 前 ...
- 重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 场地和场景的重要性 射击
- Day8-微信小程序实战-交友小程序-首页用户列表渲染及多账号调试及其点赞功能的实现
在这之前已经把编辑个人的所有信息的功能已经完成了 之后先对首页的列表搞动态的,之前都是写死的静态 1.之前都是把好友写死的,现在就在js里面定义一个数组,用循环来动态的绑定 在onReady中定义,取 ...