UiAutomator已经现有的的 API 没有提高多点触控的方法,但是我们可以通过注入多点事件的方式来实现。

Android TouchEvent Flow

一个基本的单点事件流

1
2
3
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_UP

如果加上多点事件,基本的事件流是这样的:

1
2
3
4
5
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_POINTER_DOWN
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_POINTER_UP
MotionEvent.ACTION_UP

需要明白的点:

无论多点还是单点,正常的事件流是以ACTION_DOWN开始 ACTION_UP结束。

对于多点事件来说,当你第一个手指点击屏幕触发ACTION_DOWN,后面的手指按压触发ACTION_POINTER_DOWN,然后进过ACTION_MOVEACTION_POINTER_UP,最终到达ACTION_UP

事件注入有个原则是: 1.ACTION_POINTER_UPACTION_UP的点是ACTION_MOVE后的最后那个点; 2.ACTION_POINTER_UPACTION_UP的点和ACTION_POINTER_DOWNACTION_DOWN的点应该一一对应的; 3.先DOWN的点应该后UP

代码实现

UiAutomator:事件注入入口

UiAutomator:测试框架已经为我们准备好了相关的方法。

1
2
3
4
5
Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
UiAutomation mUiAutomation = mInstrumentation.getUiAutomation();

// 这就是注入事件的方法
mUiAutomation.injectInputEvent(InputEvent event, boolean sync)

MotionEvent:相关类和方法

MotionEvent.PointerCoords:Point 坐标传输对象,有点像的DTO意思,里面包含了位置,大小,压力等属性,
MotionEvent.PointerProperties:Point 属性传输对象,有点像的DTO意思,里面包含了 ID,TYPE:区分是鼠标,手指等等不同设备的 Pointer

1
2
3
4
5
MotionEvent.obtain(long downTime, long eventTime,
int action, int pointerCount, PointerProperties[] pointerProperties,
PointerCoords[] pointerCoords, int metaState, int buttonState,
float xPrecision, float yPrecision, int deviceId,
int edgeFlags, int source, int flags)

我们使用这个方法来创建一个 MotionEvent,用于事件的注入。

详细代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public void test() {
// 伪代码
// 创建一个二维数组
// [[PointerCoords,...], [PointerCoords, ..], [PointerCoords, ..], ...]
// 二维数组的第一个点,为DOWN点,后面的为MOVE点,最后一个就是UP点。

injectMultiTouch([[PointerCoords,...],[PointerCoords,...]]);
}

private PointerCoords createPoint(int x, int y) {
PointerCoords pointerCoords = new PointerCoords();
pointerCoords.x = x;
pointerCoords.y = y;
pointerCoords.pressure = 1;
pointerCoords.size = 1;
return pointerCoords;
}

// 这里的参数是个二维数组,一维代表点的个度,二维代表点的移动位置
private boolean injectMultiTouch(PointerCoords[]... touches) {
boolean ret = true;
if (touches.length < 2) {
throw new IllegalArgumentException("Must provide coordinates for at least 2 pointers");
}

int maxSteps = 0;
for (int x = 0; x < touches.length; x++)
maxSteps = (maxSteps < touches[x].length) ? touches[x].length : maxSteps;

PointerProperties[] properties = new PointerProperties[touches.length];
PointerCoords[] pointerCoords = new PointerCoords[touches.length];
for (int x = 0; x < touches.length; x++) {
MotionEvent.PointerProperties prop = new MotionEvent.PointerProperties();
prop.id = x;
prop.toolType = MotionEvent.TOOL_TYPE_FINGER;
properties[x] = prop;

pointerCoords[x] = touches[x][0];
}

long downTime = SystemClock.uptimeMillis();
MotionEvent event;
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 1,
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= mUiAutomation.injectInputEvent(event, true);

for (int x = 1; x < touches.length; x++) {
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
getPointerAction(MotionEvent.ACTION_POINTER_DOWN, x), x + 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= mUiAutomation.injectInputEvent(event, true);
}

for (int i = 1; i < maxSteps - 1; i++) {
for (int x = 0; x < touches.length; x++) {
if (touches[x].length > i)
pointerCoords[x] = touches[x][i];
else
pointerCoords[x] = touches[x][touches[x].length - 1];
}

event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE, touches.length, properties, pointerCoords, 0, 0, 1, 1,
0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);

ret &= mUiAutomation.injectInputEvent(event, true);
SystemClock.sleep(5);
}

for (int x = 0; x < touches.length; x++)
pointerCoords[x] = touches[x][touches[x].length - 1];

for (int x = 1; x < touches.length; x++) {
event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
getPointerAction(MotionEvent.ACTION_POINTER_UP, x), x + 1, properties,
pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= mUiAutomation.injectInputEvent(event, true);
}

event = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 1,
properties, pointerCoords, 0, 0, 1, 1, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
ret &= mUiAutomation.injectInputEvent(event, true);

return ret;
}

相关项目

可参考OpenGpad-Android:基于测试框架做的使用 PC 键盘玩儿手机游戏的软件。