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框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...
随机推荐
- iOS -实现imageView中的button响应点击事件的方法
<pre name="code" class="cpp" style="font-size: 13px;">/** imagev ...
- Azure AD(四)知识补充-服务主体
一,引言 又到了新的一周了,也到了我新的分享的时间了,还记得上一周立得Flag,其中 “保证每周输出一篇文章” ,让我特别“在意”(这里用词不太恰当).主要是我的一个大学舍友,他突然问了我一个关于写博 ...
- 掌握SpringBoot-2.3的容器探针:深入篇
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:原创分类汇总及配套源码,涉及Java.Docker.K8S.DevOPS等 关于<Spr ...
- 一文说通Dotnet Core的后台任务
这是一文说通系列的第二篇,里面有些内容会用到第一篇中间件的部分概念.如果需要,可以参看第一篇:一文说通Dotnet Core的中间件 一.前言 后台任务在一些特殊的应用场合,有相当的需求. 比方, ...
- Python使用wxpy模块实现微信两两群组消息同步
python使用wxpy模块提供的微信API接口实现两两群组的消息同步 安装模块: pip install wxpy 注意:需要同步的微信群需要保存到通讯录中 以下是自己闲来无事写的代码,暂时还存在以 ...
- Node.js 学习笔记(一)
node.js说白了就是JavaScript. node.js的性能是php的86倍(大概). 在下载完后可以用命令行打开及运行. 什么是 Web 服务器? Web服务器一般指网站服务器,是指驻留 ...
- 单页面应用下刷新当前iframe
$('button.layui-btn-elastic-2').click(function(){ var srcIframe=$(".layui-side ul li dd"). ...
- TensorFlow从0到1之TensorBoard可视化数据流图(8)
TensorFlow 使用 TensorBoard 来提供计算图形的图形图像.这使得理解.调试和优化复杂的神经网络程序变得很方便.TensorBoard 也可以提供有关网络执行的量化指标.它读取 Te ...
- spark源码解析总结
========== Spark 通信架构 ========== 1.spark 一开始使用 akka 作为网络通信框架,spark 2.X 版本以后完全抛弃 akka,而使用 netty 作为新的网 ...
- (二)JavaMail创建包含内嵌图片的邮件
链接:https://blog.csdn.net/qq_41151659/article/details/96475739 代码如下: import com.sun.mail.util.MailSSL ...