在测试中,可以对应设备(UiDevice)进行很多操作, 可以检测设备的各种属性,例如当前的屏幕方向以及屏幕尺寸;
同时还可以通过UiDevice实例来执行设备级别的操作,例如 把设备设置为横屏或者竖屏、按下Home按键等
这些操作的具体流程以及逻辑大同小异,以pressHome方法为例来论述其原理。
UiDevice 的pressHome方法如下,
public boolean pressHome() {
Tracer.trace();
waitForIdle();
return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
KEY_PRESS_EVENT_TIMEOUT);
}
getAutomatorBridge方法返回UiAutomatorBridge对象,如下,
UiAutomatorBridge getAutomatorBridge() {
if (mUiAutomationBridge == null) {
throw new RuntimeException("UiDevice not initialized");
}
return mUiAutomationBridge;
}
ShellUiAutomatorBridge对象在UiAutomatorTestRunner的start方法中创建并赋值给UiDevice的变量mUiAutomationBridge
mUiDevice = UiDevice.getInstance();
mUiDevice.initialize(new ShellUiAutomatorBridge(automationWrapper.getUiAutomation()));
很明显, UiDevice是一个单例,其initialize方法如下,
public void initialize(UiAutomatorBridge uiAutomatorBridge) {
mUiAutomationBridge = uiAutomatorBridge;
}
ShellUiAutomatorBridge继承于UiAutomatorBridge。UiAutomatorBridge中的getInteractionController方法如下,
InteractionController getInteractionController() {
return mInteractionController;
}
获取的是InteractionController对象,在UiAutomatorBridge的构造方法中实例化,
UiAutomatorBridge(UiAutomation uiAutomation) {
mUiAutomation = uiAutomation;
mInteractionController = new InteractionController(this);
mQueryController = new QueryController(this);
}
InteractionController的sendKeyAndWaitForEventG方法如下,
public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
final int eventType, long timeout) {
Runnable command = new Runnable() {
@Override
public void run() {
final long eventTime = SystemClock.uptimeMillis();
KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
if (injectEventSync(downEvent)) {
KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
injectEventSync(upEvent);
}
}
};
return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
!= null;
}
先抛开其他方法的调用,肯定会回调run方法,然后首先构造一个KeyEvent down事件,然后输入到系统;成功后,构造一个up KeyEvent事件,输入到系统中。
所以分为2个步骤,看看是如何回调的。
1, runAndWaitForEvents
2, injectEventSync
runAndWaitForEvents方法如下,
private AccessibilityEvent runAndWaitForEvents(Runnable command,
AccessibilityEventFilter filter, long timeout) {
try {
return mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(command, filter,
timeout);
} catch (TimeoutException e) {
Log.w(LOG_TAG, "runAndwaitForEvent timedout waiting for events");
return null;
} catch (Exception e) {
Log.e(LOG_TAG, "exception from executeCommandAndWaitForAccessibilityEvent", e);
return null;
}
}
UiAutomatorBridge 的executeCommandAndWaitForAccessibilityEvent的方法如下,
public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
return mUiAutomation.executeAndWaitForEvent(command,
filter, timeoutMillis);
}
mUiAutomation对象是在UiAutomatorTestRunner的start方法中通过调用UiAutomationShellWrapper的connect而创建的。
executeAndWaitForEvent方法如下,
public AccessibilityEvent executeAndWaitForEvent(Runnable command,
AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
// Acquire the lock and prepare for receiving events.
synchronized (mLock) {
throwIfNotConnectedLocked();
mEventQueue.clear();
// Prepare to wait for an event.
mWaitingForEventDelivery = true;
}
// Note: We have to release the lock since calling out with this lock held
// can bite. We will correctly filter out events from other interactions,
// so starting to collect events before running the action is just fine.
// We will ignore events from previous interactions.
final long executionStartTimeMillis = SystemClock.uptimeMillis();
// Execute the command *without* the lock being held.
command.run();
// Acquire the lock and wait for the event.
synchronized (mLock) {
try {
// Wait for the event.
final long startTimeMillis = SystemClock.uptimeMillis();
while (true) {
// Drain the event queue
while (!mEventQueue.isEmpty()) {
AccessibilityEvent event = mEventQueue.remove(0);
// Ignore events from previous interactions.
if (event.getEventTime() < executionStartTimeMillis) {
continue;
}
if (filter.accept(event)) {
return event;
}
event.recycle();
}
// Check if timed out and if not wait.
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
throw new TimeoutException("Expected event not received within: "
+ timeoutMillis + " ms.");
}
try {
mLock.wait(remainingTimeMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
} finally {
mWaitingForEventDelivery = false;
mEventQueue.clear();
mLock.notifyAll();
}
}
}
在该方法实现回调,并且做一些其它的处理。
InteractionController的injectEventSync方法如下,
private boolean injectEventSync(InputEvent event) {
return mUiAutomatorBridge.injectInputEvent(event, true);
}
UiAutomatorBridge的injectInputEvent方法如下,
public boolean injectInputEvent(InputEvent event, boolean sync) {
return mUiAutomation.injectInputEvent(event, sync);
}
UiAutomation的injectInputEvent方法如下,
public boolean injectInputEvent(InputEvent event, boolean sync) {
synchronized (mLock) {
throwIfNotConnectedLocked();
}
try {
if (DEBUG) {
Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
}
// Calling out without a lock held.
return mUiAutomationConnection.injectInputEvent(event, sync);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while injecting input event!", re);
}
return false;
}
UiAutomationConnection的injectInputEvent方法如下,
@Override
public boolean injectInputEvent(InputEvent event, boolean sync) {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
throwIfNotConnectedLocked();
}
final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
: InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
final long identity = Binder.clearCallingIdentity();
try {
return InputManager.getInstance().injectInputEvent(event, mode);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
最后通过InputManager来输入模拟按键事件。
UiAutomationShellWrapper的connect方法如下,
public void connect() {
if (mHandlerThread.isAlive()) {
throw new IllegalStateException("Already connected!");
}
mHandlerThread.start();
mUiAutomation = new UiAutomation(mHandlerThread.getLooper(),
new UiAutomationConnection());
mUiAutomation.connect();
}
UiAutomationConnection也是在该方法中创建的,然后利用该对象创建UiAutomation对象。
所以实际上,调用的流程图如下,
UiDevice 》UiAutomatorBridge 》InteractionController 》UiAutomatorBridge 》UiAutomation
最后在UiAutomationConnection中完成实际的操作。
UiAutomationConnection是测试和系统之间的桥梁。