溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Monkey源碼分析之事件注入

發(fā)布時(shí)間:2020-07-11 22:46:52 來(lái)源:網(wǎng)絡(luò) 閱讀:439 作者:zhukev 欄目:移動(dòng)開(kāi)發(fā)

本系列的上一篇文章《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)是怎么樣的。
Monkey源碼分析之事件注入
從上圖可以看到,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)單描述了下
Monkey源碼分析之事件注入
  • 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ò)ActvityManagerstartInstrumentation方法啟動(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ì)模式是怎么樣得呢?我們先看下下圖。
Monkey源碼分析之事件注入
那么我們對(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

    掃描碼:

    Monkey源碼分析之事件注入

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI