上一篇文章《[Monkey源碼分析之運行流程](http://blog.csdn.net/zhubaitian/article/details/40395327)》給出了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設備通訊方式](http://blog.csdn.net/zhubaitian/article/details/40295559)》)
針對每一個命令都會有一個對應的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源碼分析之啟動](http://blog.csdn.net/zhubaitian/article/details/40343759)》描述的runMonkeyCles那個方法取調用執行
- 如果隊列沒有事件的話,調用上面描述的socket讀寫變量input來獲得socket中monkeyrunner發過來的一行數據(也就是一個命令字串)
- 調用translateCommand這個私有方法來針對不同的命令調用不同的MonkeyCommand實現類接口的translateCommand把字串命令翻譯成對應的事件并放到命令隊列里面(這個命令上面還沒有描述,往下我會分析下)
- 如果確實沒有命令了或者收到信號要退出了等情況下就跳出循環,否則回到循環開始繼續以上步驟
好,我們還是看看剛才那個translateCommand的私有方法究竟是怎么調用到不同命令的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并存儲到事件隊列。
比如monkeyrunner過來的字串轉換成隊列是[‘press','KEY_CODE'],獲得第一個列表成員是press,那么COMMAND_MAP對應于"press"字串這個key的MonkeyCommand就是:
~~~
COMMAND_MAP.put("press", new PressCommand());
~~~
所以調用的就是PressCommand這個MonkeyCommand接口實現類的translateCommand方法來把press這個命令轉換成對應的MonkeyKeyEvent了。
## 4.總結
最后我們結合上一章《[Monkey源碼分析之運行流程](http://blog.csdn.net/zhubaitian/article/details/40395327)》把整個獲取事件源然后執行該事件的過程整理下:
- 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的Event,看它是如何執行這些事件的。
<table cellspacing="0" cellpadding="0" width="539" class=" " style="margin:0px 0px 10px; padding:0px; border-collapse:collapse; width:668px; max-width:100%; word-wrap:break-word!important"><tbody style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><tr style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><td valign="top" width="112" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important">?</td></tr><tr style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><td valign="top" width="111" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">作者</span></p></td><td valign="top" width="112" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">自主博客</span></p></td><td valign="top" width="111" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">微信</span></p></td><td valign="top" width="112" height="13" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(190,192,191)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important">CSDN</span></p></td></tr><tr style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"><td valign="top" width="111" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important; background-color:rgb(227,228,228)"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">天地會珠海分舵</span></p></td><td valign="top" width="112" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; font-size:11px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important"><a target="_blank" href="http://techgogogo.com/">http://techgogogo.com</a></span><span style="margin:0px; padding:0px; max-width:100%; font-family:Helvetica; font-size:11px; letter-spacing:0px; word-wrap:break-word!important"/></p><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:14px; white-space:pre-wrap; font-family:Helvetica; word-wrap:break-word!important"><br style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important"/></p></td><td valign="top" width="111" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">服務號</span><span style="margin:0px; padding:0px; max-width:100%; font-size:10px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important">:TechGoGoGo</span></p><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; word-wrap:break-word!important">掃描碼</span><span style="margin:0px; padding:0px; max-width:100%; font-size:10px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important">:</span></p><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:14px; white-space:pre-wrap; font-family:Helvetica; word-wrap:break-word!important"><img src="image/47cf4f9ec59b0ef1f807a6c33ab5ce5f.jpg" alt="" style="max-width:100%; margin:0px; padding:0px; height:auto!important; word-wrap:break-word!important; width:auto!important; visibility:visible!important"/></p></td><td valign="top" width="112" height="39" style="border-style:solid; border-color:rgb(0,0,0); margin:0px; padding:4px; word-break:break-all; max-width:100%; word-wrap:break-word!important"><p style="margin-top:0px; margin-bottom:0px; padding-top:0px; padding-bottom:0px; max-width:100%; clear:both; min-height:1em; white-space:pre-wrap; color:rgb(62,62,62); font-family:'Helvetica Neue',Helvetica,'Hiragino Sans GB','Microsoft YaHei',?¢èí??oú,Arial,sans-serif; font-size:18px; line-height:28.7999992370605px; word-wrap:break-word!important"><span style="margin:0px; padding:0px; max-width:100%; color:rgb(0,0,0); font-size:11px; font-family:Helvetica; letter-spacing:0px; word-wrap:break-word!important"><a target="_blank" href="http://blog.csdn.net/zhubaitian">http://blog.csdn.net/zhubaitian</a></span><span style="margin:0px; padding:0px; max-width:100%; color:rgb(0,0,0); font-family:Helvetica; font-size:11px; letter-spacing:0px; line-height:28.7999992370605px; word-wrap:break-word!important"/></p><div><span style="margin:0px; padding:0px; max-width:100%; color:rgb(0,0,0); font-family:Helvetica; font-size:11px; letter-spacing:0px; line-height:28.7999992370605px; word-wrap:break-word!important"><br/></span></div></td></tr></tbody></table>
- 前言
- MonkeyRunner創建一個Note的實例
- MonkeyRunner在Windows下的Eclipse開發環境搭建步驟(兼解決網上Jython配置出錯的問題)
- MonkenRunner通過HierarchyViewer定位控件的方法和建議(Appium/UIAutomator/Robotium姊妹篇)
- MonkeyDevcie API 實踐全記錄
- MonkeyImage API 實踐全記錄
- EasyMonkeyDevice vs MonkeyDevice&amp;HierarchyViewer API Mapping Matrix
- adb概覽及協議參考
- MonkeyRunner源碼分析之-誰動了我的截圖?
- MonkeyRunner源碼分析之與Android設備通訊方式
- MonkeyRunner源碼分析之啟動
- Monkey源碼分析之運行流程
- Monkey源碼分析之事件源
- Monkey源碼分析番外篇之WindowManager注入事件如何跳出進程間安全限制
- Monkey源碼分析番外篇之Android注入事件的三種方法比較
- Monkey源碼分析之事件注入
- monkey源碼分析之事件注入方法變化
- MonkeyRunner源碼分析之工作原理圖
- Android自動化測試框架新書:&lt;&lt;MonnkeyRunner實現原理剖析&gt;&gt;交流