標籤:
使用者在命令列運行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
這裡做了幾個很重要的事情:
- 715行startAdb:開啟AndroidDebugBridge
- 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