主题介绍

本文主要对 UiAutomator 2.0 的 Click 事件调用过程进行分析,根据对过程的分析实现对坐标、音量及 Power 键的长按操作。

前言

来到社区挺长时间了,一直默默潜水学习,在日常用 UiAutomator 2.0 写脚本的过程中,偶然发现了界面除 swipe 之外的长按方法,及物理按键的长按方法,和大家分享交流一下。
本文只针对使用 UI 2.0 的童鞋,使用其他封装框架的童鞋可以略过了.....

UI 2.0 中 Click 事件的分析

以 UiDevice 类提供的 click 方法作为切入点,首先查看 UiDevice.java 中 click 方法的定义,如下:

public boolean click(int x, int y) {
    Tracer.trace(x, y);
    if (x >= getDisplayWidth() || y >= getDisplayHeight()) {
        return (false);
    }
    return getAutomatorBridge().getInteractionController().clickNoSync(x, y);
}

可以看到首先判断 x,y 值是否在当前可显示界面坐标范围内,然后调用 getAutomatorBridge() 方法,此方法如下:

InstrumentationUiAutomatorBridge getAutomatorBridge() {
    if (mUiAutomationBridge == null) {
        throw new RuntimeException("UiDevice not initialized");
    }
    return mUiAutomationBridge;
}

从上边可以看到这个方法返回的是一个 InstrumentationUiAutomatorBridge 的对象,而返回的值是 UiDevice 类定义的一个
属性,接着找到 mUiAutomationBridge 的赋值语句,如下:

private UiDevice(Instrumentation instrumentation) {
    mInstrumentation = instrumentation;
    UiAutomation uiAutomation = instrumentation.getUiAutomation();
    mUiAutomationBridge = new InstrumentationUiAutomatorBridge(instrumentation.getContext(), uiAutomation);

    // Enable multi-window support for API level 21 and up
    if (UiDevice.API_LEVEL_ACTUAL >= Build.VERSION_CODES.LOLLIPOP) {
        // Subscribe to window information
        AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
        uiAutomation.setServiceInfo(info);
    }
}

如上所示,mUiAutomationBridge 在 UiDevice 的构造器中赋值,传入两个 Context,UiAutomation 的参数。
回到 click 方法定义的地方,可以看到是通过调用 getAutomatorBridge().getInteractionController().clickNoSync(x, y) 进行点击的,
等价于 mUiAutomationBridge.getInteractionController().clickNoSync(x, y),接着我们就可以查看 clickNoSync 的实现,该方法位于
InteractionController 类中,该类未 UI2 隐藏类,不能再 case 中直接创建实例,方法实现如下:

public boolean clickNoSync(int x, int y) {
    Log.d(LOG_TAG, "clickNoSync (" + x + ", " + y + ")");

    if (touchDown(x, y)) {
        SystemClock.sleep(REGULAR_CLICK_LENGTH);
        if (touchUp(x, y))
            return true;
    }
    return false;
}

由上可见调用了 touchDown 及 touchUp 方法,接着查看这两个方法实现,如下:

private boolean touchDown(int x, int y) {
    if (DEBUG) {
        Log.d(LOG_TAG, "touchDown (" + x + ", " + y + ")");
    }
    mDownTime = SystemClock.uptimeMillis();
    MotionEvent event = getMotionEvent(mDownTime, mDownTime, MotionEvent.ACTION_DOWN, x, y);
    return injectEventSync(event);
}

private boolean touchUp(int x, int y) {
    if (DEBUG) {
        Log.d(LOG_TAG, "touchUp (" + x + ", " + y + ")");
    }
    final long eventTime = SystemClock.uptimeMillis();
    MotionEvent event = getMotionEvent(mDownTime, eventTime, MotionEvent.ACTION_UP, x, y);
    mDownTime = 0;
    return injectEventSync(event);
}

以上两个方法都调用了 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;
}

查看到这,想继续查看,但是 mUiAutomationConnection 这个类是不能直接看到的,不过已经足够了。
总结一下以上过程就是:

UiDevice->UiAutomaotrBridge->InteractionController->UiAutomatorBridge->UiAutomation

OK,整个流程梳理清楚。

当前 UiAutomator 2.0 长按方法

针对界面元素 (控件及坐标)

UI 2.0 中 UiObject2 对象提供了 click(long duration) 的 API,但在实际应用中,并没有效果,所以当前主要使用 swipe 方法替代,例如:
有一个名为 mWeather 的控件,要实现对此控件的长按,就可以用如下方式:

mWeather.swipe(Direction.LEFT, 0.01f, 1)

详细参数解释可以查看 Google 提供的官方 API 说明

针对物理按键的长按 (音量、Power 键)

目前 UI 2.0 官方提供的 API 尚未发现提供此功能,Appium 中利用反射实现了相关的功能,其他测试框架不了解。

UI 2.0 长按方法的实现

由以上分析可知,Click 这个操作由两个 Down 和 Up 两个 Touch 事件完成,最终由 UiAutomation 完成,要实现长按,只需要在 Down 和 Up 事件的注入
时间间隔加长就可以了。

怎么实现呢?

在用 Ui 2.0 来写 case 时,我们一般都会注册一个 Instrumentation 实例,如下:
instrumentation = InstrumentationRegistry.getInstrumentation();

从上边 UiDevice 的构造方法可以看到,通过 instrumentation,就可以创建 UiAutomation 实例,我们可以通过如下方式创建
UiAutomation 实例:
UiAutomation uiAutomation = instrumentation.getUiAutomation();

然后通过此 UiAutomation 直接注入事件,当前就只剩一个问题,上面注入的都是 MotionEvent 对象,怎么获取 MotionEvent 对象呢,
可以直接将 InterAction.java 中的 getMotionEvent 方法改个名字,来使用。

这样我们就可以就可以实例化一个 Down 事件,实例化一个 Up 事件,在两次事件的注入操作中间加一个 SystemClock.sleep(time) 的操作,
来实现长按。

原理就是这样了,长按 Power 键同理,具体代码我就不贴了,都是很简单的,大家有兴趣的可以自己试一下,在我的测试中,Power 键可以长按关机,
但是音量键只是显示进度条,并不改变音量大小,有知道原因的大神也可以回复解惑一下,第一次发帖,谢谢所有耐心看完的童鞋们。


↙↙↙阅读原文可查看相关链接,并与作者交流