android自动化测试Monkeyrunner源码分析之二

时间:2023-01-19 05:39:11

2. ChimpChat

ChimpChat的getInstance方法如下,

public static ChimpChat getInstance(Map<String, String> options)
{
sAdbLocation = (String)options.get("adbLocation");
sNoInitAdb = Boolean.valueOf((String)options.get("noInitAdb")).booleanValue();

IChimpBackend backend = createBackendByName((String)options.get("backend"));
if (backend == null) {
return null;
}
ChimpChat chimpchat = new ChimpChat(backend);
return chimpchat;
}

1, 根据backend的名字来创建一个backend,其实就是创建一个AndroidDebugBridge

2,构造ChimpChat对象。

createBackendByName方法如下,

  private static IChimpBackend createBackendByName(String backendName)
{
if ("adb".equals(backendName)) {
return new AdbBackend(sAdbLocation, sNoInitAdb);
}
return null;
}

AdbBackend的构造方法如下,

 public AdbBackend(String adbLocation, boolean noInitAdb)
{
this.initAdb = (!noInitAdb);
if (adbLocation == null) {
adbLocation = findAdb();
}
if (this.initAdb) {
AndroidDebugBridge.init(false);
}
this.bridge = AndroidDebugBridge.createBridge(adbLocation, true);
}

创建AndroidDebugBridge之前先要确定adb程序的位置,这就是通过findAdb方法来实现的。

然后调用createBridge方法创建AndroidDebugBridge对象,

public static AndroidDebugBridge createBridge(String osLocation, boolean forceNewBridge)
{
synchronized (sLock)
{
if (sThis != null)
{
if ((sThis.mAdbOsLocation != null) && (sThis.mAdbOsLocation.equals(osLocation)) && (!forceNewBridge)) {
return sThis;
}
sThis.stop();
}
try
{
sThis = new AndroidDebugBridge(osLocation);
sThis.start();
}
•••

实例化AndroidDebugBridge对象后,然后调用其start方法,启动adb

boolean start()
{
if ((this.mAdbOsLocation != null) && (sAdbServerPort != 0) && ((!this.mVersionCheck) || (!startAdb()))) {
return false;
}
this.mStarted = true;


this.mDeviceMonitor = new DeviceMonitor(this);
this.mDeviceMonitor.start();

return true;
}

1,startAdb:开启AndroidDebugBridge

2,NewDeviceMonitor并传入已经开启的adb:初始化android设备监控

3,DeviceMonitor.start:启动DeviceMonitor设备监控线程。

2.1 startAdb

startAdb的方法如下,

synchronized boolean startAdb()
{
if (this.mAdbOsLocation == null)
{
Log.e("adb", "Cannot start adb when AndroidDebugBridge is created without the location of adb.");

return false;
}
if (sAdbServerPort == 0)
{
Log.w("adb", "ADB server port for starting AndroidDebugBridge is not set.");
return false;
}
int status = -1;

String[] command = getAdbLaunchCommand("start-server");
String commandString = Joiner.on(',').join(command);
try
{
Log.d("ddms", String.format("Launching '%1$s' to ensure ADB is running.", new Object[] { commandString }));
ProcessBuilder processBuilder = new ProcessBuilder(command);
if (DdmPreferences.getUseAdbHost())
{
String adbHostValue = DdmPreferences.getAdbHostValue();
if ((adbHostValue != null) && (!adbHostValue.isEmpty()))
{
Map<String, String> env = processBuilder.environment();
env.put("ADBHOST", adbHostValue);
}
}
Process proc = processBuilder.start();

ArrayList<String> errorOutput = new ArrayList();
ArrayList<String> stdOutput = new ArrayList();
status = grabProcessOutput(proc, errorOutput, stdOutput, false);
}
catch (IOException ioe)
{
Log.e("ddms", "Unable to run 'adb': " + ioe.getMessage());
}
catch (InterruptedException ie)
{
Log.e("ddms", "Unable to run 'adb': " + ie.getMessage());
}
if (status != 0)
{
Log.e("ddms", String.format("'%1$s' failed -- run manually if necessary", new Object[] { commandString }));

return false;
}
Log.d("ddms", String.format("'%1$s' succeeded", new Object[] { commandString }));
return true;
}

1,准备好启动db server的command字串

2,通过ProcessBuilder启动command字串指定的adb server

adb服务器进程已经运行起来了。AndroidDebugBridge启动起来后,下一步就是把这个adb实例传到DeviceMonitor来去监测所有连接到adb服务器也就是pc主机端的android设备的状态。

2.2启动DeviceMonitor设备监控线程

流程图如下,

android自动化测试Monkeyrunner源码分析之二

DeviceMonitor的构造方法如下,

DeviceMonitor(AndroidDebugBridge server)
{
this.mServer = server;

this.mDebuggerPorts.add(Integer.valueOf(DdmPreferences.getDebugPortBase()));
}

Start方法如下,

void start()
{
new Thread("Device List Monitor")
{
public void run()
{
DeviceMonitor.this.deviceMonitorLoop();
}
}.start();
}

另开一个线程无限循环来检测设备的状态。

private void deviceMonitorLoop()
{
do
{
try
{
if (this.mMainAdbConnection == null)
{
Log.d("DeviceMonitor", "Opening adb connection");
this.mMainAdbConnection = openAdbConnection();
if (this.mMainAdbConnection == null)
{
this.mConnectionAttempt += 1;
Log.e("DeviceMonitor", "Connection attempts: " + this.mConnectionAttempt);
if (this.mConnectionAttempt > 10) {
if (!this.mServer.startAdb())
{
this.mRestartAttemptCount += 1;
Log.e("DeviceMonitor", "adb restart attempts: " + this.mRestartAttemptCount);
}
else
{
Log.i("DeviceMonitor", "adb restarted");
this.mRestartAttemptCount = 0;
}
}
waitABit();
}
else
{
Log.d("DeviceMonitor", "Connected to adb for device monitoring");
this.mConnectionAttempt = 0;
}
}
if ((this.mMainAdbConnection != null) && (!this.mMonitoring)) {
this.mMonitoring = sendDeviceListMonitoringRequest();
}
if (this.mMonitoring)
{
int length = readLength(this.mMainAdbConnection, this.mLengthBuffer);
if (length >= 0)
{
processIncomingDeviceData(length);


this.mInitialDeviceListDone = true;
}
}
}
catch (AsynchronousCloseException ace) {}catch (TimeoutException ioe)
{
handleExpectionInMonitorLoop(ioe);
}
catch (IOException ioe)
{
handleExpectionInMonitorLoop(ioe);
}
} while (!this.mQuit);
}

一旦发现设备有变动,该循环会立刻调用processIncomingDeviceData这个方法来更新设备信息。

private void processIncomingDeviceData(int length)
throws IOException
{
ArrayList<Device> list = new ArrayList();
if (length > 0)
{
byte[] buffer = new byte[length];
String result = read(this.mMainAdbConnection, buffer);

String[] devices = result.split("\n");
for (String d : devices)
{
String[] param = d.split("\t");
if (param.length == 2)
{
Device device = new Device(this, param[0], IDevice.DeviceState.getState(param[1]));



list.add(device);
}
}
}
updateDevices(list);
}

该方法首先会取得所有的device列表(类似"adb devices -l"命令获得所有device列表),

然后调用updateDevices这个方法来对所有设备信息进行一次更新:

•••
for (Device d : devicesToQuery) {
queryNewDeviceForInfo(d);
}
•••

调用queryNewDeviceForInfo这个方法去更新每个设备所有的porperty信息

private void queryNewDeviceForMountingPoint(final Device device, final String name)
throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException
{
device.executeShellCommand("echo $" + name, new MultiLineReceiver()
{
public boolean isCancelled()
{
return false;
}

public void processNewLines(String[] lines)
{
for (String line : lines) {
if (!line.isEmpty()) {
device.setMountingPoint(name, line);
}
}
}
});
}

该方法调用了一个ddmlib库的device类里面的executeShellCommand方法来执行‘getprop'这个命令。

到目前位置我们达到的目的是知道了getSystemProperty这个MonkeyDevice的api最终确实是通过发送'adb shell getporp‘命令来获得设备属性的。

AdbHelper的executeRemoteCommand方法如下,

static void executeRemoteCommand(InetSocketAddress adbSockAddr, AdbService adbService, String command, IDevice device, IShellOutputReceiver rcvr, long maxTimeToOutputResponse, TimeUnit maxTimeUnits, @Nullable InputStream is)
throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, IOException
{
long maxTimeToOutputMs = 0L;
if (maxTimeToOutputResponse > 0L)
{
if (maxTimeUnits == null) {
throw new NullPointerException("Time unit must not be null for non-zero max.");
}
maxTimeToOutputMs = maxTimeUnits.toMillis(maxTimeToOutputResponse);
}
Log.v("ddms", "execute: running " + command);

SocketChannel adbChan = null;
try
{
adbChan = SocketChannel.open(adbSockAddr);
adbChan.configureBlocking(false);
setDevice(adbChan, device);

byte[] request = formAdbRequest(adbService.name().toLowerCase() + ":" + command);
write(adbChan, request);

AdbResponse resp = readAdbResponse(adbChan, false);
if (!resp.okay)
{
Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message);
throw new AdbCommandRejectedException(resp.message);
}
byte[] data = new byte[16384];
if (is != null)
{
int read;
while ((read = is.read(data)) != -1)
{
ByteBuffer buf = ByteBuffer.wrap(data, 0, read);
int written = 0;
while (buf.hasRemaining()) {
written += adbChan.write(buf);
}
if (written != read)
{
Log.e("ddms", "ADB write inconsistency, wrote " + written + "expected " + read);

throw new AdbCommandRejectedException("write failed");
}
}
}
ByteBuffer buf = ByteBuffer.wrap(data);
buf.clear();
long timeToResponseCount = 0L;
for (;;)
{
if ((rcvr != null) && (rcvr.isCancelled()))
{
Log.v("ddms", "execute: cancelled");
break;
}
int count = adbChan.read(buf);
if (count < 0)
{
rcvr.flush();
Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: " + count);

break;
}
if (count == 0)
{
try
{
int wait = 25;
timeToResponseCount += wait;
if ((maxTimeToOutputMs > 0L) && (timeToResponseCount > maxTimeToOutputMs)) {
throw new ShellCommandUnresponsiveException();
}
Thread.sleep(wait);
}
catch (InterruptedException ie) {}
}
else
{
timeToResponseCount = 0L;
if (rcvr != null) {
rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
}
buf.rewind();
}
}
}
finally
{
if (adbChan != null) {
adbChan.close();
}
Log.v("ddms", "execute: returning");
}
}

方法中先创建一个面向adb服务器的socket通道,然后通过发送adb协议请求的'shell:'命令获得一个adb shell

然后再把相应的adb shell命令发送到该socket。从这里可以看到,“发送adb shell命令“其实是基于”发送adb协议请求“的,

因为在发送命令之前需要先通过组织基于adb协议的请求”shell:“来获得adb shell.

 

MonkeyRunner启动步骤如下,

1,实例化MonkeyRunnerStarter时会去实例化ChimpChat这个类

2,实例化ChimpChat这个类的时候会去创建AndroidDebugBridge对象启动一个adb进程来进行与adb服务器以及目标设备的adb守护进程通讯

3,.实例化ChimpChat时还会在上面创建的adb对象的基础上创建DeviceMonitor对象并

启动一个线程来监控和维护连接到主机pc的android设备信息,因为监控设备时需要通过adb来实现的

4,最后在以上都准备好后就会尝试启动jython编译器的console或者直接调用jython编译器去解析执行脚本。