Monkey源碼分析之事件注入
本系列的上一篇文章《Monkey源碼分析之事件源》中我們描述了monkey是怎么從事件源取得命令,然后將命令轉(zhuǎn)換成事件放到事件隊(duì)列里面的,但是到現(xiàn)在位置我們還沒(méi)有了解monkey里面的事件是怎么一回事,本篇文章就以這個(gè)問(wèn)題作為切入點(diǎn),嘗試去搞清楚monkey的event架構(gòu)是怎么樣的,然后為什么是這樣架構(gòu)的,以及它又是怎么注入事件來(lái)觸發(fā)點(diǎn)擊等動(dòng)作的。
在看這篇文章之前,希望大家最好先去看下另外幾篇博文,這樣理解起來(lái)就會(huì)更容易更清晰了:
- 《Monkey源碼分析番外篇之Android注入事件的三種方法比較》
- 《Monkey源碼分析番外篇之WindowManager注入事件如何跳出進(jìn)程間安全限制》
- 《Android下WindowManager的作用》
1. 事件架構(gòu)
這里我們先從上一篇文章《Monkey源碼分析之事件源》中來(lái)自網(wǎng)上的monkey架構(gòu)圖中截取MonkeyEvent相關(guān)的部分來(lái)看下MonkeyEvent的架構(gòu)是怎么樣的。
從上圖可以看到,MonkeyEvent定義了三個(gè)public方法,然后繼承下來(lái)的有5個(gè)不同的類(lèi),每個(gè)類(lèi)對(duì)應(yīng)一種事件類(lèi)型:
- MonkeyActivityEvent: 代表Activity相關(guān)的事件
- MonkeyMotionEvent:代表Motion相關(guān)的事件
- MonkeyKeyEvent: 代表Key相關(guān)的事件
- MonkeyFlibEvent: 代表Flib相關(guān)的事件
- MonkeyThrottleEvent:代表睡眠事件
圖中還描述了這是一個(gè)command設(shè)計(jì)模式,其實(shí)僅僅在這個(gè)圖里面是沒(méi)有看出來(lái)就是command設(shè)計(jì)模式的,往下我們會(huì)描述它究竟是怎么實(shí)現(xiàn)了command設(shè)計(jì)模式的。
這里我們先拿一個(gè)實(shí)例來(lái)看下一個(gè)具體的event是怎么構(gòu)成的,這里為了連貫性,我們就拿一個(gè)上一篇文章描述的通過(guò)網(wǎng)絡(luò)事件源過(guò)來(lái)的一個(gè)事件做描述吧,這里我挑了MonkeyKeyEvent。
2. 構(gòu)建MonkeyKeyEvent
這里它的父類(lèi)MonkeyEvent我們就不深入描述了,因?yàn)樗皇锹暶髁藥讉€(gè)方法而已,只要腦袋里知道其聲明了一個(gè)很重要的injectKeyEvent的方法,每個(gè)子類(lèi)都需要通過(guò)實(shí)現(xiàn)它來(lái)注入事件就可以了。
現(xiàn)在我們先來(lái)看下MonkeyKeyEvent的構(gòu)造函數(shù):
public class MonkeyKeyEvent extends MonkeyEvent { private long mDownTime = -1; private int mMetaState = -1; private int mAction = -1; private int mKeyCode = -1; private int mScancode = -1; private int mRepeatCount = -1; private int mDeviceId = -1; private long mEventTime = -1; private KeyEvent keyEvent = null; public MonkeyKeyEvent(int action, int keycode) { super(EVENT_TYPE_KEY); mAction = action; mKeyCode = keycode; } public MonkeyKeyEvent(KeyEvent e) { super(EVENT_TYPE_KEY); keyEvent = e; } public MonkeyKeyEvent(long downTime, long eventTime, int action, int code, int repeat, int metaState, int device, int scancode) { super(EVENT_TYPE_KEY); mAction = action; mKeyCode = code; mMetaState = metaState; mScancode = scancode; mRepeatCount = repeat; mDeviceId = device; mDownTime = downTime; mEventTime = eventTime; }
MonkeyKeyEvent有多個(gè)構(gòu)造函數(shù),參數(shù)都不一樣,但是目的都只有一個(gè),通過(guò)傳進(jìn)來(lái)的參數(shù)獲得足夠的信息保存成成員變量,以便今后創(chuàng)建一個(gè)android.view.KeyEvent,皆因該系統(tǒng)事件就是可以根據(jù)不同的參數(shù)進(jìn)行初始化的。比如下面的getEvent方法就是根據(jù)不同的參數(shù)創(chuàng)建對(duì)應(yīng)的KeyEvent的。注意這系統(tǒng)KeyEvent是非常重要的,因?yàn)槲覀兘窈笸ㄟ^(guò)WindowManager注入事件就要把它的對(duì)象傳進(jìn)去去驅(qū)動(dòng)相應(yīng)的按鍵相關(guān)的事件。
* @return the key event */ private KeyEvent getEvent() { if (keyEvent == null) { if (mDeviceId < 0) { keyEvent = new KeyEvent(mAction, mKeyCode); } else { // for scripts keyEvent = new KeyEvent(mDownTime, mEventTime, mAction, mKeyCode, mRepeatCount, mMetaState, mDeviceId, mScancode); } } return keyEvent; }
支持的成員變量比較多,名字都挺淺顯易懂,我這里就簡(jiǎn)單描述兩個(gè)我們最常用的:
- mAction:代表了這個(gè)keyevent的動(dòng)作,就是系統(tǒng)KeyEvent里面定義的ACTION_DOWN,ACTION_UP或者ACTION_MULTIPLE.
- mKeyCode: 代表了你按下的究竟是哪個(gè)按鍵,同樣是在系統(tǒng)的KeyEvent定義的,比如82就代表了我們的系統(tǒng)菜單這個(gè)鍵值。
public static final int KEYCODE_MENU = 82;
3. 獲取窗口事件注入者WindowManager
既然要往系統(tǒng)注入事件,那么首先要做的事情當(dāng)然是先去獲得注入事件的管理類(lèi),然后實(shí)例化它來(lái)給我們調(diào)用了,我們注入事件用的就是WindowManager這個(gè)類(lèi),而它的實(shí)例化是在monkey啟動(dòng)的時(shí)候通過(guò)main函數(shù)調(diào)用的run那里開(kāi)始初始化的:
private int run(String[] args) { ... if (!getSystemInterfaces()) { return -3; } .... }
那么我們進(jìn)入該方法看下我們需要的WindowManager是怎么初始化的。
private boolean getSystemInterfaces() { mAm = ActivityManagerNative.getDefault(); if (mAm == null) { System.err.println("** Error: Unable to connect to activity manager; is the system " + "running?"); return false; } mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); if (mWm == null) { System.err.println("** Error: Unable to connect to window manager; is the system " + "running?"); return false; } mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); if (mPm == null) { System.err.println("** Error: Unable to connect to package manager; is the system " + "running?"); return false; } try { mAm.setActivityController(new ActivityController()); mNetworkMonitor.register(mAm); } catch (RemoteException e) { System.err.println("** Failed talking with activity manager!"); return false; } return true; }
這里我們主要是要理解里面用到的一些管理類(lèi)。這里其實(shí)我們真正值得關(guān)注的就是WindowManager這個(gè)類(lèi),因?yàn)槲覀冏⑷胝鎸?shí)時(shí)間的時(shí)候其實(shí)就是調(diào)用了它的方法。其他的類(lèi)其實(shí)在我們這篇文章中并沒(méi)有用到的,但是既然看到了就順便了解下吧。
我們先看下代碼中提到的ActivityManagerNative這個(gè)類(lèi)相關(guān)的信息,具體請(qǐng)查看轉(zhuǎn)發(fā)的博文《ActivityManager框架解析》,個(gè)人認(rèn)為寫(xiě)的挺不錯(cuò)的。以下我按照自己的理解簡(jiǎn)單描述了下
- ActivityManager: 管理著系統(tǒng)的所有正在運(yùn)行的activities,通過(guò)它可以獲得系統(tǒng)正在運(yùn)行的tasks,services,內(nèi)存信息等。正常來(lái)說(shuō)我們的應(yīng)用可以同通過(guò)(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)實(shí)例化。而它提供的方法的操作都是依賴(lài)于ActivityManagerNativeProxy這個(gè)代理類(lèi)來(lái)實(shí)現(xiàn)的
- ActivityManagerNative:ActivityManagerProxy實(shí)現(xiàn)了接口IActivitManager,但并不真正實(shí)現(xiàn)這些方法,它只是一個(gè)代理類(lèi),真正動(dòng)作的執(zhí)行為Stub類(lèi)ActivityManagerService,ActivityManagerService對(duì)象只有一個(gè)并存在于system_process進(jìn)程中,ActivityManagerService繼承于ActivityManagerNative存根類(lèi)。
- ActivityManagerProxy:代碼中的第一行mAm = ActivityManagerNative.getDefault();獲得的其實(shí)就是ActivityManagerProxy的對(duì)象,而不是ActivityManagerNative
下一個(gè)就是IWindowManager類(lèi),不清楚的請(qǐng)看本人較早轉(zhuǎn)發(fā)的博客<Android 之 Window、WindowManager 與窗口管理>
- IWindowManager:WindowManager主要用來(lái)管理窗口的一些狀態(tài)、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等,但在android1.6以后隱藏掉了。這里之所以還能調(diào)用是因?yàn)閙onkey是在有android源碼的情況下編譯出來(lái)的,如果沒(méi)有源碼的話,那么就需要用到反射機(jī)制利用Class.forName來(lái)調(diào)用獲取了。
然后是PackageManager:
- PackageManager:本類(lèi)API是對(duì)所有基于加載信息的數(shù)據(jù)結(jié)構(gòu)的封裝,包括以下功能:
- 安裝,卸載應(yīng)用查詢permission相關(guān)信息
- 查詢Application相關(guān)信息(application,activity,receiver,service,provider及相應(yīng)屬性等)
- 查詢已安裝應(yīng)用
- 增加,刪除permission
- 清除用戶數(shù)據(jù)、緩存,代碼段等
最后是SeriviceManager,具體描述請(qǐng)看《Android 之 ServiceManager與服務(wù)管理》
- ServiceManager:ServiceMananger是android中比較重要的一個(gè)進(jìn)程,它是在init進(jìn)程啟動(dòng)之后啟動(dòng),從名字上就可以看出來(lái)它是用來(lái)管理系統(tǒng)中的service。比如:InputMethodService、ActivityManagerService等。在ServiceManager中有兩個(gè)比較重要的方法:add_service、check_service。系統(tǒng)的service需要通過(guò)add_service把自己的信息注冊(cè)到ServiceManager中,當(dāng)需要使用時(shí),通過(guò)check_service檢查該service是否存在
4.WindowManager往系統(tǒng)窗口注入事件
那么到了現(xiàn)在我們已經(jīng)獲得了要WindowManager對(duì)象了,下一步就要看MonkeyKeyEvent是怎么使用這個(gè)對(duì)象來(lái)向系統(tǒng)窗口發(fā)送按鍵key事件的了。我們定位到injectEvent這個(gè)方法。
@Override public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) { if (verbose > 1) { String note; if (mAction == KeyEvent.ACTION_UP) { note = "ACTION_UP"; } else { note = "ACTION_DOWN"; } try { System.out.println(":Sending Key (" + note + "): " + mKeyCode + " // " + MonkeySourceRandom.getKeyName(mKeyCode)); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(":Sending Key (" + note + "): " + mKeyCode + " // Unknown key event"); } } // inject key event try { if (!iwm.injectKeyEvent(getEvent(), false)) { return MonkeyEvent.INJECT_FAIL; } } catch (RemoteException ex) { return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION; } return MonkeyEvent.INJECT_SUCCESS; }
注意傳入?yún)?shù)
- iwm:這個(gè)就是我們前面獲取到的WindowManager的實(shí)例對(duì)象
- iam:ActivityManager的實(shí)例對(duì)象,其實(shí)在這里我們并不需要用到,但是為了兼容其他MonkeyXXXEvent對(duì)這個(gè)接口方法的實(shí)現(xiàn),這里還是要傳進(jìn)來(lái),但不作處理
整個(gè)方法代碼不多,最終就通過(guò)調(diào)用iwm.injectKeyEvent方法,傳入上面MonkeyKeyEvent初始化的時(shí)候創(chuàng)建的是系統(tǒng)KeyEvent對(duì)象,來(lái)實(shí)現(xiàn)按鍵事件的注入,這樣就能模擬用戶按下系統(tǒng)菜單等按鍵的功能了。
5.monkey注入事件處理方式分類(lèi)
剛才以MonkeyKeyEvent作為實(shí)例來(lái)描述了該類(lèi)型的事件是怎么構(gòu)造以及如何在重寫(xiě)MonkeyEvent抽象父類(lèi)的injectEvent時(shí)調(diào)用iWindowManager這個(gè)隱藏類(lèi)的injectKeyEvent方法來(lái)注入按鍵事件的。其實(shí)其他的事件類(lèi)型重寫(xiě)MonkeyEvent的injectEvent方法的時(shí)候并不一定會(huì)真正的往系統(tǒng)窗口注入事件的,比如MonkeyThrottleEvent實(shí)現(xiàn)的injectEvent其實(shí)就僅僅是睡眠一下而已:
@Override public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) { if (verbose > 1) { System.out.println("Sleeping for " + mThrottle + " milliseconds"); } try { Thread.sleep(mThrottle); } catch (InterruptedException e1) { System.out.println("** Monkey interrupted in sleep."); return MonkeyEvent.INJECT_FAIL; } return MonkeyEvent.INJECT_SUCCESS; }
所以雖然不同的MonkeyEvent實(shí)現(xiàn)類(lèi)都實(shí)現(xiàn)了父類(lèi)的injectEvent方法,但是并不是所有的的MonkeyEvent都需要注入事件的。所有這個(gè)接口方法的名字我覺(jué)得Google 工程師起得不好,比如叫做handleEvent就不會(huì)造成混亂了(個(gè)人見(jiàn)解) 以下列表列出了monkey支持的關(guān)鍵事件的不同處理方法:
事件處理方式 | MonkeyEvent實(shí)現(xiàn)類(lèi) | 關(guān)鍵代碼 | 注釋 |
通過(guò)WindowManager注入事件 | MonkeyKeyEvent | injectKeyiwm.injectKeyEvent(getEvent(),false)Event | |
MonkeyTouchEvent | iwm.injectPointerEvent(me,false) | |
MonkeyTrackballEvent | iwm.injectTrackballEvent(me,false) | |
通過(guò)往事件設(shè)備/dev/input/event0發(fā)送命令注入事件 | MonkeyFlipEvent | FileOutputStream("/dev/input/event0") | |
通過(guò)ActvityManager的startInstrumentation方法啟動(dòng)一個(gè)應(yīng)用 | MonkeyInstrumentationEvent | iam.startInstrumentation(cn,null, 0,args,null) | |
睡眠 | MonkeyThrottleEvent | Thread.sleep(mThrottle) | |
MonkeyWaitEvent | Thread.sleep(mWaitTime) | |
6. MonkeyEvent之Command模式
都說(shuō)MonkeyEvent使用Command模式來(lái)設(shè)計(jì)得,那么究竟command設(shè)計(jì)模式是怎么樣得呢?我們先看下下圖。
那么我們對(duì)號(hào)入座,看下MonkeyEvent得設(shè)計(jì)是否滿足該command模式的要求:
- Command :MonkeyEvent,聲明了injectEvent這個(gè)execute接口方法
- ConcreteCommand: 各個(gè)MonkeyEvent實(shí)現(xiàn)類(lèi):MonkeyKeyEvent,MonkeyTouchEvent,MonkeyWaitEvent...
- Client : Monkey,記得它在runMonkeyCyles方法中調(diào)用了mEventSource.getNextEvent()方法來(lái)從事件源獲取事件,并根據(jù)各個(gè)事件源的translateCommand方法來(lái)創(chuàng)建對(duì)應(yīng)事件(ConcretCommand)吧?不記得的話請(qǐng)先看《Monkey源碼分析之運(yùn)行流程》和《Monkey源碼分析之事件源》
- Receiver : WindowManager等的實(shí)例對(duì)象,因?yàn)槭撬鼈冏罱K實(shí)施和執(zhí)行了injectXXXEvent這些請(qǐng)求。
- Invoker : Monkey,因?yàn)橹苯诱{(diào)用MonkeyKevent(command)的injectEvent(execute)這個(gè)方法的地方依然是在Monkey的runMonkeyCeles這個(gè)方法中:ev.injectEvent(mWm,mAm,mVerbose)。所以Monkey在這里既扮演餓Command角色,又扮演了Invoker這個(gè)角色。
從中可以看到 MonkeyEvent的設(shè)計(jì)確實(shí)是滿足了Command模式的,那么這樣設(shè)計(jì)有什么好處呢?大家不知道的最好自己去google,這里我自己不精通設(shè)計(jì)模式,所以我只能實(shí)際情況實(shí)際分析,看下網(wǎng)上描述的這個(gè)設(shè)計(jì)模式的優(yōu)點(diǎn)在我們的monkey中是否有獲得:
- ?。?)命令模式使新的命令很容易地被加入到系統(tǒng)里 :誠(chéng)然!如果增加個(gè)實(shí)現(xiàn)處理吹下屏幕的事件(Command)的話我們只需要增加個(gè)類(lèi)MonkeyBlowEvent,并實(shí)現(xiàn)injectEvent接口,然后在里面調(diào)用相應(yīng)的Receiver來(lái)注入Blow這個(gè)事件就行了
- ?。?)允許接收請(qǐng)求的一方?jīng)Q定是否要否決請(qǐng)求 :這點(diǎn)本人沒(méi)有領(lǐng)悟好處是什么,誰(shuí)清楚的請(qǐng)comment
- ?。?)能較容易地設(shè)計(jì)一個(gè)命令隊(duì)列 :確實(shí)!monkey中就是把所有的事件抽象成MonkeyEvent然后放到我們的EventQueque里面的
- ?。?)可以容易地實(shí)現(xiàn)對(duì)請(qǐng)求的撤銷(xiāo)和恢復(fù) :這里沒(méi)有用到,因?yàn)橐粋€(gè)event消費(fèi)掉后是不能撤銷(xiāo)的。你總不能說(shuō)你現(xiàn)在點(diǎn)擊了個(gè)按鈕后悔了,程序會(huì)點(diǎn)擊后先不執(zhí)行等待你發(fā)送個(gè)undo命令吧。不過(guò)如果用在文檔編輯的undo功能中應(yīng)該是挺不錯(cuò)的
- ?。?)在需要的情況下,可以較容易地將命令記入日志 :也是,每個(gè)ConcreteCommand類(lèi)都是獨(dú)立的,所以想把命令記錄下來(lái)是很簡(jiǎn)單的事情該是我MonkeyKeyEvent的命令總不會(huì)變成是你MonkeyTouchEvent的命令嘛
|
作者 | 自主博客 | 微信 | CSDN |
天地會(huì)珠海分舵 | http://techgogogo.com | 服務(wù)號(hào):TechGoGoGo 掃描碼: | 向AI問(wèn)一下細(xì)節(jié)
AI 助 手
|