在这篇记录中我将完成solo类中其他未记录的方法的学习笔记,主要是Click,和Type/Enter,那么我最先开始的是Click这个类型的方法,其实这个操作的原理并不难理解,主要是拿到控件,再通过控件上的坐标点发送点击事件,下面是代码
public void clickOnText(String text) {
clicker.clickOnText(text, false, 1, true, 0);
}
调用Clicker类中的 clickOnText()
public void clickOnText(String regex, boolean longClick, int match, boolean scroll, int time) {
TextView textToClick = waiter.waitForText(regex, match, Timeout.getSmallTimeout(), scroll, true, false);
if (textToClick != null) {
clickOnScreen(textToClick, longClick, time);
}
else {
if(match > 1){
Assert.fail(match + " matches of text string: '" + regex + "' are not found!");
}
else{
ArrayList<TextView> allTextViews = RobotiumUtils.removeInvisibleViews(viewFetcher.getCurrentViews(TextView.class, true));
allTextViews.addAll((Collection<? extends TextView>) webUtils.getTextViewsFromWebView());
for (TextView textView : allTextViews) {
Log.d(LOG_TAG, "'" + regex + "' not found. Have found: '" + textView.getText() + "'");
}
allTextViews = null;
Assert.fail("Text string: '" + regex + "' is not found!");
}
}
}
这个方法中首先还是获取参数中传过来的控件这个方法没得可说,和前面的Search是一样的,拿到控件后就是点击了,调用 clickOnScreen()
public void clickOnScreen(View view, boolean longClick, int time) {
if(view == null)
Assert.fail("View is null and can therefore not be clicked!");
float[] xyToClick = getClickCoordinates(view);
float x = xyToClick[0];
float y = xyToClick[1];
if(x == 0 || y == 0){
sleeper.sleepMini();
try {
view = viewFetcher.getIdenticalView(view);
} catch (Exception ignored){}
if(view != null){
xyToClick = getClickCoordinates(view);
x = xyToClick[0];
y = xyToClick[1];
}
}
if (longClick)
clickLongOnScreen(x, y, time, view);
else
clickOnScreen(x, y, view);
}
这个方法主要是将View控件转化为坐标点,然后传给clickOnScreen()的重载形式,其中getClickCoordinates(view);这个方法就是要得到view的中心点的坐标,再加上view在屏幕上的坐标,得到了一个view中心点的坐标,(这里开始我不太明白但是也是在看了前辈的博文后才有所理解),算了不能犯懒我还是详细记录一下吧, view.getLocationOnScreen()这个方法是得到view控件左边和上边 距离屏幕左边和屏幕上边的距离从而得到坐标,再加上view内部中心点的的坐标,得到view中心点在屏幕上的绝对坐标
private float[] getClickCoordinates(View view){
sleeper.sleep(MINI_WAIT);
int[] xyLocation = new int[2];
float[] xyToClick = new float[2];
view.getLocationOnScreen(xyLocation);
final int viewWidth = view.getWidth();
final int viewHeight = view.getHeight();
final float x = xyLocation[0] + (viewWidth / 2.0f);
float y = xyLocation[1] + (viewHeight / 2.0f);
xyToClick[0] = x;
xyToClick[1] = y;
return xyToClick;
}
得到坐标后发送个体clickOnScreen()重载方法这里有一个 LongClick的值来判断 是点击还是长按
public void clickOnScreen(float x, float y, View view) {
boolean successfull = false;
int retry = 0;
SecurityException ex = null;
while(!successfull && retry < 10) {
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, eventTime,
MotionEvent.ACTION_DOWN, x, y, 0);
MotionEvent event2 = MotionEvent.obtain(downTime, eventTime,
MotionEvent.ACTION_UP, x, y, 0);
try{
inst.sendPointerSync(event);
inst.sendPointerSync(event2);
successfull = true;
}catch(SecurityException e){
ex = e;
dialogUtils.hideSoftKeyboard(null, false, true);
sleeper.sleep(MINI_WAIT);
retry++;
View identicalView = viewFetcher.getIdenticalView(view);
if(identicalView != null){
float[] xyToClick = getClickCoordinates(identicalView);
x = xyToClick[0];
y = xyToClick[1];
}
}
}
if(!successfull) {
Assert.fail("Click at ("+x+", "+y+") can not be completed! ("+(ex != null ? ex.getClass().getName()+": "+ex.getMessage() : "null")+")");
}
}
到这里就一目了然了,看到了使用Instrumentation发送 MotionEvent事件完成点击,其实长按和点击是差不多的就是Key_dwon 然后sleep.
下面来看看type 先看代码
public void typeText(final EditText editText, final String text){
if(editText != null){
inst.runOnMainSync(new Runnable()
{
public void run()
{
editText.setInputType(InputType.TYPE_NULL);
}
});
clicker.clickOnScreen(editText, false, 0);
dialogUtils.hideSoftKeyboard(editText, true, true);
boolean successfull = false;
int retry = 0;
while(!successfull && retry < 10) {
try{
inst.sendStringSync(text);
successfull = true;
}catch(SecurityException e){
dialogUtils.hideSoftKeyboard(editText, true, true);
retry++;
}
}
if(!successfull) {
Assert.fail("Text can not be typed!");
}
}
}
}
这里其实没有什么太多难度,此方法首先设置输入类型deitText..setInputType(0),然后把焦点数值在EditText上,隐藏键盘,这些都是准备工作,最好开始输入,又事强大的Instrumentation来发送事件
,这里有一点很重要typeText 和EnterText在方法调用时其实是类似的,但是有一些区别具体请看源码
typeText调用inst.sendStringSync(text);
public void sendStringSync(String text) {
if (text == null) {
return;
}
KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
KeyEvent[] events = keyCharacterMap.getEvents(text.toCharArray());
if (events != null) {
for (int i = 0; i < events.length; i++) {
// We have to change the time of an event before injecting it because
// all KeyEvents returned by KeyCharacterMap.getEvents() have the same
// time stamp and the system rejects too old events. Hence, it is
// possible for an event to become stale before it is injected if it
// takes too long to inject the preceding ones.
sendKeySync(KeyEvent.changeTimeRepeat(events[i], SystemClock.uptimeMillis(), 0));
}
}
}
可以注意到 再输入时将text转为了 char数组 一个一个的输入,让我们类对比EnterText方法 ,这个方法直接调用EditText的setText的方法editText.setText(text);
public final void setText(CharSequence text) {
setText(text, mBufferType);
}
这个方法就是一下把文本全部写上去没有发送KeyEvent事件,这一点是需要注意的
到此处Solo类的绝大多数类型都已经统计完毕,这是我对Robotium原理的一些简单理解,还有很多不对的和不清晰的地方 比如为什么要用 OnMainThread和OnUiThread等希望大家指出