1. 程式人生 > >第5章5節《MonkeyRunner原始碼剖析》Monkey原理分析-啟動執行: 獲取系統服務引用(原創)

第5章5節《MonkeyRunner原始碼剖析》Monkey原理分析-啟動執行: 獲取系統服務引用(原創)

天地會珠海分舵注:本來這一系列是準備出一本書的,詳情請見早前博文“尋求合作伙伴編寫《深入理解 MonkeyRunner》書籍“。但因為諸多原因,沒有如願。所以這裡把草稿分享出來,所以錯誤在所難免。有需要的就參考下吧,轉發的話還請保留每篇文章結尾的出處等資訊。

上一節我們描述了monkey的命令處理入口函式run是如何呼叫optionProcess方法來解析命令列引數的。啟動引數主要時去指導Monkey時怎麼執行起來的,但Monkey作為MonkeyRunner框架的一部分,更重要的是如何將從MonkeyRunner測試指令碼出發的命令轉化成事件來注入到系統中以進行測試自動化。如前面所說,run方法除了對啟動引數進行解析之外還做了很多其他的事情,比如這一小節需要分析的去建立對系統服務的引用。因為只有獲得這些引用之後才能實現對系統的事件注入。當然,run方法中其中有一部分程式碼是跟MonkeyRunner框架不相干的,所以我們不會花時間去分析它,也免得鑽進去後影響大家對monkey作為MonkeyRunner框架的服務的理解。

下面我們先看下run方法在processOptions之後呼叫的下一個關鍵方法getSystemInterfaces:

程式碼2-5-1 Monkey - run

 431     private int run(String[] args) {
        ...
 450         if (!processOptions()) {
 451             return -1;
 452         }
        ...
 488         if (!getSystemInterfaces()) {
 489             return
-3; 490 } ... }

processOptions方法之後到488行之前的程式碼所做的去準備monkey測試目標packages和生成隨機測試seed這些都跟作為MonkeyRunner的一個服務的monkey沒有多大關係的。這些主要是當monkey扮演的是一個獨立的隨機壓力測試工具來進行隨機對指定的package進行隨機壓力測試才有意義。所以這裡我們沒有必要花篇幅去分析它,這不會影響我們對MonkeyRunner框架的理解。

這裡需要關注的是488行的getSystemInterfaces的一個呼叫,這個方法做了一個很重要的事情,就是去獲得與Android作業系統互動的3個引用:

  • Activity互動控制服務ActivityManagerService

  • 應用包管理服務PackageManagerService

  • 視窗管理服務WindowManagerService。

這些引用在Monkey作為一個MonkeyRunner一個服務執行的時候重要性已經沒有在老版本中那麼明顯了。以往系統注入按鍵事件為例,我們現在分析的安卓4.4.2版本中,Monkey服務是用InputManagerService服務來注入事件以觸發按鍵等動作的。但是在比較老的版本中,往視窗注入事件主要是通過WindowManagerService服務來完成的,等會我們會給出兩個不同版本的按鍵事件注入原始碼來印證這個轉變。

以下我們先描述下這幾個服務的作用以及獲取的方式:

  • ActivityManagerService: 按照官方的解析,這個類的作用主要是用來為與系統中所有的正在執行的Activity進行互動提供互動介面,主要是圍繞著執行中的程序資訊,任務資訊,服務資訊等。但在Monkey中主要是在當monkey作為隨機壓力測試工具的時候用到。該服務的引用可以通過”ActivityManagerNative.getDefault()”方法獲得

  • PackageManagerService:按照官方的解析,它的作用主要就是用來獲取系統已經安裝的包的不同的資訊。也就是說它主要是用來管理應用程式包的。 同樣,它也是在當monkey作為隨機壓力測試工具才會用到,作為MonkeyRunner服務的時候並不會用到。該服務的引用可以通過AIDL機制來獲得。

  • WindowManagerService: WindowManagerService主要用來管理視窗的一些狀態、屬性、view增加、刪除、更新、視窗順序、訊息收集和處理等。在稍微老點的Android版本中,Monkey主要是用它直接來注入視窗事件的,在Android 4.1之後才引入InputManagerService服務來處理相應的事件注入請求。該服務的引用可以通過AIDL機制來獲得。其實InputManagerServce並不是說最近的版本才有的,之前就一直存在,只不過之前它是作為WindowManagerService的一個服務類存在。而自從安卓4.1版本後它就獨立出來作為一個服務執行而已。

  • InputManagerService: 主要負責的就是使用者從鍵盤,螢幕等進行操作的管理。WindowManagerService是整個視窗的大管家,而InputManagerService在監控接收到使用者出發的相應的輸入事件後最終是會呼叫WindowManagerService服務來進行處理的。

下面的安卓架構圖顯示了這些服務是處在安卓作業系統的什麼位置,我相信讀者肯定之前已經看過了,但讀者請注意該圖並沒把InputManager服務給畫出來,相信是該圖並沒有及時更新的原因。

圖5-5-1 安卓架構圖
圖5-5-1 安卓架構圖

從中我們可以看到整個安卓作業系統從上往下分為多個層次,其中最上層就是應用程,比如我們電話本,瀏覽器之類的應用就執行在這一層。支撐這些應用執行的背後是一些列的服務和系統,應用層下一層的應用程式框架層就是專門提供這種服務的,比如我們這裡提供Activity管理服務的ActivityManagerService,提供視窗和控制元件管理服務的WindowManagerService,提供應用包管理服務的PackageManagerService,以及提供使用者輸入管理的InputManagerService都是執行在這一層的。

前面3個服務雖然有些已經不會用到,但是由於歷史的原因,為了保持呼叫的一致性,有些介面還是需要傳入相應的變數到相應的方法裡面,雖然該方面並不會用到該服務。比如Monkey服務在需要往系統注入按鍵事件的時候會呼叫到MonkeyKeyEvent這個類的injectEvent方法,該方法支援的輸入引數就有上面提到的WindowManagerService和ActivityManagerService,但實際上這兩個服務並沒有用到的。請看程式碼如下:

程式碼5-5-2 MonkeyKeyEvent - injectEvent示例

 85     @Override
 86     public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
 87         if (verbose > 1) {
 88             String note;
 89             if (mAction == KeyEvent.ACTION_UP) {
 90                 note = "ACTION_UP";
 91             } else {
 92                 note = "ACTION_DOWN";
 93             }
 94             try {
 95                 System.out.println(":Sending Key (" + note + "): "
 96                         + mKeyCode + "    // "
 97                         + MonkeySourceRandom.getKeyName(mKeyCode));
 98             } catch (ArrayIndexOutOfBoundsException e) {
 99                 System.out.println(":Sending Key (" + note + "): "
100                         + mKeyCode + "    // Unknown key event");
101             }
102         }
103         KeyEvent keyEvent = mKeyEvent;
104         if (keyEvent == null) {
105             long eventTime = mEventTime;
106             if (eventTime <= 0) {
107                 eventTime = SystemClock.uptimeMillis();
108             }
109             long downTime = mDownTime;
110             if (downTime <= 0) {
111                 downTime = eventTime;
112             }
113             keyEvent = new KeyEvent(downTime, eventTime, mAction, mKeyCode,
114                     mRepeatCount, mMetaState, mDeviceId, mScanCode,
115                     KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD);
116         }
117         if (!InputManager.getInstance().injectInputEvent(keyEvent,
118                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
119             return MonkeyEvent.INJECT_FAIL;
120         }
121         return MonkeyEvent.INJECT_SUCCESS;
122     }
123 }

引數中雖然是傳進來了WindowManagerService和ActivityManagerService的服務的引用,但是最終整個注入按鍵事件的方法體中並沒有用到。最終注入事件也是通過InputManager來實現的,並沒有通過上面的這些服務。其實如果我們返回老一點的版本,會看到這裡注入按鍵事件時使用到的會是WindowManagerService。比如我查到的MonkeyKeyEvent最後一次使用WindowManagerService來進行按鍵事件注入的版本是android-4.0.4_r2.1,請看下圖:

圖5-5-2 MonkeyKeyEvent老版本事件注入方式
這裡寫圖片描述

MonkeyKeyEvent相關的詳細分析我們留給下一章來描述,這裡我們只是想通過MonkeyKeyEvent事件注入方式的變化來告訴大家其實新版本的MonkeyRunner不會再直接使用WindowManagerService來進行按鍵事件注入而已。

當然,InputManagerService服務管理的主要是使用者輸入的操作,其他一些視窗相關的操作還是需要用到WindowManagerService來進行處理的。比如模擬螢幕旋轉的MonkeyRotationEvent就不屬於使用者輸入的範疇,就需要使用到WindowManagerService服務來往系統注入相應的事件。請看下面程式碼:

程式碼5-5-3 MonkeyRotationEvent - injectEvent

 39     @Override
 40     public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
        ...
 45         // inject rotation event
 46         try {
 47             iwm.freezeRotation(mRotationDegree);
        ...
 50             }
        ...
 55     }

從以上程式碼我們可以看到模擬螢幕旋轉的操作是通過呼叫WindowManagerService服務的freezeRotation方法來實現的。

那麼我們往下還是看下getSystemInterfaces這個方法是如何獲得這些服務的引用的:

程式碼5-5-4 Monkey - getSystemInterfaces

 829     /**
 830      * Attach to the required system interfaces.
 831      *
 832      * @return Returns true if all system interfaces were available.
 833      */
 834     private boolean getSystemInterfaces() {
 835         mAm = ActivityManagerNative.getDefault();
        ...
 841         mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
        ...
 847         mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        ...
 861     }

從以上程式碼可以看到這些系統服務介面的獲取方式,除了ActivityManagerService外都是使用了AIDL的機制,以下簡要解析下這三個服務獲取的一些基本知識:

  • ActivityManagerNative.getDefault返回來的其實並不是ActivityManagerService的例項,而是代理類ActivityManagerProxy的例項,而該代理類實際上代理的就是ActivityManagerService。這裡ActivityManager,ActivityManagerService和ActivityManagerProxy使用了設計模式中的代理模式。至於它們是怎麼實現的就超出了本書的邊界了,讀者如果感興趣的本人推薦你去看羅昇陽寫的《Android系統原始碼情景分析》。

  • 下面的WindowManager和PackageManager都是通過Android的AIDL機制來獲得的。大家應該都清楚,Android系統中的每個程序都是獨立執行的,程序之間是不能直接互相呼叫的,它們是各自活在自己的虛擬世界裡的。因此,當兩個程序需要互動時就需要提供一些機制在不同程序之間進行資料通訊。比如我們要使用到的這些服務都是獨立的,為了使其他的應用程式也可以訪問這些服務,Android系統採用了遠端過程呼叫(Remote Procedure Call,RPC)方式來實現,在安卓中該機制叫做Binder機制,類似於Windows上的COM和Linux上的Corba。而與很多其他的基於RPC的解決方案一樣,Android使用一種介面定義語言(Interface Definition Language,IDL)來公開服務的介面。841-842行呼叫的IWindowManagerService和IPackageManagerService的Stub.asInterface方法就是AIDL機制中獲取對應遠端服務的代理引用的方法。有了這些服務的代理引用後,使用者就可以像本地呼叫一樣來呼叫這些遠端服務了。

看完這幾個服務的初始化,大家可能會有點疑問,不是說現在主要是用InputManagerService服務來進行事件注入嗎?怎麼沒有看到對這個服務的引用進行初始化啊? 其實從程式碼5-5-2中我們可以看到,對該服務的引用是通過InputManager的getInstance方法來實現的。其實看到這個getInstance方法,我們應該立刻能判斷出它其實是一個單例模式實現的類。

程式碼5-5-5 InputManager - getInstance方法

 183       public static InputManager getInstance()
 184    {
 185      synchronized (InputManager.class) {
 186       if (sInstance == null) {
 187         IBinder b = ServiceManager.getService("input");
 188          sInstance = new InputManager(IInputManager.Stub.asInterface(b));
 189        }
 190        return sInstance;
 191      }
 192    }

從以上程式碼我們看到它確實是以單例模式進行實現的,186行判斷如果已經存在一個InputManager這個引用InputManagerService服務的例項的話就會跳到190行直接把改引用物件返回給呼叫者。

當然,如果之前就沒有建立好該引用的話,那麼就需要在189-188行先對InputManager這個引用物件進行初始化了。之所以這了把InputManager稱呼為應用物件主要是因為它接受到使用者的命令請求後,主要就是直接拋給InputManagerService服務來進行處理的。這裡187行先通過ServiceManager的getService方法獲得代表InputManagerService這個遠端服務物件的IBinder介面(Binder是安卓程序間通訊的核心機制,往往配合AIDL這個介面定義機制來使用)。所以在188行我們可以看到該程式碼先是通過InputManager的介面IIputManager.Stub.asInterface方法獲得InputManagerService的代理引用,然後再把該引用物件作為引數傳給InputManager的建構函式來儲存起來。這樣的話當我們呼叫InputManager例項物件來進行事件注入的話就可以直接通過剛儲存起來的對InputManagerService的引用來請求InputManagerService服務來注入事件了。

這一節我們在分析獲取系統服務引用的過程中順帶簡單描述了下這些服務的一些背景知識,但這裡需要再次強調的是安卓的服務和程序間通訊IPC機制的知識是遠不止此的,但由於它們的機制以及實現的細節並不是本書的重點,所以這裡只是簡單的描述,如果大家對它們的機制和實現感興趣的,可以自行進行安卓作業系統原始碼的分析,或者查閱其他相關書籍,比如上面提到的羅昇陽著的《安卓系統原始碼情景分析》。

——— 未完待續———