Monkey原始碼分析之事件注入
本系列的上一篇文章《Monkey原始碼分析之事件源》中我們描述了monkey是怎麼從事件源取得命令,然後將命令轉換成事件放到事件佇列裡面的,但是到現在位置我們還沒有了解monkey裡面的事件是怎麼一回事,本篇文章就以這個問題作為切入點,嘗試去搞清楚monkey的event架構是怎麼樣的,然後為什麼是這樣架構的,以及它又是怎麼注入事件來觸發點選等動作的。
在看這篇文章之前,希望大家最好先去看下另外幾篇博文,這樣理解起來就會更容易更清晰了:
1. 事件架構
這裡我們先從上一篇文章《Monkey原始碼分析之事件源》中來自網上的monkey架構圖中擷取MonkeyEvent相關的部分來看下MonkeyEvent的架構是怎麼樣的。從上圖可以看到,MonkeyEvent定義了三個public方法,然後繼承下來的有5個不同的類,每個類對應一種事件型別:
- MonkeyActivityEvent: 代表Activity相關的事件
- MonkeyMotionEvent:代表Motion相關的事件
- MonkeyKeyEvent: 代表Key相關的事件
- MonkeyFlibEvent: 代表Flib相關的事件
- MonkeyThrottleEvent:代表睡眠事件
2. 構建MonkeyKeyEvent
MonkeyKeyEvent有多個建構函式,引數都不一樣,但是目的都只有一個,通過傳進來的引數獲得足夠的資訊儲存成成員變數,以便今後建立一個android.view.KeyEvent,皆因該系統事件就是可以根據不同的引數進行初始化的。比如下面的getEvent方法就是根據不同的引數建立對應的KeyEvent的。注意這系統KeyEvent是非常重要的,因為我們今後通過WindowManager注入事件就要把它的物件傳進去去驅動相應的按鍵相關的事件。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; }
* @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;
}
支援的成員變數比較多,名字都挺淺顯易懂,我這裡就簡單描述兩個我們最常用的:
- mAction:代表了這個keyevent的動作,就是系統KeyEvent裡面定義的ACTION_DOWN,ACTION_UP或者ACTION_MULTIPLE.
- mKeyCode: 代表了你按下的究竟是哪個按鍵,同樣是在系統的KeyEvent定義的,比如82就代表了我們的系統選單這個鍵值。
public static final int KEYCODE_MENU = 82;
3. 獲取視窗事件注入者WindowManager
既然要往系統注入事件,那麼首先要做的事情當然是先去獲得注入事件的管理類,然後例項化它來給我們呼叫了,我們注入事件用的就是WindowManager這個類,而它的例項化是在monkey啟動的時候通過main函式呼叫的run那裡開始初始化的: private int run(String[] args) {
...
if (!getSystemInterfaces()) {
return -3;
}
....
}
那麼我們進入該方法看下我們需要的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;
}
這裡我們主要是要理解裡面用到的一些管理類。這裡其實我們真正值得關注的就是WindowManager這個類,因為我們注入真實時間的時候其實就是呼叫了它的方法。其他的類其實在我們這篇文章中並沒有用到的,但是既然看到了就順便了解下吧。
我們先看下程式碼中提到的ActivityManagerNative這個類相關的資訊,具體請檢視轉發的博文《ActivityManager框架解析》,個人認為寫的挺不錯的。以下我按照自己的理解簡單描述了下
- ActivityManager: 管理著系統的所有正在執行的activities,通過它可以獲得系統正在執行的tasks,services,記憶體資訊等。正常來說我們的應用可以同通過(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)例項化。而它提供的方法的操作都是依賴於ActivityManagerNativeProxy這個代理類來實現的
- ActivityManagerNative:ActivityManagerProxy實現了介面IActivitManager,但並不真正實現這些方法,它只是一個代理類,真正動作的執行為Stub類ActivityManagerService,ActivityManagerService物件只有一個並存在於system_process程序中,ActivityManagerService繼承於ActivityManagerNative存根類。
- ActivityManagerProxy:程式碼中的第一行mAm = ActivityManagerNative.getDefault();獲得的其實就是ActivityManagerProxy的物件,而不是ActivityManagerNative
- IWindowManager:WindowManager主要用來管理視窗的一些狀態、屬性、view增加、刪除、更新、視窗順序、訊息收集和處理等,但在android1.6以後隱藏掉了。這裡之所以還能呼叫是因為monkey是在有android原始碼的情況下編譯出來的,如果沒有原始碼的話,那麼就需要用到反射機制利用Class.forName來呼叫獲取了。
然後是PackageManager:
- PackageManager:本類API是對所有基於載入資訊的資料結構的封裝,包括以下功能:
- 安裝,解除安裝應用查詢permission相關資訊
- 查詢Application相關資訊(application,activity,receiver,service,provider及相應屬性等)
- 查詢已安裝應用
- 增加,刪除permission
- 清除使用者資料、快取,程式碼段等
- ServiceManager:ServiceMananger是android中比較重要的一個程序,它是在init程序啟動之後啟動,從名字上就可以看出來它是用來管理系統中的service。比如:InputMethodService、ActivityManagerService等。在ServiceManager中有兩個比較重要的方法:add_service、check_service。系統的service需要通過add_service把自己的資訊註冊到ServiceManager中,當需要使用時,通過check_service檢查該service是否存在
4.WindowManager往系統視窗注入事件
那麼到了現在我們已經獲得了要WindowManager物件了,下一步就要看MonkeyKeyEvent是怎麼使用這個物件來向系統視窗傳送按鍵key事件的了。我們定位到injectEvent這個方法。 @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;
}
注意傳入引數
- iwm:這個就是我們前面獲取到的WindowManager的例項物件
- iam:ActivityManager的例項物件,其實在這裡我們並不需要用到,但是為了相容其他MonkeyXXXEvent對這個介面方法的實現,這裡還是要傳進來,但不作處理
5.monkey注入事件處理方式分類
剛才以MonkeyKeyEvent作為例項來描述了該型別的事件是怎麼構造以及如何在重寫MonkeyEvent抽象父類的injectEvent時呼叫iWindowManager這個隱藏類的injectKeyEvent方法來注入按鍵事件的。其實其他的事件型別重寫MonkeyEvent的injectEvent方法的時候並不一定會真正的往系統視窗注入事件的,比如MonkeyThrottleEvent實現的injectEvent其實就僅僅是睡眠一下而已: @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實現類都實現了父類的injectEvent方法,但是並不是所有的的MonkeyEvent都需要注入事件的。所有這個介面方法的名字我覺得Google 工程師起得不好,比如叫做handleEvent就不會造成混亂了(個人見解)
以下列表列出了monkey支援的關鍵事件的不同處理方法:
事件處理方式 |
MonkeyEvent實現類 |
關鍵程式碼 |
註釋 |
通過WindowManager注入事件 |
MonkeyKeyEvent |
injectKeyiwm.injectKeyEvent(getEvent(),false)Event |
|
MonkeyTouchEvent |
iwm.injectPointerEvent(me,false) |
||
MonkeyTrackballEvent |
iwm.injectTrackballEvent(me,false) |
||
通過往事件裝置/dev/input/event0傳送命令注入事件 |
MonkeyFlipEvent |
FileOutputStream("/dev/input/event0") |
|
通過ActvityManager的startInstrumentation方法啟動一個應用 |
MonkeyInstrumentationEvent |
iam.startInstrumentation(cn,null, 0,args,null) |
|
睡眠 |
MonkeyThrottleEvent |
Thread.sleep(mThrottle) |
|
MonkeyWaitEvent |
Thread.sleep(mWaitTime) |
6. MonkeyEvent之Command模式
都說MonkeyEvent使用Command模式來設計得,那麼究竟command設計模式是怎麼樣得呢?我們先看下下圖。那麼我們對號入座,看下MonkeyEvent得設計是否滿足該command模式的要求:
- Command:MonkeyEvent,聲明瞭injectEvent這個execute介面方法
- ConcreteCommand: 各個MonkeyEvent實現類:MonkeyKeyEvent,MonkeyTouchEvent,MonkeyWaitEvent...
- Client: Monkey,記得它在runMonkeyCyles方法中呼叫了mEventSource.getNextEvent()方法來從事件源獲取事件,並根據各個事件源的translateCommand方法來建立對應事件(ConcretCommand)吧?不記得的話請先看《Monkey原始碼分析之執行流程》和《Monkey原始碼分析之事件源》
- Receiver: WindowManager等的例項物件,因為是它們最終實施和執行了injectXXXEvent這些請求。
- Invoker: Monkey,因為直接呼叫MonkeyKevent(command)的injectEvent(execute)這個方法的地方依然是在Monkey的runMonkeyCeles這個方法中:ev.injectEvent(mWm,mAm,mVerbose)。所以Monkey在這裡既扮演餓Command角色,又扮演了Invoker這個角色。
- (1)命令模式使新的命令很容易地被加入到系統裡:誠然!如果增加個實現處理吹下螢幕的事件(Command)的話我們只需要增加個類MonkeyBlowEvent,並實現injectEvent介面,然後在裡面呼叫相應的Receiver來注入Blow這個事件就行了
- (2)允許接收請求的一方決定是否要否決請求:這點本人沒有領悟好處是什麼,誰清楚的請comment
- (3)能較容易地設計一個命令佇列:確實!monkey中就是把所有的事件抽象成MonkeyEvent然後放到我們的EventQueque裡面的
- (4)可以容易地實現對請求的撤銷和恢復:這裡沒有用到,因為一個event消費掉後是不能撤銷的。你總不能說你現在點選了個按鈕後悔了,程式會點選後先不執行等待你傳送個undo命令吧。不過如果用在文件編輯的undo功能中應該是挺不錯的
- (5)在需要的情況下,可以較容易地將命令記入日誌:也是,每個ConcreteCommand類都是獨立的,所以想把命令記錄下來是很簡單的事情該是我MonkeyKeyEvent的命令總不會變成是你MonkeyTouchEvent的命令嘛
相關推薦
Monkey原始碼分析之事件注入
本系列的上一篇文章《Monkey原始碼分析之事件源》中我們描述了monkey是怎麼從事件源取得命令,然後將命令轉換成事件放到事件佇列裡面的,但是到現在位置我們還沒有了解monkey裡面的事件是怎麼一回事,本篇文章就以這個問題作為切入點,嘗試去搞清楚monkey的event架
Monkey原始碼分析之事件源
上一篇文章《Monkey原始碼分析之執行流程》給出了monkey執行的整個流程,讓我們有一個概貌,那麼往後的文章我們會嘗試進一步的闡述相關的一些知識點。 這裡先把整個monkey類的結構圖給出來供大家參考,該圖源自網上(我自己的backbook pro上沒有安裝OmniG
Qt原始碼分析之事件分發器QEventDispatcherWin32
分析Qt原始碼一則想自己在開發學習中有積累,同時自己也一直有一種理念,使用她那麼就更深入的認識她。 如果有分析不正確的,還煩請各位看官指正。 事件分發器建立 在QCoreApplication建構函式中 if (!QCoreApplicationPrivate
React原始碼分析之事件系統
React原始碼分析之事件系統(轉載自阿里雲) react自己實現了一套高效的事件系統,包括了事件的註冊、儲存、分發、和重用,在DOM事件體系基礎上做了很大改進,減少了記憶體消耗,簡化了事件邏輯,並最大化的解決了IE等瀏覽器的事件不相容問題。與傳統的DOM體系相比,它有如下特點:
Monkey原始碼分析之執行流程
在《MonkeyRunner原始碼分析之與Android裝置通訊方式》中,我們談及到MonkeyRunner控制目標android裝置有多種方法,其中之一就是在目標機器啟動一個monkey服務來監聽指定的一個埠,然後monkeyrunner再連線上這個埠來發送命令,驅動mo
Android事件分發機制原始碼分析之Activity篇
在之前的事件分發分析中,曾提及到View的事件是由ViewGroup分發的,然而ViewGroup的事件我們只是稍微帶過是由Activity分發的。而我們知道,事件產生於使用者按下螢幕的一瞬間,事件生成後,經過一系列的過程來到我們的Activity層,那麼事件是怎樣從Activity傳遞
android原始碼分析之View的事件分發(上)
1、View的繼承關係圖 View的繼承關係圖如下: 其中最重要的子類為ViewGroup,View是所有UI元件的基類,而ViewGroup是容納這些元件的容器,同時它也是繼承於View類。而UI元件的繼承關係如上圖,比較常用的元件類用紅色字型標出
Live555 原始碼分析之延遲事件處理
live555的延遲事件 主要存放在BasicTaskScheduler0的成員變數 DelayQueue fDelayQueue;中 其中 D
Netty原始碼分析之ChannelPipeline—入站事件的傳播
之前的文章中我們說過ChannelPipeline作為Netty中的資料管道,負責傳遞Channel中訊息的事件傳播,事件的傳播分為入站和出站兩個方向,分別通知ChannelInboundHandler與ChannelOutboundHandler來觸發對應事件。這篇文章我們先對Netty中入站事件的傳播,也
Netty原始碼分析之ChannelPipeline—出站事件的傳播
上篇文章中我們梳理了ChannelPipeline中入站事件的傳播,這篇文章中我們看下出站事件的傳播,也就是ChannelOutboundHandler介面的實現。 1、出站事件的傳播示例 我們對上篇文章中的示例程式碼進行改造,在ChannelPipeline中加入ChannelOutboundHandler
Spark原始碼分析之Spark Shell(上)
https://www.cnblogs.com/xing901022/p/6412619.html 文中分析的spark版本為apache的spark-2.1.0-bin-hadoop2.7。 bin目錄結構: -rwxr-xr-x. 1 bigdata bigdata 1089 Dec
Netty 原始碼分析之拆包器的奧祕
為什麼要粘包拆包 為什麼要粘包 首先你得了解一下TCP/IP協議,在使用者資料量非常小的情況下,極端情況下,一個位元組,該TCP資料包的有效載荷非常低,傳遞100位元組的資料,需要100次TCP傳送,100次ACK,在應用及時性要求不高的情況下,將這100個有效資料拼接成一個數據包,那會縮短到一個TCP資
【Android】原始碼分析 - View事件分發機制
事件分發物件 (1)所有 Touch 事件都被封裝成了 MotionEvent 物件,包括 Touch 的位置、時間、歷史記錄以及第幾個手指(多指觸控)等。 (2)事件型別分為 ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_POINTER_D
Android原始碼分析之為什麼在onCreate() 和 onResume() 獲取不到 View 的寬高
轉載自:https://www.jianshu.com/p/d7ab114ac1f7 先來看一段很熟悉的程式碼,可能在最開始接觸安卓的時候,大部分人都寫過的一段程式碼;即嘗試在 onCreate() 和 onResume() 方法中去獲取某個 View 的寬高資訊: 但是列印輸出後,我們會發
netty原始碼分析之服務端啟動
ServerBootstrap與Bootstrap分別是netty中服務端與客戶端的引導類,主要負責服務端與客戶端初始化、配置及啟動引導等工作,接下來我們就通過netty原始碼中的示例對ServerBootstrap與Bootstrap的原始碼進行一個簡單的分析。首先我們知道這兩個類都繼承自AbstractB
SNMP原始碼分析之(一)配置檔案部分
snmpd.conf想必不陌生。在程序啟動過程中會去讀取配置檔案中各個配置。其中幾個引數需要先知道是幹什麼的: token:配置檔案的每行的開頭,例如 group MyROGroup v1 readSec 這行token的引數是group。
【kubernetes/k8s原始碼分析】kubelet原始碼分析之cdvisor原始碼分析
資料流 UnsecuredDependencies -> run 1. cadvisor.New初始化 if kubeDeps.CAdvisorInterface == nil { imageFsInfoProvider := cadv
【kubernetes/k8s原始碼分析】kubelet原始碼分析之容器網路初始化原始碼分析
一. 網路基礎 1.1 網路名稱空間的操作 建立網路名稱空間: ip netns add 名稱空間內執行命令: ip netns exec 進入名稱空間: ip netns exec bash 1.2 bridge-nf-c
【kubernetes/k8s原始碼分析】kubelet原始碼分析之資源上報
0. 資料流 路徑: pkg/kubelet/kubelet.go Run函式() -> syncNodeStatus () -> registerWithAPIServer() ->
【kubernetes/k8s原始碼分析】kubelet原始碼分析之啟動容器
主要是呼叫runtime,這裡預設為docker 0. 資料流 NewMainKubelet(cmd/kubelet/app/server.go) -> NewKubeGenericRuntimeManager(pkg/kubelet/kuberuntime/kuberuntime