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

來源:互聯網
上載者:User

標籤:

使用者在命令列運行monkeyrunner命令來執行測試指令碼的時候ADB伺服器有可能還沒有起來,AndroidDebugBridge類的主要作用之一就是去開啟一個新的進程來啟動ADB伺服器,這樣我們的測試指令碼才能發送命令給ADB伺服器去驅動目標裝置做事情,比如安裝或者刪除待測應用的安裝包等。

MonkeyRunner在啟動的過程中會牽涉到一系列的調用並關聯到不同的類來做不同的事情。


圖8-3-1 啟動AndroidDebugBridge涉及的類別關係

以上類圖列出了啟動AndroidDebugBridge涉及的關鍵類的關係,同時列出了在啟動過程中每個類設計的關鍵成員方法和成員變數,在進入程式碼分析之前我們先對這些做一些描述:

  • MonkeyRunnerStarter: 這個類我們在上一小節已經碰到過,它就是monkeyrunner這個jar包的入口類。它擁有的options對象我們已經分析過。這一小節我們主要分析的是從monkeyRunnerStarter建構函式引發的一系列以啟動AndroidDebugBridge為目標的調用。過程中它會執行個體化ChimpChat這個類,並把執行個體通過MonkeyRunner的setChimpChat方法儲存到MonkeyRunner的chimpchat這個成員變數裡面儲存起來。儲存起來有什麼用呢?大家應該注意到MonkeyRunner,ChimpChat和AdbBackend類都擁有waitForConnection這個方法,該方法一般是在我們的測試指令碼最開始的時候調用的,目的是獲得一個叫做AdbChimpDevice的高層抽象裝置對象,這個我們在第6小節”啟動Monkey”中會詳細描述。這裡我們主要向說明的是MonkeyRunner儲存了ChimpChat執行個體後,它的waitForConnection方法就能通過chimpchat對象直接調用ChimpChat類的waitForConnection方法。
  • ChimpChat: 這個類主要的功能是組合AdbBackend這個類來實現對AndroidDebugBridge的建立。MonkeyRunnerStarter依賴這個類來建立AdbBackend類的執行個體並把該執行個體儲存起來到該類的mBackend這個成員變數裡面。儲存起來的目的跟上面MonkeyRunner組合ChimpChat的目的一樣,這樣它的waitForConnection方法就能通過mBackend這個對象來調用AdbBackend類的waitForConnection方法
  • AdbBackend: 負責建立AndroidDebugBridge執行個體。它在自身執行個體化的時候會建立AndroidDebugBridge的執行個體並把該執行個體儲存起來到bridge這個成員變數裡面,這樣只要擁有了AdbBackend執行個體的類就能通過bridge來獲得AndroidDebugBridge維護的最新的裝置列表。比如AdbBackend在實現waitForConnection方法時就調用了bridge.getDevices方法來獲得所有串連上來的裝置列表,然後通過裝置序號來找到目標裝置來進行串連
  • AndroidDebugBridge: 這個類有兩個重要功能,其一是啟動ADB伺服器,其二是啟動裝置監控線程DeviceMonitor。第二點我們會在下一小節進行闡述,我們這一小節重點是分析第一點看它是怎麼啟動ADB伺服器的。從中列出來的該類的成員變數和成員方法可以看到它們主要都是跟啟動ADB伺服器相關的,比如DEFAULT_ADB_HOST和DEFAULT_ADB_PORT變數主要是指定ADB伺服器預設需要監聽的地址和連接埠,getAdbLaunchCommand方法是去獲得相應的ADB啟動或者停止命令字串來啟動ADB伺服器(開創新進程”adb start-server”)或停止ADB伺服器(“adb kill-server”)

下面我們通過源碼分析來闡述個中原理。在通過上一節分析的處理好命令列參數之後,monkeyrunner入口main函數的下一步就是去嘗試根據這些參數來調用MonkeyRunnerStarter的建構函式:

178   public static void main(String[] args) {179     MonkeyRunnerOptions options = MonkeyRunnerOptions.processOptions(args);180 181     if (options == null) {182       return;183     }184 185 186     replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE, options.getLogLevel());187 188     MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options);189     int error = runner.run();190 191 192     System.exit(error);193   }194 }
代碼8-3-1 初始化MonkeyRunnerStarter

其中的options參數就是上一節最後根據所有參數建立的MonkeyRunnerOptions對象,裡面儲存了所有的參數。往下我們進入MonkeyRunnerStarter的建構函式:

 55   public MonkeyRunnerStarter(MonkeyRunnerOptions options) 56   { 57     Map<String, String> chimp_options = new TreeMap(); 58     chimp_options.put("backend", options.getBackendName()); 59     this.options = options; 60     this.chimp = ChimpChat.getInstance(chimp_options); 61     MonkeyRunner.setChimpChat(this.chimp); 62   }
代碼8-3-2 MonkeyRunnerStarter建構函式

僅從這個方法的幾行代碼我們可以看到它其實做的事情就是去根據‘backend’來初始化ChimpChat 並把該執行個體儲存到MonkeyRunnerStarter的chimp成員變數中,同時也會調用MonkeyRunner的靜態方法setChimpChat把該ChimpChat對象設定到MonkeyRunner的靜態成員變數裡面,為什麼說它一定是靜態成員變數呢?因為第61行儲存該執行個體調用的是MonkeyRunner這個類的方法,而不是一個執行個體,所以該方法肯定就是靜態,而一個靜態方法裡面的成員函數也必然是靜態。大家跳進去MonkeyRunner這個類就可以看到:

 51   static void setChimpChat(ChimpChat chimp) 52   { 53     chimpchat = chimp; 54   }
代碼8-3-3 MonkeyRunner - setChimpChat

我們返回來繼續看ChimpChat是怎麼啟動的,首先我們MonkeyRunnerStarter建構函式第58行的optionsGetBackendName()是怎麼獲得backend的名字的,從上一節命令列參數分析我們可以知道它預設是用‘adb’的,所以它獲得的就是‘adb’,或者使用者指定的其他backend(其實這種情況不支援,往下繼續分析我們就會清楚了).

取得backend的名字之後就會調用60行的ChimpChat.getInstance來對ChimpChat進行執行個體化:

 46   public static ChimpChat getInstance(Map<String, String> options) 47   { 48     sAdbLocation = (String)options.get("adbLocation"); 49     sNoInitAdb = Boolean.valueOf((String)options.get("noInitAdb")).booleanValue(); 50  51     IChimpBackend backend = createBackendByName((String)options.get("backend")); 52     if (backend == null) { 53       return null; 54     } 55     ChimpChat chimpchat = new ChimpChat(backend); 56     return chimpchat; 57   }

代碼8-3-4 ChimpChat - getInstance

ChimpChat執行個體化所做的事情有兩點,這也就是我們這一小節的重點所在了:

  • 根據backend的名字來建立一個backend,其實就是建立一個AndroidDebugBridge - ADB
  • 調用建構函式把這個backend儲存到ChimChat的成員變數

往下我們繼續看ChimpChat中AndroidDebugBridge這個backend是怎麼建立的,我們進入到51行調用的createBackendByName這個函數:

 75   private static IChimpBackend createBackendByName(String backendName) 76   { 77     if ("adb".equals(backendName)) { 78       return new AdbBackend(sAdbLocation, sNoInitAdb); 79     } 80     return null; 81   }
代碼8-3-5 ChimpChat - createBackendByName

這裡注意第77行,這就是為什麼我之前說backend其實只是支援‘adb’而已,起碼暫時的代碼是這樣子,如果今後google決定支援其他更新的backend,就另當別論了。這還是有可能的,畢竟google留了這個介面。

 56   public AdbBackend(String adbLocation, boolean noInitAdb) 57   { 58     this.initAdb = (!noInitAdb); 59  60  61     if (adbLocation == null) { 62       adbLocation = findAdb(); 63     } 64  65     if (this.initAdb) { 66       AndroidDebugBridge.init(false); 67     } 68  69     this.bridge = AndroidDebugBridge.createBridge(adbLocation, true); 70   }
代碼8-3-6 AdbBackend建構函式

建立AndroidDebugBridge之前我們先要確定我們的adb程式的位置,這就是通過62行來實現的,我們進去findAdb去看下它是怎麼找到我們的sdk中的adb的:

 72   private String findAdb() 73   { 74     String mrParentLocation = System.getProperty("com.android.monkeyrunner.bindir"); 75  76  77  78  79  80     if ((mrParentLocation != null) && (mrParentLocation.length() != 0)) 81     { 82       File platformTools = new File(new File(mrParentLocation).getParent(), "platform-tools"); 83  84       if (platformTools.isDirectory()) { 85         return platformTools.getAbsolutePath() + File.separator + SdkConstants.FN_ADB; 86       } 87  88       return mrParentLocation + File.separator + SdkConstants.FN_ADB; 89     } 90  91     return SdkConstants.FN_ADB; 92   }
代碼8-3-7 AdbBackend - findAdb

首先它通過尋找JVM中的System Property來找到"com.android.monkeyrunner.bindir"這個屬性的值,記得前面小節運行環境初始化的時候在monkeyrunner這個shell指令碼裡面它是怎麼通過java的-D參數把該值儲存到JVM裡面的吧?其實它就是你的檔案系統中儲存sdk的monkeyrunner這個bin(shell)檔案的路徑,在我的機器上是"com.android.monkeyrunner.bindir:/Users/apple/Develop/sdk/tools".

找到這個路徑後通過第82行的代碼再取得它的父目錄,也就是sdk的目錄,再加上‘platform-tools‘這個子目錄,然後再通過85或者88這行加上adb這個名字,這裡的FN_ADB就是adb的名字,在windows下會加上個‘.exe‘變成‘adb.exe‘ ,類linux系統下就只是‘adb’。在本人的機器裡面就是"Users/apple/Develop/sdk/platform-tools/adb"

好,找到了adb所在路經後,AdbBackend的建構函式就會根據這個參數去調用AndroidDebugBridge的createBridge這個靜態方法:

 265   public static AndroidDebugBridge createBridge()      ... 271       try 272       { 273         sThis = new AndroidDebugBridge(); 274         sThis.start(); 275       } catch (InvalidParameterException e) { 276         sThis = null; 277       }      ... 297       return sThis; 298     } 299   }
代碼8-3-8 AndroidDebugBridge - createBridge

第273行AndroidDebugBridge的建構函式做的事情就是執行個體化AndroidDebugBridge,ADB真正啟動起來是調用274行的start()這個成員方法:

 713   boolean start() 714   { 715     if ((this.mAdbOsLocation != null) && (sAdbServerPort != 0) && ((!this.mVersionCheck) || (!startAdb()))) { 716       return false; 717     } 718  719     this.mStarted = true; 720  721  722     this.mDeviceMonitor = new DeviceMonitor(this); 723     this.mDeviceMonitor.start(); 724  725     return true; 726   }
代碼8-3-9 AndroidDebugBridge - start

這裡做了幾個很重要的事情:

  1. 715行startAdb:開啟AndroidDebugBridge
  2. 722-723行: 初始化android裝置監控並啟動DeviceMonitor裝置監控線程。

這一小節我們先看第一個startAdb,看它是如何把AndroidDebugBridge給開啟起來的,第2點我們將會在下一小節描述。

 943   synchronized boolean startAdb() 944   { 945     if (this.mAdbOsLocation == null) { 946       Log.e("adb", "Cannot start adb when AndroidDebugBridge is created without the location of adb."); 947  948       return false; 949     } 950  951     if (sAdbServerPort == 0) { 952       Log.w("adb", "ADB server port for starting AndroidDebugBridge is not set."); 953       return false; 954     } 955  956  957     int status = -1; 958  959     String[] command = getAdbLaunchCommand("start-server"); 960     String commandString = Joiner.on(‘,‘).join(command); 961     try { 962       Log.d("ddms", String.format("Launching ‘%1$s‘ to ensure ADB is running.", new Object[] { commandString })); 963       ProcessBuilder processBuilder = new ProcessBuilder(command); 964       if (DdmPreferences.getUseAdbHost()) { 965         String adbHostValue = DdmPreferences.getAdbHostValue(); 966         if ((adbHostValue != null) && (!adbHostValue.isEmpty())) 967         { 968           Map<String, String> env = processBuilder.environment(); 969           env.put("ADBHOST", adbHostValue); 970         } 971       } 972       Process proc = processBuilder.start(); 973       974       ArrayList<String> errorOutput = new ArrayList(); 975       ArrayList<String> stdOutput = new ArrayList(); 976       status = grabProcessOutput(proc, errorOutput, stdOutput, false); 977     } catch (IOException ioe) { 978       Log.e("ddms", "Unable to run ‘adb‘: " + ioe.getMessage()); 979     } 980     catch (InterruptedException ie) { 981       Log.e("ddms", "Unable to run ‘adb‘: " + ie.getMessage()); 982     } 983     984  985     if (status != 0) { 986       Log.e("ddms", String.format("‘%1$s‘ failed -- run manually if necessary", new Object[] { commandString })); 987       988       return false; 989     } 990     Log.d("ddms", String.format("‘%1$s‘ succeeded", new Object[] { commandString })); 991     return true; 992   }
代碼8-3-10 AndroidDebugBridge - startAdb

這裡所做的事情就是:

  • 準備好啟動db server的command字串
  • 通過ProcessBuilder啟動command字串指定的adb server
  • 錯誤處理

command字串通過959行的getAdbLauncherCommand(‘start-server‘)來實現:

 994   private String[] getAdbLaunchCommand(String option) 995   { 996     List<String> command = new ArrayList(4); 997     command.add(this.mAdbOsLocation); 998     if (sAdbServerPort != 5037) { 999       command.add("-P");1000       command.add(Integer.toString(sAdbServerPort));1001     }1002     command.add(option);1003     return (String[])command.toArray(new String[command.size()]);1004   }
代碼8-3-11  AndroidDebugBridge - getAdbLaunchCommand

整個函數玩的就是字串組合,最後獲得的字串就是”adb -P $port start-server”,也就是開啟adb伺服器的命令列字串了,最終把這個字串打散成字串數組返回。這裡注意port預設值就是ADB伺服器的預設監聽連接埠5037。

startAdb方法獲得命令之後下一步就是直接調用java的ProcessBuilder建構函式來建立一個ADB伺服器處理序了。建立好後就可以通過972行的‘processBuilder.start()‘把這個進程啟動起來。

迄今為止AndroidDebugBridge啟動函數start()所做事情的第一點“1. 啟動AndroidDebugBridge"已經完成了,adb伺服器處理序已經運行起來了。

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


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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.