溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Monkey源碼分析之事件注入

發布時間:2020-07-11 22:46:52 來源:網絡 閱讀:469 作者:zhukev 欄目:移動開發

本系列的上一篇文章《Monkey源碼分析之事件源》中我們描述了monkey是怎么從事件源取得命令,然后將命令轉換成事件放到事件隊列里面的,但是到現在位置我們還沒有了解monkey里面的事件是怎么一回事,本篇文章就以這個問題作為切入點,嘗試去搞清楚monkey的event架構是怎么樣的,然后為什么是這樣架構的,以及它又是怎么注入事件來觸發點擊等動作的。

在看這篇文章之前,希望大家最好先去看下另外幾篇博文,這樣理解起來就會更容易更清晰了:

  • 《Monkey源碼分析番外篇之Android注入事件的三種方法比較》
  • 《Monkey源碼分析番外篇之WindowManager注入事件如何跳出進程間安全限制》
  • Android下WindowManager的作用

1. 事件架構

這里我們先從上一篇文章《Monkey源碼分析之事件源》中來自網上的monkey架構圖中截取MonkeyEvent相關的部分來看下MonkeyEvent的架構是怎么樣的。
Monkey源碼分析之事件注入
從上圖可以看到,MonkeyEvent定義了三個public方法,然后繼承下來的有5個不同的類,每個類對應一種事件類型:
  • MonkeyActivityEvent: 代表Activity相關的事件
  • MonkeyMotionEvent:代表Motion相關的事件
  • MonkeyKeyEvent: 代表Key相關的事件
  • MonkeyFlibEvent: 代表Flib相關的事件
  • MonkeyThrottleEvent:代表睡眠事件
圖中還描述了這是一個command設計模式,其實僅僅在這個圖里面是沒有看出來就是command設計模式的,往下我們會描述它究竟是怎么實現了command設計模式的。
這里我們先拿一個實例來看下一個具體的event是怎么構成的,這里為了連貫性,我們就拿一個上一篇文章描述的通過網絡事件源過來的一個事件做描述吧,這里我挑了MonkeyKeyEvent。

2. 構建MonkeyKeyEvent

這里它的父類MonkeyEvent我們就不深入描述了,因為它只是聲明了幾個方法而已,只要腦袋里知道其聲明了一個很重要的injectKeyEvent的方法,每個子類都需要通過實現它來注入事件就可以了。
現在我們先來看下MonkeyKeyEvent的構造函數:
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有多個構造函數,參數都不一樣,但是目的都只有一個,通過傳進來的參數獲得足夠的信息保存成成員變量,以便今后創建一個android.view.KeyEvent,皆因該系統事件就是可以根據不同的參數進行初始化的。比如下面的getEvent方法就是根據不同的參數創建對應的KeyEvent的。注意這系統KeyEvent是非常重要的,因為我們今后通過WindowManager注入事件就要把它的對象傳進去去驅動相應的按鍵相關的事件。
     * @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框架解析》,個人認為寫的挺不錯的。以下我按照自己的理解簡單描述了下
Monkey源碼分析之事件注入
  • 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類,不清楚的請看本人較早轉發的博客<Android 之 Window、WindowManager 與窗口管理>
  •  IWindowManager:WindowManager主要用來管理窗口的一些狀態、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等,但在android1.6以后隱藏掉了。這里之所以還能調用是因為monkey是在有android源碼的情況下編譯出來的,如果沒有源碼的話,那么就需要用到反射機制利用Class.forName來調用獲取了。

然后是PackageManager:
  • PackageManager:本類API是對所有基于加載信息的數據結構的封裝,包括以下功能:
  • 安裝,卸載應用查詢permission相關信息
  • 查詢Application相關信息(application,activity,receiver,service,provider及相應屬性等)
  • 查詢已安裝應用
  • 增加,刪除permission
  • 清除用戶數據、緩存,代碼段等
最后是SeriviceManager,具體描述請看《Android 之 ServiceManager與服務管理》
  • 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對這個接口方法的實現,這里還是要傳進來,但不作處理
整個方法代碼不多,最終就通過調用iwm.injectKeyEvent方法,傳入上面MonkeyKeyEvent初始化的時候創建的是系統KeyEvent對象,來實現按鍵事件的注入,這樣就能模擬用戶按下系統菜單等按鍵的功能了。

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")


通過ActvityManagerstartInstrumentation方法啟動一個應用

MonkeyInstrumentationEvent

iam.startInstrumentation(cn,null, 0,args,null)


睡眠

MonkeyThrottleEvent

Thread.sleep(mThrottle)


MonkeyWaitEvent

Thread.sleep(mWaitTime)



6. MonkeyEvent之Command模式

都說MonkeyEvent使用Command模式來設計得,那么究竟command設計模式是怎么樣得呢?我們先看下下圖。
Monkey源碼分析之事件注入
那么我們對號入座,看下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這個角色。
從中可以看到 MonkeyEvent的設計確實是滿足了Command模式的,那么這樣設計有什么好處呢?大家不知道的最好自己去google,這里我自己不精通設計模式,所以我只能實際情況實際分析,看下網上描述的這個設計模式的優點在我們的monkey中是否有獲得:
  •  ?。?)命令模式使新的命令很容易地被加入到系統里 :誠然!如果增加個實現處理吹下屏幕的事件(Command)的話我們只需要增加個類MonkeyBlowEvent,并實現injectEvent接口,然后在里面調用相應的Receiver來注入Blow這個事件就行了
  •  ?。?)允許接收請求的一方決定是否要否決請求 :這點本人沒有領悟好處是什么,誰清楚的請comment
  •  ?。?)能較容易地設計一個命令隊列 :確實!monkey中就是把所有的事件抽象成MonkeyEvent然后放到我們的EventQueque里面的
  •  ?。?)可以容易地實現對請求的撤銷和恢復 :這里沒有用到,因為一個event消費掉后是不能撤銷的。你總不能說你現在點擊了個按鈕后悔了,程序會點擊后先不執行等待你發送個undo命令吧。不過如果用在文檔編輯的undo功能中應該是挺不錯的
  •  ?。?)在需要的情況下,可以較容易地將命令記入日志 :也是,每個ConcreteCommand類都是獨立的,所以想把命令記錄下來是很簡單的事情該是我MonkeyKeyEvent的命令總不會變成是你MonkeyTouchEvent的命令嘛

 

作者

自主博客

微信

CSDN

天地會珠海分舵

http://techgogogo.com


服務號:TechGoGoGo

掃描碼:

Monkey源碼分析之事件注入

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女