Monkey源码分析之事件源
上一篇文章《Monkey源码分析之运行流程》给出了monkey运行的整个流程,让我们有一个概貌,那么往后的文章我们会尝试进一步的阐述相关的一些知识点。
这里先把整个monkey类的结构图给出来供大家参考,该图源自网上(我自己的backbook pro上没有安装OmniGraffle工具,55美金,不舍得,所以直接贴网上的) 图中有几点需要注意下的:
- MonkeyEventScript应该是MonkeySourceScript
- MonkeyEventRandom应该是MonkeySourceRandom
- 这里没有列出其他源,比如我们今天描述的重点MonkeySourceNetwork,因为它不是由MonkeyEventQueque这个类维护的,但其维护的事件队列和MonkeyEventQueque一样都是继承于LinkedList的,所以大同小异
本文我们重点是以处理来来自网络sokcet也就是monkeyrunner的命令为例子来阐述事件源是怎么处理的,其他的源大同小异。
1. 事件队列维护者CommandQueque
在开始之前我们需要先去了解几个基础类,这样子我们才方便分析。 我们在获取了事件源之后,会把这些事件排队放入一个队列,然后其他地方就可以去把队列里面的事件取出来进一步进行处理了。那么这里我们先看下维护这个事件队列的相应代码:
public static interface CommandQueue {
/**
* Enqueue an event to be returned later. This allows a
* command to return multiple events. Commands using the
* command queue still have to return a valid event from their
* translateCommand method. The returned command will be
* executed before anything put into the queue.
*
* @param e the event to be enqueued.
*/
public void enqueueEvent(MonkeyEvent e);
}; // Queue of Events to be processed. This allows commands to push
// multiple events into the queue to be processed.
private static class CommandQueueImpl implements CommandQueue{
private final Queue<MonkeyEvent> queuedEvents = new LinkedList<MonkeyEvent>(); public void enqueueEvent(MonkeyEvent e) {
queuedEvents.offer(e);
} /**
* Get the next queued event to excecute.
*
* @return the next event, or null if there aren't any more.
*/
public MonkeyEvent getNextQueuedEvent() {
return queuedEvents.poll();
}
};
接口CommandQueue只定义个了一个方法enqueueEvent,由实现类CommandQueueImpl来实现,而实现类维护了一个MonkeyEvent类型的由LinkedList实现的队列quequeEvents,然后实现了两个方法来分别往这个队列里面放和取事件。挺简单的实现,这里主要是要提醒大家queueEvents这个队列的重要性。这里要注意的是MonkeyEventScript和monkeyEventRandom这两个事件源维护队列的类稍微有些不一样,用的是MonkeyEventQueue这个类,但是其实这个类也是继承自上面描述的LinkedList的,所以原理是一样的。 最后创建和维护一个CommandQueueImple这个实现类的一个实例commandQueque来转被对里面的quequeEvents进行管理。
private final CommandQueueImpl commandQueue = new CommandQueueImpl();
2. 事件翻译员MonkeyCommand
下一个我们需要了解的基础内部类就是MonkeCommand。从数据源过来的命令都是一串字符串,我们需要把它转换成对应的monkey事件并存入到我们上面提到的由CommandQueque维护的事件队列quequeEvents里面。首先我们看下MonkeyCommand这个接口:
/**
* Interface that MonkeyCommands must implement.
*/
public interface MonkeyCommand {
/**
* Translate the command line into a sequence of MonkeyEvents.
*
* @param command the command line.
* @param queue the command queue.
* @return MonkeyCommandReturn indicating what happened.
*/
MonkeyCommandReturn translateCommand(List<String> command, CommandQueue queue);
}
它只定义了一个实现类需要实现的方法translateCommand,从它的描述和接受的的参数可以知道,这个方法要做的事情就是把从事件源接受到的字符串命令转换成上面说的CommandQueue类型维护的那个eventQueues。以monkeyrunner发过来的press这个命令为例子,传过来给monkey的字串是"press KEY_COKDE"(请查看《MonkeyRunner源码分析之与Android设备通讯方式》) 针对每一个命令都会有一个对应的MonkeyCommand的实现类来做真正的字串到事件的翻译工作,以刚才提到的press这个命令为例子,我们看下它的实现代码:
/**
* Command to "press" a buttons (Sends an up and down key event.)
*/
private static class PressCommand implements MonkeyCommand {
// press keycode
public MonkeyCommandReturn translateCommand(List<String> command,
CommandQueue queue) {
if (command.size() == 2) {
int keyCode = getKeyCode(command.get(1));
if (keyCode < 0) {
// Ok, you gave us something bad.
Log.e(TAG, "Can't find keyname: " + command.get(1));
return EARG;
} queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, keyCode));
queue.enqueueEvent(new MonkeyKeyEvent(KeyEvent.ACTION_UP, keyCode));
return OK; }
return EARG;
}
}
以monkeyrunner过来的'press KEY_CODE'为例分析这段代码:
- 从字串中得到第1个参数,也就是key_code
- 判断key_code是否有效
- 建立按下按键的MonkeyKeyEvent事件并存入到CommandQueque维护的quequeEvents
- 建立弹起按键的MonkeyKeyEvent事件并存入到CommandQueque维护的quequeEvents(press这个动作会出发按下和弹起按键两个动作)
命令字串和对应的MonkeyCommand实现类的对应关系会由MonkeySourceNetwork类的COMMAND_MAP这个私有静态成员来维护,这里只是分析了"press"这个命令,其他的大家有兴趣就自行分析,原理是一致的。
private static final Map<String, MonkeyCommand> COMMAND_MAP = new HashMap<String, MonkeyCommand>(); static {
// Add in all the commands we support
COMMAND_MAP.put("flip", new FlipCommand());
COMMAND_MAP.put("touch", new TouchCommand());
COMMAND_MAP.put("trackball", new TrackballCommand());
COMMAND_MAP.put("key", new KeyCommand());
COMMAND_MAP.put("sleep", new SleepCommand());
COMMAND_MAP.put("wake", new WakeCommand());
COMMAND_MAP.put("tap", new TapCommand());
COMMAND_MAP.put("press", new PressCommand());
COMMAND_MAP.put("type", new TypeCommand());
COMMAND_MAP.put("listvar", new MonkeySourceNetworkVars.ListVarCommand());
COMMAND_MAP.put("getvar", new MonkeySourceNetworkVars.GetVarCommand());
COMMAND_MAP.put("listviews", new MonkeySourceNetworkViews.ListViewsCommand());
COMMAND_MAP.put("queryview", new MonkeySourceNetworkViews.QueryViewCommand());
COMMAND_MAP.put("getrootview", new MonkeySourceNetworkViews.GetRootViewCommand());
COMMAND_MAP.put("getviewswithtext",
new MonkeySourceNetworkViews.GetViewsWithTextCommand());
COMMAND_MAP.put("deferreturn", new DeferReturnCommand());
}
3. 事件源获取者之getNextEvent
终于到了如何获取事件的分析了,我们继续以MonkeySourceNetwork这个处理monkeyrunner过来的网络命令为例子,看下它是如何处理monkeyrunner过来的命令的。我们先看下它实现的接口类MonkeyEventSource
/**
* event source interface
*/
public interface MonkeyEventSource {
/**
* @return the next monkey event from the source
*/
public MonkeyEvent getNextEvent(); /**
* set verbose to allow different level of log
*
* @param verbose output mode? 1= verbose, 2=very verbose
*/
public void setVerbose(int verbose); /**
* check whether precondition is satisfied
*
* @return false if something fails, e.g. factor failure in random source or
* file can not open from script source etc
*/
public boolean validate();
}
这里我最关心的就是getNextEvent这个接口,因为就是它来从socket获得我们monkeyrunner过来的命令,然后通过上面描述的MonkeyCommand的实现类来把命令翻译成最上面的CommandQueque维护的quequeEvents队列的。往下我们会看它是怎么做到的,这里我们先看下接口实现类MonkeySourceNetwork的构造函数:
public MonkeySourceNetwork(int port) throws IOException {
// Only bind this to local host. This means that you can only
// talk to the monkey locally, or though adb port forwarding.
serverSocket = new ServerSocket(port,
0, // default backlog
InetAddress.getLocalHost());
}
所做的事情就是通过指定的端口实例化一个ServerSocket,这里要注意它绑定的只是本地主机地址,意思是说只有本地的socket连接或者通过端口转发连过来的adb端口(也就是我们这篇文章关注的monkeyrunner启动的那个adb)才会被接受。 这里只是实例化了一个socket,现在为止还没有真正启动起来的,也就是说还没有开始真正的启动对指定端口的监听的。真正开始监听是startServer这个方法触发的:
/**
* Start a network server listening on the specified port. The
* network protocol is a line oriented protocol, where each line
* is a different command that can be run.
*
* @param port the port to listen on
*/
private void startServer() throws IOException {
clientSocket = serverSocket.accept();
// At this point, we have a client connected.
// Attach the accessibility listeners so that we can start receiving
// view events. Do this before wake so we can catch the wake event
// if possible.
MonkeySourceNetworkViews.setup();
// Wake the device up in preparation for doing some commands.
wake(); input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// auto-flush
output = new PrintWriter(clientSocket.getOutputStream(), true);
}
这里除了开始监听端口之外,还如monkeyrunner对端口读写的情况一样,维护和实例化了input和output这两个成员变量来专门对端口数据进行操作。 那么这个startServer开始监听数据的方法又是由谁调用的呢?这里终于就来到了我们这一章节,也是本文的核心getNextEvent了
public MonkeyEvent getNextEvent() {
if (!started) {
try {
startServer();
} catch (IOException e) {
Log.e(TAG, "Got IOException from server", e);
return null;
}
started = true;
} // Now, get the next command. This call may block, but that's OK
try {
while (true) {
// Check to see if we have any events queued up. If
// we do, use those until we have no more. Then get
// more input from the user.
MonkeyEvent queuedEvent = commandQueue.getNextQueuedEvent();
if (queuedEvent != null) {
// dispatch the event
return queuedEvent;
} // Check to see if we have any returns that have been deferred. If so, now that
// we've run the queued commands, wait for the given event to happen (or the timeout
// to be reached), and handle the deferred MonkeyCommandReturn.
if (deferredReturn != null) {
Log.d(TAG, "Waiting for event");
MonkeyCommandReturn ret = deferredReturn.waitForEvent();
deferredReturn = null;
handleReturn(ret);
} String command = input.readLine();
if (command == null) {
Log.d(TAG, "Connection dropped.");
// Treat this exactly the same as if the user had
// ended the session cleanly with a done commant.
command = DONE;
} if (DONE.equals(command)) {
// stop the server so it can accept new connections
try {
stopServer();
} catch (IOException e) {
Log.e(TAG, "Got IOException shutting down!", e);
return null;
}
// return a noop event so we keep executing the main
// loop
return new MonkeyNoopEvent();
} // Do quit checking here
if (QUIT.equals(command)) {
// then we're done
Log.d(TAG, "Quit requested");
// let the host know the command ran OK
returnOk();
return null;
} // Do comment checking here. Comments aren't a
// command, so we don't echo anything back to the
// user.
if (command.startsWith("#")) {
// keep going
continue;
} // Translate the command line. This will handle returning error/ok to the user
translateCommand(command);
}
} catch (IOException e) {
Log.e(TAG, "Exception: ", e);
return null;
}
}
有了以上介绍的那些背景知识,这段代码的理解就不会太费力了,我这里大概描述下:
- 启动socket端口监听monkeyrunner过来的连接和数据
- 进入无限循环
- 调用最上面描述的commandQueque这个事件队列维护者实例来尝试来从队列获得一个事件
- 如果队列由事件的话就立刻返回给上一篇文章《MonkeyRunner源码分析之启动》描述的runMonkeyCles那个方法取调用执行
- 如果队列没有事件的话,调用上面描述的socket读写变量input来获得socket中monkeyrunner发过来的一行数据(也就是一个命令字串)
- 调用translateCommand这个私有方法来针对不同的命令调用不同的MonkeyCommand实现类接口的translateCommand把字串命令翻译成对应的事件并放到命令队列里面(这个命令上面还没有描述,往下我会分析下)
- 如果确实没有命令了或者收到信号要退出了等情况下就跳出循环,否则回到循环开始继续以上步骤
/**
* Translate the given command line into a MonkeyEvent.
*
* @param commandLine the full command line given.
*/
private void translateCommand(String commandLine) {
Log.d(TAG, "translateCommand: " + commandLine);
List<String> parts = commandLineSplit(commandLine);
if (parts.size() > 0) {
MonkeyCommand command = COMMAND_MAP.get(parts.get(0));
if (command != null) {
MonkeyCommandReturn ret = command.translateCommand(parts, commandQueue);
handleReturn(ret);
}
}
}
很简单,就是获取monkeyunner进来的命令字串列表的的第一个值,然后通过上面的COMMAND_MAP把字串转换成对应的MonkeyCommand实现类,然后调用其tranlsateCommand把该字串命令翻译成对应的MonkeyEvent并存储到事件队列。
COMMAND_MAP.put("press", new PressCommand());
所以调用的就是PressCommand这个MonkeyCommand接口实现类的translateCommand方法来把press这个命令转换成对应的MonkeyKeyEvent了。
4.总结
- Monkey启动开始调用run方法
- ran方法根据输入的参数实例化指定的事件源,比如我们这里的MonkeySourceNetwork
- Monkey类中的runMonkeyCyles这个方法开始循环取事件执行
- 调用Monkey类维护的mEventSource的getNextEvent方法来获取一条事件,在本文实例中就是上面表述的MonkeySourceNetwork实例的getNextEvent方法
- getNextEvent方法从CommandQueueImpl实例commandQueque所维护的quequeEvents里面读取一条事件
- 如果事件存在则返回
- getNextEvent方法启动事件源读取监听,本文实例中就是上面的startServer方法来监听monkeyrunner过来的socket连接和命令数据
- getNextEvent方法从事件源读取一个命令
- getNextEvent方法通过调用对应的的MonkeyCommand接口实现类的translateCommand方法把字串命令翻译成对应的monkey事件然后保存到commandQueque维护的quequeEvents队列
- 执行返回event的injectEvent方法
- 调用Monkey类维护的mEventSource的getNextEvent方法来获取一条事件,在本文实例中就是上面表述的MonkeySourceNetwork实例的getNextEvent方法
作者 | 自主博客 | 微信服务号及扫描码 | CSDN |
天地会珠海分舵 | http://techgogogo.com | 服务号:TechGoGoGo扫描码:![]() |
http://blog.csdn.net/zhubaitian |
Monkey源码分析之事件源的更多相关文章
- Monkey源码分析之事件注入
本系列的上一篇文章<Monkey源码分析之事件源>中我们描述了monkey是怎么从事件源取得命令,然后将命令转换成事件放到事件队列里面的,但是到现在位置我们还没有了解monkey里面的事件 ...
- monkey源码分析之事件注入方法变化
在上一篇文章<Monkey源码分析之事件注入>中,我们看到了monkey在注入事件的时候用到了<Monkey源码分析番外篇之Android注入事件的三种方法比较>中的第一种方法 ...
- 安卓Monkey源码分析之运行流程
在<MonkeyRunner源码分析之与Android设备通讯方式>中,我们谈及到MonkeyRunner控制目标android设备有多种方法,其中之一就是在目标机器启动一个monkey服 ...
- 安卓MonkeyRunner源码分析之工作原理架构图及系列集合
花了点时间整理了下MonkeyRunner的工作原理图,请配合本人博客里面MonkeyRunner其他源码分析文章进行阅读.下面整理成相应系列列表方便大家阅读: MonkeyRunner源码分析之-谁 ...
- Robotium源码分析之Instrumentation进阶-attach
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架.鉴于之 ...
- Robotium源码分析之Instrumentation进阶
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架.鉴于之 ...
- jQuery 2.0.3 源码分析 事件体系结构
那么jQuery事件处理机制能帮我们处理那些问题? 毋容置疑首先要解决浏览器事件兼容问题 可以在一个事件类型上添加多个事件处理函数,可以一次添加多个事件类型的事件处理函数 提供了常用事件的便捷方法 支 ...
- jQuery-1.9.1源码分析系列(十) 事件系统——事件体系结构
又是一个重磅功能点. 在分析源码之前分析一下体系结构,有助于源码理解.实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了:而且jQuery事件系 ...
- bootstrap源码分析之tab(选项卡)
实现tab选项卡的应用,此插件相对比较简单 源码文件: tab.js 实现原理 1.单击一个元素时,首先将原来高亮的元素取消2.然后给被单击元素进行高亮3.如果单击元素是下拉框中某个选项,则选中本身, ...
随机推荐
- JDK自带的监控分析工具JConsole
非常多开发人员认为自己懂Java编程.事实是大多数开发人员都仅仅领会到了Java平台的皮毛.所学也仅仅够应付工作. 作者将深度挖掘Java平台的核心功能.揭示一些鲜为人知的事实.帮助您解决最棘手的编程 ...
- HDU3988-Harry Potter and the Hide Story(数论-质因数分解)
Harry Potter and the Hide Story Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 ...
- C#之关于时间的整理
今天在整理C#的异步编程的时候,看到一个Stopwatch类.让我想起了,时候整理一下C#关于时间的类,望补充.斧正. DataTime类 表示时间上的一刻,即某个时间节点,通常以日期和当天的时间表示 ...
- ABP项目中的使用AutoMapper
AutoMapper之ABP项目中的使用 最近在研究ABP项目,昨天写了Castle Windsor常用介绍以及其在ABP项目的应用介绍 欢迎各位拍砖,有关ABP的介绍请看阳光铭睿 博客 AutoMa ...
- WebKit介绍及总结(一)
一 . WebKit 简单介绍 Webkit 是一个开放源码的浏览器引擎 (web browser engine) ,最初的代码来自 KDE 的 KHTML 和 KJS( 均开放源码 ) .苹果公司在 ...
- C#管理控制IIS7的方法
原文:C#管理控制IIS7的方法 转自 http://www.lob.cn/jq/csyy/7285.shtml 把在找到正确方法前遇到的挫折也拿出来与大家分享,相信不少朋友从iis6到iis7的过渡 ...
- (大数据工程师学习路径)第一步 Linux 基础入门----用户及文件权限管理
用户及文件权限管理 实验介绍 1.Linux 中创建.删除用户,及用户组等操作. 2.Linux 中的文件权限设置. 一.Linux 用户管理 Linux 是一个可以实现多用户登陆的操作系统,比如“李 ...
- 基于ORACLE建表和循环回路来创建数据库存储过程SQL语句来实现
一个.概要 在实际的软件开发项目.我们经常会遇到需要创造更多的相同类型的数据库表或存储过程时,.例如.假设按照尾号点表的ID号,然后,你需要创建10用户信息表,的用户信息放在同一个表中. 对于类型同样 ...
- Android变化如何破解几场金
我们在玩游戏的总会遇到一些东西需要购买,但是,我们可能要花钱,那么我们应该怎么办呢?这与游戏的插.我们在这里谈论的Android游戏,搜索互联网上的移动端游戏插件,您可能会发现一个叫段:八门神器.ap ...
- 【Java】【jquery】ajax垃圾问题
1.暗示HTML.JSP文件本身使用UTF-8格公式 2.HTML的head加: <META http-equiv="Content-Type" content=" ...