android自动化测试Uiautomator API分析之一

在测试中,可以对应设备(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

1.1 runAndWaitForEvents

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();
            }
        }
    }

在该方法实现回调,并且做一些其它的处理。

1.2, injectEventSync

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是测试和系统之间的桥梁。

你可能感兴趣的:(---【自动化测试】)