当前位置: 代码迷 >> Android >> 第8章3节《MonkeyRunner源码剖析》MonkeyRunner起动运行过程-启动AndroidDebugBridge
  详细解决方案

第8章3节《MonkeyRunner源码剖析》MonkeyRunner起动运行过程-启动AndroidDebugBridge

热度:86   发布时间:2016-04-24 12:06:37.0
第8章3节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge

用户在命令行运行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)勾搭。本文由天地会珠海分舵原创。转载请自觉,是否投诉维权看心情。