第14章4節《MonkeyRunner源代碼剖析》 HierarchyViewer實現原理-裝備ViewServer-port轉發
在初始化HierarchyViewer的實例過程中,HierarchyViewer會調用自己的成員方法setupViewServer來把ViewServer裝備好,那麽我們這裏先看下這種方法:
39 private void setupViewServer() { 40 DeviceBridge.setupDeviceForward(mDevice); 41 if (!DeviceBridge.isViewServerRunning(mDevice)) { 42 if (!DeviceBridge.startViewServer(mDevice)) { 43 // TODO: Get rid of this delay. 44 try { 45 Thread.sleep(2000); 46 } catch (InterruptedException e) { 47 } 48 if (!DeviceBridge.startViewServer(mDevice)) { 49 Log.e(TAG, "Unable to debug device " + mDevice); 50 throw new RuntimeException("Could not connect to the view server"); 51 } 52 return; 53 } 54 } 55 DeviceBridge.loadViewServerInfo(mDevice); 56 }
從以上代碼中我們能夠看到該方法去裝備ViewServer主要做的事情有例如以下幾點:
- 40行:設置本地port到目標機器端ViewServer監聽port的port轉發
- 41-54行:確定ViewServer線程是否已經啟動,沒有的話就啟動它。
- 55行:獲取ViewServer的版本號以及其支持的協議版本號
本小節我們先描寫敘述第一點,看HierarchyViewer是怎樣設置本地port到目標機器端ViewServer監聽port的port轉發的。在第13章第2小節我們也手動做過這個事情,當時發送的命令是:
adb forward tcp:4939 tcp:4939
那麽HierarchyViewer是不是也是通過代碼做同樣的事情呢?那麽我們帶著這個疑問來進入深入的代碼分析。我們進入setupDeviceForward這種方法:
110 /** 111 * Sets up a just-connected device to work with the view server. 112 * <p/> 113 * This starts a port forwarding between a local port and a port on the 114 * device. 115 * 116 * @param device 117 */ 118 public static void setupDeviceForward(IDevice device) { 119 synchronized (sDevicePortMap) { 120 if (device.getState() == IDevice.DeviceState.ONLINE) { 121 int localPort = sNextLocalPort++; 122 try { 123 device.createForward(localPort, DEFAULT_SERVER_PORT); 124 sDevicePortMap.put(device, localPort); 125 } catch (TimeoutException e) { 126 Log.e(TAG, "Timeout setting up port forwarding for " + device); 127 } catch (AdbCommandRejectedException e) { 128 Log.e(TAG, String.format("Adb rejected forward command for device %1$s: %2$s", 129 device, e.getMessage())); 130 } catch (IOException e) { 131 Log.e(TAG, String.format("Failed to create forward for device %1$s: %2$s", 132 device, e.getMessage())); 133 } 134 } 135 } 136 }
這個處理port轉發的方法主要分3步走:
- 第1步:獲得本地ViewServer轉發port號
- 第2步:通過Device類發送adb命令創建本地到ViewServerport轉發
- 第3步:把本地port號和相應的設備序列號保存起來以便查找
我們先看第1步,就是121行,這裏要註意”sNextLocalPort”這個變量。事實上它是個靜態變量:
private static int sNextLocalPort = 4939;
代碼14-4-3 DeviceBridge - sNextLocalPort
所以代碼14-4-2中121行所代表的意思是:
- 第一個建立的ViewServerport轉發的本地port是4939
- 下一個建立的ViewServerport轉發的本地port是在4939的基礎自添加1
註意這裏自添加的寫法是”sNextLocalPort ++”。假設反過來寫成”++sNextLocalPort”, 那麽第一個本地port就會變成4940了。這些都是Java的基本的語法了,這裏以防我們做測試的沒有太多編程經驗,所以指出來。
好我們繼續分析第2步port轉發相應代碼, 這種方法傳入的參數就是HierarchyViewer的成員變量mDevice,依據本章第3小節的描寫敘述,這個變量是ddmlib中的Device類的一個實例,所以以上調用”device.createForward”方法實際上調用的就是Device的createForward方法:
644 @Override 645 public void createForward(int localPort, int remotePort) 646 throws TimeoutException, AdbCommandRejectedException, IOException { 647 AdbHelper.createForward(AndroidDebugBridge.getSocketAddress(), this, 648 String.format("tcp:%d", localPort), //$NON-NLS-1$ 649 String.format("tcp:%d", remotePort)); //$NON-NLS-1$ 650 }代碼14-4-3 Device - createForward
像第10章《MonkeyDevice實現原理基礎》所描寫敘述的那樣。Device終於直接調用AdbHelper靜態類的createForward方法來設置port轉發:
549 public static void createForward(InetSocketAddress adbSockAddr, Device device, 550 String localPortSpec, String remotePortSpec) 551 throws TimeoutException, AdbCommandRejectedException, IOException { 552 553 SocketChannel adbChan = null; 554 try { 555 adbChan = SocketChannel.open(adbSockAddr); 556 adbChan.configureBlocking(false); 557 558 byte[] request = formAdbRequest(String.format( 559 "host-serial:%1$s:forward:%2$s;%3$s", //$NON-NLS-1$ 560 device.getSerialNumber(), localPortSpec, remotePortSpec)); 561 562 write(adbChan, request); 563 564 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 565 if (!resp.okay) { 566 Log.w("create-forward", "Error creating forward: " + resp.message); 567 throw new AdbCommandRejectedException(resp.message); 568 } 569 } finally { 570 if (adbChan != null) { 571 adbChan.close(); 572 } 573 } 574 }代碼14-4-4 AdbHelper - createForward
formAdbRequest我們在之前已經分析過。做的事情就是組建好ADB協議的命令以待發送給ADB服務器,在我們558行中終於組建好的ADB協議命令將會例如以下:
“host-serial:xxx:forward:localPortSpec;remotePortSpec”
當中xxx就是代表目標設備的序列號,能夠通過”adb devices -l”獲得:
圖14-4-1獲取設備序列號
所以在終於這個ADB協議命令字串將會變成:
“host-serial:HT21ATD05099:foward:4939;4939”
而參照ADB協議,實際上就相當於ADB命令行client命令的:
“adb -s HT21ATD05099 forward tcp:4939 tcp:4939”
這事實上跟第13章第2小節手動發送ViewServerport轉發命令是一樣的,僅僅是這裏多了個-s參數來指定要轉發的port屬於哪個設備上的ViewServer而已。
到如今為止我們已經完畢了port轉發的第2步了,那麽我們往下看第3步,做的事情就是把代表目標設備的Device實例和本地ViewServer的轉發port做為鍵值對給保存起來到sDevicePortMap這個成員變量裏面:
sDevicePortMap.put(device, Integer.valueOf(localPort)); sDevicePortMap這個成員變量是個HashMap: 55 private static final HashMap<IDevice, Integer> sDevicePortMap = new HashMap();代碼14-4-5 DeviceBridge - sDevicePortmap
註意這個變量是非常重要的,由於HierarchyViewer連接相應的設備的socket就是靠它來提供相應的本地ViewServer轉發port號的。
註:很多其它文章請關註公眾號:techgogogo或個人博客http://techgogogo.com。
當然,也非常歡迎您直接微信(zhubaitian1)勾搭。
本文由天地會珠海分舵原創。轉載請自覺,是否投訴維權看心情。
第14章4節《MonkeyRunner源代碼剖析》 HierarchyViewer實現原理-裝備ViewServer-port轉發