第8章6節《MonkeyRunner源碼剖析》MonkeyRunner啟動運行過程-啟動Monkey

來源:互聯網
上載者:User

標籤:

大家可能會覺得奇怪,為什麼啟動目標裝置端的monkey進程會放在“運行測試指令碼”這一節之後來闡述。

縱觀前面整個MonkeyRunner的啟動流程,我們看到並沒有提及到monkey進程啟動的地方。那麼就奇怪了,monkey是什麼時候被MonkeyRunner啟動起來的呢?

我們的測試指令碼一開始時幾乎毫無例外的都需要執行一個調用:MonkeyRunner.waitForConnection(),如果有多個裝置串連到主機的話還需要指定裝置序號,還可以指定等待串連的Timeout時間,比如:MonkeyRunner.waitForConnection(“xxx”,10000),其中xxx代表序號,10000代表逾時Timeout時10秒;如果沒有指定參數的話架構會提供預設的參數。其實monkey的啟動就是在waitForConnection的一些列調用中完成的。

MonkeyRunner調用waitForConnection的目的是什麼呢?顧名思義,當然是等待串連上裝置了。但是細想下覺得又不對,在上面MonkeyRunner啟動運行過程中,不是已經在啟動裝置監控線程DeviceMonitor中建立了代表目標裝置的Device類型的mDevices列表了嗎?表明裝置已經串連上來了啊。這時就算我們不運行任何monkeyrunner指令碼,發送“adb devices”命令也能夠列出來所有已經串連上來的裝置了。那麼難道waitForConnection起名字Google給起錯了嗎?其實不是的,這裡waitForConnection確實是在等待裝置的串連,只是等待的不是上面Device裝置的串連,因為確實這個裝置在啟動裝置監控線程的時候已經串連上來的了,這裡等待的是去串連上AdbChimpDevice這個裝置。這裡大家可能迷糊了,這又是什麼裝置啊?這裡其實有幾個抽象裝置類(抽象裝置類在這裡的意思不是說這些類是抽象的,而是說這些類代表的裝置是真是裝置在主機端的一個虛擬抽象),如果我做如下的解析大家應該就很好理解了:

  • Device(ddmlib):代表的是通過ADB進行控制的裝置
  • ChimpManager(chimpchat): 代表的是通過money進行控制的裝置
  • AdbChimpDevice(chimpchat): 是一個更高層次的裝置概念的抽象,它包含了以上的通過ADB控制的Device裝置,同時也包含了上面的通過monkey進行控制的裝置,當調用過來的時候AdbChimpDevice會根據究竟是需要發送命令到ADB伺服器還是發送到monkey來決定使用的是哪個裝置
  • MonkeyDevice(monkeyrunner): 可以被測試指令碼直接使用的一個高層抽象裝置,它所有的API基本上都是分發到AdbChimpDevice來執行的

大家看下下面的類圖中AdbChimpDevice和Device以及ChimpManager的關係就會更清楚了:AdbChimpDevice擁有兩個關鍵的成員變數,一個是IDevice類型,其實就是Device的父類的一個執行個體;一個是ChimpManager類型的一個執行個體。在下一章分析MonkeyDevice的實現原理的時候我們就會看到MonkeyDevice過來的一個調用,比如press,最終是被分發到AdbChimpDevice,AdbChimpDevice再來決定是通過ChimpManager來往monkey發送命令還是通過Device往ADB伺服器發送命令。


圖8-6-1 啟動Monkey過程涉及的關鍵類

我們先分析下類圖中每個類的關係以及簡要描述下MonkeyRunner是怎麼啟動 monkey的,然後再去分析它們相關的實現代碼。

  • MonkeyRunner: 每個測試指令碼相信都會首先用這個類的waitForConnection方法來獲得一個MonkeyDevice對象來去實現點擊等操作,但此後在指令碼中這個類基本上就不見蹤影了。它擁有一個ChimpChat的執行個體變數chimpchat,上面我們已經分析過它是在MonkeyRunnerStarter執行個體化的時候被設定的。waitForConnection方法最終會通過chimpchat這個執行個體調用ChimpChat類的waitForConnection方法
  • MonkeyDevice: 可以說是指令碼使用最頻繁的一個類,基本所有和目標裝置互動的操作都是通過調用它來完成的。它擁有大量的操作目標裝置的方法,比如press,takeSnapShot,type等。注意它擁有了一個iChimpDevice類型的impl執行個體,通過下面的分析我們會看到它其實就是AdbChimpDevice的一個執行個體,因為AdbChimpDevice也是實現了IChimpDevice介面的。根據上面的描述AdbChimpDevice是一個高層抽象出來的裝置,它擁有ChimpManager的執行個體可以向monkey發送命令,也擁有Device執行個體可以向ADB伺服器發送adb命令
  • ChimpChat: 這個類我們在上面的啟動流程中已經分析過,它是在MonkeyRunnerStarter執行個體化的過程中被執行個體化的,而它執行個體化的過程中又會啟動AndroidDebugBridge和DeviceMonitor。執行個體化完後,MonkeyRunnerStarter建構函式最後會調用MonkeyRunner的setChimpChat靜態方法把它設定到MonkeyRunner的chimpchat這個成員變數裡面。ChimpChat擁有一個IChimpBackend類型的mBackend對象,其實就是一個AdbBackend對象了,因為根據前面啟動流程的分析,當ChimpChat建構函式在執行個體化完AdbBackend對象後會將該對象儲存下來到mBackend這個成員對象裡面,ChimpChat的waitForConnection方法就是通過這個對象來調用AdbBackend的waitForConnection方法的
  • AdbBackend:這個類同樣在上面的啟動流程中已經分析過,它是在執行個體化ChimpChat的過程中被執行個體化的,而它執行個體化的過程中又會去執行個體化AndroidDebugBridge,所以它持有一個AndroidDebugBridge的執行個體。通過這個執行個體AdbBackend就能調用AndroidDebugBridge的getDevices方法來獲得所有已經串連上來的Device裝置列表,並根據waitForConnection傳進來的裝置序號來找到目標Device裝置,再來執行個體化AdbChimpDevice這個高層抽象裝置並返回該裝置給上層
  • AdbChimpDevice: 這個就是前面一直強調的高層裝置,擁有代表ADB裝置的device執行個體,同時也擁有代表monkey裝置的manager執行個體。MonkeyRunner類的waitForConnection方法最終獲得的就是這個類的執行個體,並以這個執行個體為參數構造MonkeyDevice對象的,構造過程中MonkeyDevice會把這個執行個體儲存到MonkeyDevice對象的impl這個成員變數裡面,所以上面描述的MonkeyDevice類的press,type,takeSnapshot等API調用就可以通過impl這個對象來調用其對應的press,type,takeSnapshot等方法,並將這些方法分發到monkey裝置ChimpManager或者ADB裝置Device去執行
  • ChimpManager: 代表了一個monkey裝置,也就是說所有monkey相關的請求都是發送到ChimpManager來進行處理的。它擁有幾個專門和monkey通訊的成員變數,比如monkeSocket是用來和monkey進行串連的;monkeyWriter是用來往該socket寫資料的,也就是發送請求的;monkeyReader是用來往該socket讀資料的,也就是讀取請求結果的。同時它擁有很多monkey相關的調用,比如touch等
  • AndroidDebugBridge: 這個類同樣在前面的MonkeyRunner啟動流程中已經分析過它是怎麼啟動起來的,前面描述的它的主要功能是將ADB伺服器和DeviceMonitor給啟動起來。在這一節它的主要功能將會是因為它維護了mDeviceMonitor這個DeviceMonitor的執行個體,所以能通過它來獲得DeviceMonitor維護的最新的Device列表。為什麼要獲得這個列表,請看上面AdbBackend類的描述
  • DeviceMonitor: 這個類在這裡的主要功能就是提供了getDevices這個方法來獲得它維護的最新的Device裝置列表

有了以下的基本認知之後,我們就可以通過分析代碼來闡述Monkey是怎麼在使用者調用MonkeyRunner.waitForConnection的方法引發的一系列調用過程中啟動起來的了,我們先看下MonkeyRunner的這個waitForConnection方法:

 62   public static MonkeyDevice waitForConnection(PyObject[] args, String[] kws) 63   { 64     ArgParser ap = JythonUtils.createArgParser(args, kws); 65     Preconditions.checkNotNull(ap); 66     long timeoutMs; 67     try 68     { 69       double timeoutInSecs = JythonUtils.getFloat(ap, 0); 70       timeoutMs = (timeoutInSecs * 1000.0D); 71     } catch (PyException e) { 72       timeoutMs = Long.MAX_VALUE; 73     } 74  75     IChimpDevice device = chimpchat.waitForConnection(timeoutMs, ap.getString(1, ".*")); 76  77     MonkeyDevice chimpDevice = new MonkeyDevice(device); 78     return chimpDevice; 79   }
代碼8-6-1 MonkeyRunner - waitForConnection

指令碼調用到的MonkeyRunner,MonkeyDevice等這些類都是通過JAVA來編寫的,而指令碼自身卻是通過jython(可以被調用JAVA的python)編寫的,所以它們之前的參數需要有一個轉換的機制,至於它們是怎麼轉換的不是重點,所以我自己都沒有去研究過jython的實現原理,因為這不影響我對MonkeyRunner架構的理解。好,我們繼續對上面代碼的分析:

  • 75行: 調用chimpchat對象的waitForConnection方法來獲得一個AdbChimpDevice的高層抽象裝置執行個體
  • 79-78行: 將上面的AdbChimpDevice執行個體作為參數傳入到MonkeyDevice來構造一個MonkeDevice對象並返回給測試代碼,這樣測試代碼就可以通過操作該MonkeyDevice執行個體來控制目標裝置了

我們重點往下分析ChimpChat的waitForConnection方法,至於MonkeyDevice建構函式,我們在下一章會對整個MonkeyDevice的運行原理進行一個詳盡的分析,所以這裡就不重複了。

 89   public IChimpDevice waitForConnection(long timeoutMs, String deviceId) 90   { 91     return this.mBackend.waitForConnection(timeoutMs, deviceId); 92   } 98   public IChimpDevice waitForConnection() 99   {100     return this.mBackend.waitForConnection(2147483647L, ".*");101   }
代碼8-6-2 ChimpChat - waitForConnection

ChimpChat提供了兩個waitForConnection方法,其中一個是不帶參數的,相當於使用者在指令碼直接調用MonkeyDevice.waitForConnection();另外一個是帶參數long類型逾時和裝置序號的。無論是哪個方法ChimpChat都是很簡單只有一行,調用的是mBackend的waitForConnection,只是如果使用者沒有提供參數的話,ChimpChat會預設初始化逾時和裝置序號這兩個參數,其中裝置序號會被初始化為一個Regex”.*”,代表任意一個首先找到的裝置。這裡的mBackend就是前面分析“啟動AndroidDebugBridge”的過程中執行個體化的AdbBackend對象,所以我們要定位到該類的waitForConnection方法。

116   public IChimpDevice waitForConnection(long timeoutMs, String deviceIdRegex)117   {118     do {119       IDevice device = findAttachedDevice(deviceIdRegex);120 121       if ((device != null) && (device.getState() == IDevice.DeviceState.ONLINE)) {122         IChimpDevice chimpDevice = new AdbChimpDevice(device);123         this.devices.add(chimpDevice);124         return chimpDevice;125       }126       try127       {128         Thread.sleep(200L);129       } catch (InterruptedException e) {130         LOG.log(Level.SEVERE, "Error sleeping", e);131       }132       timeoutMs -= 200L;133     } while (timeoutMs > 0L);134    135 136     return null;137   }
代碼8-6-3 AdbBackend - waitForConnection

該方法會做一個while迴圈一直等待找到一個跟使用者提供的或者預設的序號吻合的裝置知道逾時,如果找到了該裝置就會以該裝置作為參數去構造一個AdbChimpDevice的執行個體對象並返回:

  • 133行: 進入while迴圈,如果在逾時時間內還沒有串連上目標裝置的話就一直迴圈下去
  • 128和132行: 每次迴圈如果沒有串連上目標裝置的話就睡眠0.2秒再進行下次迴圈
  • 119行: 根據提供的裝置序號(Regex)尋找目標Device裝置,去哪裡尋找呢?其實就是去DeviceMonitor監控線程對象維護的最新Device裝置隊列中尋找
  • 122行: 找到目標Device裝置後,將該裝置對象傳進去到AdbChimpDevice的建構函式中去執行個體化AndroidChimpDevice對象並返回。我們往下會看到AdbChimpDevice在執行個體化的過程中會去實現ChimpManager對象來啟動monkey進行通訊

往下我們先重點分析119行findAttachedDevice是如何根據裝置序號找到目標Device對象的,然後再去分析AdbChimpDevice是如何構造起來的。我們先進入到findAttachedDevice方法中:

99   private IDevice findAttachedDevice(String deviceIdRegex)100   {101     Pattern pattern = Pattern.compile(deviceIdRegex);102     for (IDevice device : this.bridge.getDevices()) {103       String serialNumber = device.getSerialNumber();104       if (pattern.matcher(serialNumber).matches()) {105         return device;106       }107     }108     return null;109   }
代碼8-6-4 AdbBackend - findAttachedDevice

在第3節分析“啟動AndroidDebugBridge”的過程中我們已經學習了AdbBackend在初始化AndroidDebugBridge對象後會將該執行個體儲存到自身的bridge成員對象裡面,所以這個方法做的事情就是通過這個AndroidDebugBridge執行個體去獲得所有最新的Device裝置列表(102行),然後比對每個Device裝置的序號和參數提供的時候吻合,如果是的話就返回(103-105行)。

我們先看下是怎麼獲得裝置列表的:

 482   public IDevice[] getDevices() 483   { 484     synchronized (sLock) { 485       if (this.mDeviceMonitor != null) { 486         return this.mDeviceMonitor.getDevices(); 487       } 488     } 489  490     return new IDevice[0]; 491   }
代碼8-6-5 AdbBackend - getDevices

AndroidDebugBridge對象起啟動DeviceMonitor裝置監控線程對象後會將該對象儲存起來到mDeviceMonitor成員變數裡面,這些我們在前面都學習過了。這裡AndroidDebugBridge的getDevices方法就是去DeviceMonitor對象中調用其getDevices方法來擷取最新的裝置列表的:

129   Device[] getDevices()130   {131     synchronized (this.mDevices) {132       return (Device[])this.mDevices.toArray(new Device[this.mDevices.size()]);133     }134   }
代碼8-6-6 DeviceMonitor - getDevices

在第4節“啟動裝置監控線程DeviceMonitor”中我們已經學習了當DeviceMonitor在往ADB伺服器發送監控命令“host:track-devices”後,一旦監控到有裝置新增加或者移除或者狀態變化等,就會將裝置的改動更新儲存到mDevices這個Device列表裡面。以上方法返回的就是這一整個列表。

代碼8-6-4 AdbBackend - findAttachedDevice中,findAttachedDevice在獲得這個裝置列表後,會取出每個裝置的序號來和目標裝置序號進行對比尋找直接找到吻合的裝置才返回,那麼我們看下其對應的擷取序號的方法device.getSerialNumber():

244   public String getSerialNumber() 245   { 246     return this.mSerialNumber; 247   }
代碼8-6-7 Device - getSerialNumber

直接返回的就是Device裝置儲存的mSerialNumber變數,這個變數就是代表了對應裝置的序號。大家應該還記得第4節”啟動裝置監控線程DeviceMonitor”中分析到“processIncomingDeviceData”方法時,一旦ADB伺服器將最新的裝置列表發送過來的時候就會取出每個裝置的序號和裝置狀態來初始化Device裝置來把該裝置的序號,裝置狀態儲存到該Device裝置對應的mSerialNumber和mStat成員變數中。

在獲得比對裝置序號後,findAttachedDevice就會跟提供的序號進行比對,如果吻合就返回給調用者” 代碼8-6-3 AdbBackend - waitForConnection”了。而AdbBackend的waitForConnection在獲得這個Device執行個體後就會把它傳到AdbChimpDevice的建構函式中來構造AdbChimpDevice的執行個體對象。我們看看它的建構函式是怎麼做的:

 68   public AdbChimpDevice(IDevice device) 69   { 70     this.device = device; 71     this.manager = createManager("127.0.0.1", 12345); 72     73     Preconditions.checkNotNull(this.manager); 74   }
代碼8-6-8 AdbChimpDevice建構函式

如前面一直強調的,AdbChimpDevice是一個很重要的類,它是一個高層抽象的裝置對象,它組合了代表通過monkey控制的裝置ChimpManager和通過ADB控制的裝置Device。這個組合關係就是通過上面這個AdnbChimpDevice建構函式體現出來的了。第70行組合的就是Device裝置,71行組合的就是ChimpManager執行個體。只是Device執行個體是在啟動裝置監控線程DeviceMonitor中就已經執行個體化建立好的,而ChimpManager是在這個時候才進行建立的。建立的時候指定的是本機迴環IP地址”127.0.0.1”,連接埠指定是monkey本地轉送連接埠12345

建立ChimpManager的調用createManager的代碼有點長,我們會分兩部分來進行分析,其中第一部分是啟動Monkey,第二部分是建立ChimpManager。我們先看第一部分:

123   private ChimpManager createManager(String address, int port) {124     try {125       this.device.createForward(port, port);126     } catch (TimeoutException e) {127       LOG.log(Level.SEVERE, "Timeout creating adb port forwarding", e);128       return null;129     } catch (AdbCommandRejectedException e) {130       LOG.log(Level.SEVERE, "Adb rejected adb port forwarding command: " + e.getMessage(), e);131       return null;132     } catch (IOException e) {133       LOG.log(Level.SEVERE, "Unable to create adb port forwarding: " + e.getMessage(), e);134       return null;135     }136 137     String command = "monkey --port " + port;138     executeAsyncCommand(command, new LoggingOutputReceiver(LOG, Level.FINE));139 140     try141     {142       Thread.sleep(1000L);143     } catch (InterruptedException e) {144       LOG.log(Level.SEVERE, "Unable to sleep", e);145     }146     InetAddress addr;147     try148     {149       addr = InetAddress.getByName(address);150     } catch (UnknownHostException e) {151       LOG.log(Level.SEVERE, "Unable to convert address into InetAddress: " + address, e);152       return null;153     }...}
代碼8-6-9 AdbChimpDevice - createManager之啟動monkey

createManager首先做的事情就是去把目標裝置端的monkey服務進程給啟動起來接收MonkeyRunner測試指令碼發送過去的請求。代碼流程如下所示:

  • 125行: 設定本機到目標機器monkey進程監聽連接埠的連接埠轉寄,調用的是Device的createForward的方法,這個方法我們在下一章描述Device類詳解的時候會進行分析。這裡只需要它基本可以看作是在命令列發送”adb forward 12345 12345“來完成從本機12345連接埠到遠程monkey監控的12345連接埠的轉寄就好了。設定好連接埠轉寄後往下的代碼就能直接連接原生12345連接埠,這就等同於連上的是遠端目標裝置中monkey監聽的12345連接埠了
  • 139-138行: 設定好monkey連接埠轉寄後,createManager方法就會往ADB伺服器發送shell命令”monkey --port 12345”來啟動monkey去監聽連接埠12345。發送adb shell命令使用的方法是createAsyncCommand方法,其實該方法沒有什麼好分析的,因為它把發送命令請求直接轉寄給Device類的executeShellCommand而已,而executeShellCommand這個方法我們也是在下一章會進行分析
  • 149行: 將本機監聽地址“127.0.0.1”轉換成InetAddress對象格式,這樣往下建立Socket串連的時候才能直接使用

createManager之啟動monkey到了這裡就完成了,往下我們繼續看第二部分createManager之建立ChimpManager:

123   private ChimpManager createManager(String address, int port) {... //啟動monkey代碼略159     boolean success = false;160     ChimpManager mm = null;161     long start = System.currentTimeMillis();162 163     while (!success) {164       long now = System.currentTimeMillis();165       long diff = now - start;166       if (diff > 30000L) {167         LOG.severe("Timeout while trying to create chimp mananger");168         return null;169       }170       try171       {172         Thread.sleep(1000L);173       } catch (InterruptedException e) {174         LOG.log(Level.SEVERE, "Unable to sleep", e);175       }176       Socket monkeySocket;177       try178       {179         monkeySocket = new Socket(addr, port);180       } catch (IOException e) {181         LOG.log(Level.FINE, "Unable to connect socket", e);182         success = false; }183       continue;184      185       try186       {187         mm = new ChimpManager(monkeySocket);188       } catch (IOException e) {189         LOG.log(Level.SEVERE, "Unable to open writer and reader to socket"); }190       continue;191      192       try193       {194         mm.wake();195       } catch (IOException e) {196         LOG.log(Level.FINE, "Unable to wake up device", e);197         success = false; }198       continue;199      200       success = true;201     }202    203     return mm;}
代碼8-6-10 AdbChimpDevice - createManager之建立ChimpManager

其實上面一堆代碼無非是在一個迴圈中做了3個事情:

  • 179行:  建立串連到本機monkey轉送連接埠的Socket對象
  • 187行:  根據該Socket物件建構ChimpManager執行個體,ChimpManager的詳細分析會放到下一章的描述ChimpManager類詳解的時候進行分析
  • 194行:  往monkey發送命令去喚醒休眠螢幕,如果螢幕是在休眠狀態的話。wake的原理也會在下一章進行分析

分析到這裡這一小節的目標就已經達到了,我們已經學習到了monkey服務進程是如何在指令碼中通過調用MonkeyRunner的waitForConnection方法啟動起來的了,同時我們也學習到了AdbChimpDevice和ChimpManager這兩個關鍵類建立的相關知識點。

下一小節我們來嘗試把本章學到的內容進行一個總結。

註:更多文章請關注公眾號:techgogogo或個人部落格http://techgogogo.com。當然,也非常歡迎您直接(zhubaitian1)勾搭。本文由天地會珠海分舵原創。轉載請自覺,是否投訴維權看心情。


第8章6節《MonkeyRunner源碼剖析》MonkeyRunner啟動運行過程-啟動Monkey

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.