UiAutomator中执行一个UI相关操作,比如click、swipe、drag等,都需要借助两个类,即GestureController、Gestures、PointerGesture。
Gestures翻译成中文就是手势。所谓手势,其实就是指用户手指或触摸笔在触摸屏上连续触碰行为,比如在屏幕上从左至右划出一个动作,就是手势。
PointerGesture就是代表,当操作一个手势时,其包含的一个点的动作。简单点理解就是,手势,比如滑动,它是连续的,可以看成是很多点连续组成的结果。而PointerGesture就代表手势执行过程中的一个点上的动作。即Gestures包含了很多PointerGesture。
一个Gestures的动作包括很多PointerGesture。所以,Gestures提供了构建PointerGesture的工厂方法。
我的理解就是android把UI操作抽象成了手势Gestures。所以Gestures中封装了点击、滑动等动作。
一个手指手势PointerGesture包括多个PointerAction动作,比如一个手指手势包括:点->滑动等。PointerAction就是指的这其中一个动作。这些动作组合起来就变成手指手势PointerGesture了。
包括手势起始点start、手势结束点end和持续时间duaratioin。并且还有一个插值接口函数interpolate()作用是:在持续时间duaration内,在起始点到结束点之间插入多个点Point。
/** A {@link PointerAction} represents part of a {@link PointerGesture}. */
private static abstract class PointerAction {
final Point start;
final Point end;
final long duration;
public PointerAction(Point startPoint, Point endPoint, long time) {
start = startPoint;
end = endPoint;
duration = time;
}
public abstract Point interpolate(float fraction);
}
PointerAction共有两个实现类PointerLinearMoveAction和PointerPauseAction。
1、PointerLinearMoveAction会利用interpolate()来插入start~end区间内多个点Point,来模拟手势的move滑动行为;
/**
* A {@link PointerLinearMotionAction} moves the pointer between two points at a constant
* speed.
*/
private static class PointerLinearMoveAction extends PointerAction {
public PointerLinearMoveAction(Point startPoint, Point endPoint, int speed) {
super(startPoint, endPoint, (long)(1000 * calcDistance(startPoint, endPoint) / speed));
}
@Override
public Point interpolate(float fraction) {
Point ret = new Point(start);
ret.offset((int)(fraction * (end.x - start.x)), (int)(fraction * (end.y - start.y)));
return ret;
}
private static double calcDistance(final Point a, final Point b) {
return Math.sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
}
}
2、PointerPauseAction中的interpolate()插入的点和起始点start一样,从而可以模拟长按行为;
/** A {@link PointerPauseAction} holds the pointer steady for the given amount of time. */
private static class PointerPauseAction extends PointerAction {
public PointerPauseAction(Point startPoint, long time) {
super(startPoint, startPoint, time);
}
@Override
public Point interpolate(float fraction) {
return new Point(start);
}
}
手指手势类PointerGesture类表示的是一个手指在屏幕上做出的手势。 PointerGesture内部维护了一个双向列表,保存了手指运动过程中所有的手指动作PointerAction。
1、PointerGesture对象中使用双端队列Deque
mActions来存储组成手势的所有actions动作; 2、使用long类型的mDelay表示延迟;
3、使用long类型的mDuration表示持续时间
1、构造器
当只是click操作,则使用第一个构造器即可;当手势为滑动等包含多个点操作时,需要使用第二个构造器,mDelay延迟会影响滑动手势的PointerAction的插入点的数量。滑动距离相同的情况下,延迟越大,插值器插入的点越少。
可以看到PointerGesture的构造器其实是创建了一个PointerAction并保存到双向队列中第一个位置。代表手势的起始动作。
class PointerGesture {
// The list of actions that make up this gesture.
private Deque mActions = new ArrayDeque();
private long mDelay;
private long mDuration;
/** Constructs a PointerGesture which touches down at the given start point. */
public PointerGesture(Point startPoint) {
this(startPoint, 0);
}
/**
* Constructs a PointerGesture which touches down at the given start point after a given delay.
* Used in multi-point gestures when the pointers do not all touch down at the same time.
*/
public PointerGesture(Point startPoint, long initialDelay) {
if (initialDelay < 0) {
throw new IllegalArgumentException("initialDelay cannot be negative");
}
mActions.addFirst(new PointerPauseAction(startPoint, 0));
mDelay = initialDelay;
}
}
2、pause() 长按,代表的是持续双向队列中最后一个action一段时间
/** Adds an action which pauses for the specified amount of {@code time} in milliseconds. */
public PointerGesture pause(long time) {
if (time < 0) {
throw new IllegalArgumentException("time cannot be negative");
}
mActions.addLast(new PointerPauseAction(mActions.peekLast().end, time));
mDuration += (mActions.peekLast().duration);
return this;
}
3、move() 移动
/** Adds an action that moves the pointer to {@code dest} at {@code speed} pixels per second. */
public PointerGesture move(Point dest, int speed) {
mActions.addLast(new PointerLinearMoveAction(mActions.peekLast().end, dest, speed));
mDuration += (mActions.peekLast().duration);
return this;
}
4、pointAt()获取手势中某个时间节点的点Point
这里会取出双向队列保存的所有action,然后计算出该action的point。不同的PointAction在这里调用了其插值器函数interpolate()来计算当前时间滑动到什么位置。
/** Returns the pointer location at {@code time} milliseconds into this gesture. */
public Point pointAt(long time) {
if (time < 0) {
throw new IllegalArgumentException("Time cannot be negative");
}
time -= mDelay;
for (PointerAction action : mActions) {
if (time < action.duration) {
return action.interpolate((float)time / action.duration);
}
time -= action.duration;
}
return mActions.peekLast().end;
}
Gestures仅仅是在PointerGesture上层再封装一层。某些手势动作比如click,Gestures仅仅是返回一个PointerGesture, 有些动作比如多个手指同时操作捏合,则Gestures返回该动作的多个PointerGesture。
1、构造器
Gestures的构造器是私有的,无法直接创建。所以Gestures其实是单例。需要借助UiDevice对象来创建Gestures对象。构建Gestures需要ViewConfiguration,这个需要用到context,而context又需要借助UiDeivce。
// Keep a handle to the ViewConfiguration
private ViewConfiguration mViewConfig;
// Private constructor.
private Gestures(ViewConfiguration config) {
mViewConfig = config;
}
/** Returns the {@link Gestures} instance for the given {@link Context}. */
public static Gestures getInstance(UiDevice device) {
if (sInstance == null) {
Context context = device.getInstrumentation().getContext();
sInstance = new Gestures(ViewConfiguration.get(context));
}
return sInstance;
}
2、点击click
返回的是单个PointerGesture,持续时间不知道则为0。
/** Returns a {@link PointerGesture} representing a click at the given {@code point}. */
public PointerGesture click(Point point) {
// A basic click is a touch down and touch up over the same point with no delay.
return click(point, 0);
}
/**
* Returns a {@link PointerGesture} representing a click at the given {@code point} that lasts
* for {@code duration} milliseconds.
*
* @param point The point to click.
* @param duration The duration of the click in milliseconds.
* @return The {@link PointerGesture} representing this click.
*/
public PointerGesture click(Point point, long duration) {
// A click is a touch down and touch up over the same point with an optional delay inbetween
return new PointerGesture(point).pause(duration);
}
3、滑动
/**
* Returns a {@link PointerGesture} representing a swipe.
*
* @param start The touch down point for the swipe.
* @param end The touch up point for the swipe.
* @param speed The speed at which to move in pixels per second.
* @return The {@link PointerGesture} representing this swipe.
*/
public PointerGesture swipe(Point start, Point end, int speed) {
// A swipe is a click that moves before releasing the pointer.
return click(start).move(end, speed);
}
执行给定的PointerGesture中双端队列Deque
public void performGesture(PointerGesture ... gestures){}
如果是单指操作,则gestures就是有当前手指的手势对象;
如果是多指操作,则gestures是多个手指的手势对象
函数内部逻辑大致为
1、使用一个map来存储每个手指的手势和手势的起始点Pointer对象;
2、初始化一个优先队列active,用来在循环中存储所有手指的PointerGesture,其排序按照谁先开始谁放前面的规则;
3、使用一个优先队列pending来存储存储所有手指的PointerGesture,其排序按照哪个手指的PointerGesture先结束谁放前面的规则;
4、使用elapsedTime来统计手势动作已经执行多次时间;
5、用一个while循环依次取出pending中所有的PointerGesture,封装成MotionEvent.ACTION_DOWN时间并插入到系统中,从而触发了每个手指的down点击事件;
6、再用一个while循环遍历优先队列active中每个PointerGesture,判断当前时间是否已经超过了PointerGesture的手势持续时间,如果超过了则封装一个MotionEvent.ACTION_UP事件,并插入到系统中,代表某个手指离开屏幕;
7、当某个手势还没有结束,则计算出滑动需要经过的所有点,封装出MotionEvent.ACTION_MOVE事件,开始滑动。
public void performGesture(PointerGesture ... gestures) {
// Initialize pointers
int count = 0;
Map pointers = new HashMap();
for (PointerGesture g : gestures) {
pointers.put(g, new Pointer(count++, g.start()));
}
// Initialize MotionEvent arrays
List properties = new ArrayList();
List coordinates = new ArrayList();
// Track active and pending gestures
PriorityQueue active = new PriorityQueue(gestures.length,
END_TIME_COMPARATOR);
PriorityQueue pending = new PriorityQueue(gestures.length,
START_TIME_COMPARATOR);
pending.addAll(Arrays.asList(gestures));
// Record the start time
long startTime = SystemClock.uptimeMillis();
// Loop
MotionEvent event;
for (long elapsedTime = 0; !pending.isEmpty() || !active.isEmpty();
elapsedTime = SystemClock.uptimeMillis() - startTime) {
// Touchdown any new pointers
while (!pending.isEmpty() && elapsedTime > pending.peek().delay()) {
PointerGesture gesture = pending.remove();
Pointer pointer = pointers.get(gesture);
// Add the pointer to the MotionEvent arrays
properties.add(pointer.prop);
coordinates.add(pointer.coords);
// Touch down
int action = MotionEvent.ACTION_DOWN;
if (!active.isEmpty()) {
// Use ACTION_POINTER_DOWN for secondary pointers. The index is stored at
// ACTION_POINTER_INDEX_SHIFT.
action = MotionEvent.ACTION_POINTER_DOWN
+ ((properties.size() - 1) << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
event = getMotionEvent(startTime, startTime + elapsedTime, action, properties,
coordinates);
getDevice().getUiAutomation().injectInputEvent(event, true);
// Move the PointerGesture to the active list
active.add(gesture);
}
// Touch up any completed pointers
while (!active.isEmpty()
&& elapsedTime > active.peek().delay() + active.peek().duration()) {
PointerGesture gesture = active.remove();
Pointer pointer = pointers.get(gesture);
// Update pointer positions
pointer.updatePosition(gesture.end());
for (PointerGesture current : active) {
pointers.get(current).updatePosition(current.pointAt(elapsedTime));
}
int action = MotionEvent.ACTION_UP;
int index = properties.indexOf(pointer.prop);
if (!active.isEmpty()) {
action = MotionEvent.ACTION_POINTER_UP
+ (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
}
event = getMotionEvent(startTime, startTime + elapsedTime, action, properties,
coordinates);
getDevice().getUiAutomation().injectInputEvent(event, true);
properties.remove(index);
coordinates.remove(index);
}
// Move any active pointers
for (PointerGesture gesture : active) {
Pointer pointer = pointers.get(gesture);
pointer.updatePosition(gesture.pointAt(elapsedTime - gesture.delay()));
}
if (!active.isEmpty()) {
event = getMotionEvent(startTime, startTime + elapsedTime, MotionEvent.ACTION_MOVE,
properties, coordinates);
getDevice().getUiAutomation().injectInputEvent(event, true);
}
}
}
PointerAction:代表 手势中的某个动作;
PointerGesture:代表 一个手指在屏幕上操作的完整手势;
Gestures: 在PointerGesture基础上封装了一层,可以表示多个手指的PointerGesture;
GestureController:根据Gestures真正执行动作的类
网上有篇文章写的也不错,值得参考下:
Android之UiAutomator测试框架源码分析(第29篇:UiObject2中的点击控件功能深度分析)_叫我王员外就行的博客-CSDN博客_uiautomator2 源码分析前言UI自动化的三个重要组成部分为查找控件、操作控件、预期结果,UiObject2是如何做到操作控件的?通过本篇源码分析,我们将知道插装测试的点击原理https://blog.csdn.net/cadi2011/article/details/106739947