由于是从 UiAutomator 阵营转到 appium 上来的,所以留下了个不太好的习惯就是习惯性的使用 appium 的 find_element_by_android_uiautomator 进行控件元素的定位。那疑问就来了 appium 又是如何通过我传入的字符串进行解析的。这里说来惭愧,实际上只是看了 UiSelectorParser 但是 appium 到底什么时候调用它我还没去详细看过。
进入到 appium 源代码目录的
这里我们关注 UiSelectorParser.java 从字面就能够明白它是用来解析你的 UiSelector 语句的,还有一个 UIScrollableParser 就不用我解释了是干什么的了。
那么就进去看看 UiSelectorParser 到底做了些什么吧
首先进来先看到这句
private final static Method[] methods = UiSelector.class.getDeclaredMethods();
一个静态的 methods 数组,这里是获取了 UiSelector 这个类中的所有方法(不包括继承),后面会拿你传入参数中的方法与 methods 数组进行比较。
public UiSelector parse(String textToParse) throws UiSelectorSyntaxException {
selector = new UiSelector();
text = cleanseText(textToParse);
while (text.length() > 0) {
consumePeriod();
consumeFunctionCall();
}
return selector;
}
这里我们先举个例子,假设现在传入的参数内容是
"new UiSelector().resourceId(\"com.seewo.teachercare:id/pass_msg_head_name_textView\").checked(true)"
解析函数就这么几句话,首先是 new 出一个 UiSelector 对象,在来进入 cleansetText 中 看看这个方法做了什么
private String cleanseText(String dirtyText) {
String cleanText = dirtyText.trim();
if (cleanText.startsWith("new UiSelector()")) {
cleanText = cleanText.substring(16);
}
else if (cleanText.startsWith("UiSelector()")) {
cleanText = cleanText.substring(12);
}
else if (!cleanText.startsWith(".")){
cleanText = "." + cleanText;
}
return cleanText;
}
哦 原来如此直接是将 UiSelector 给过滤了,那么刚才我传入的字符串现在就变成了
".resourceId(\"com.seewo.teachercare:id/pass_msg_head_name_textView\").checked(true)"
不过这里就要注意一点很坑的地方,因为 appium 传 uiautomator 的方法都是字符串的形式,所以你一定要保证空格以及大小写正确,要是你在 new 与 UiSelector 之间再多一个空格的话,估计你找半天都不知道啥原因。
继续往下走
while (text.length() > 0) {
consumePeriod();
consumeFunctionCall();
}
这里循环判断文本的长度,如果文本内容没有解析完就继续解析,consumePeriod 这个方法的作用只是判断字符串开头有没有带'.'如果有就去掉,所以文本的解析以及 UiSelector 方法的调用都是在 consumeFunctionCall 方法里面。
那么就来看看 consumeFunctionCall 方法吧。
private void consumeFunctionCall() throws UiSelectorSyntaxException {
String methodName;
StringBuilder argument = new StringBuilder();
int parenIndex = text.indexOf('(');
//这里获取到方法名,通过截取‘(’ 刚好就得到了 resourceId
methodName = text.substring(0, parenIndex);
//下面的一大串则是为了获取'('以及')'中的内容,结果 argument的内容就是 //"com.seewo.teachercare:id/pass_msg_head_name_textView"
int index = parenIndex+1;
int parenCount = 1;
while (parenCount > 0) {
try {
switch (text.charAt(index)) {
case ')':
parenCount--;
if (parenCount > 0) {
argument.append(text.charAt(index));
}
break;
case '(':
parenCount++;
argument.append(text.charAt(index));
break;
default:
argument.append(text.charAt(index));
}
} catch (StringIndexOutOfBoundsException e) {
throw new UiSelectorSyntaxException("unclosed paren in expression");
}
index++;
}
if (argument.length() < 1) {
throw new UiSelectorSyntaxException(methodName + " method expects an argument");
}
//设置剩余的文本内容+2的意思是之前被过滤掉的左右括号 那么这里的结果就是.checked(true)
// add two for parentheses surrounding arg
text = text.substring(methodName.length() + argument.length() + 2);
//这里是将最开始获取的method的数组的函数名与传入的methodName做比较 将相同的已ArrayList进行返回
//得到 public com.android.uiautomator.core.UiSelector com.android.uiautomator.core.UiSelector.resourceId(java.lang.String)
ArrayList<Method> overloadedMethods = getSelectorMethods(methodName);
if (overloadedMethods.size() < 1) {
throw new UiSelectorSyntaxException("UiSelector has no " + methodName + " method");
}
selector = applyArgToMethods(overloadedMethods, argument.toString());
}
好了剩下 applyArgToMethods 了看到它的返回值是 UiSelector 就知道实际上执行 UiAutomator 的代码是在这里了。
private UiSelector applyArgToMethods(ArrayList<Method> methods, String argument) throws UiSelectorSyntaxException {
Object arg = null;
Method ourMethod = null;
UiSelectorSyntaxException exThrown = null;
//这里遍历methods ,并且同时比较参数 argument与method方法的参数类型是否一致
for (Method method : methods) {
try {
//获取参数类型
Type parameterType = method.getGenericParameterTypes()[0];
//比较参数类型,不一致的话就抛出异常
arg = coerceArgToType(parameterType, argument);
ourMethod = method;
} catch (UiSelectorSyntaxException e) {
exThrown = e;
}
}
if (ourMethod == null || arg == null) {
if (exThrown != null) {
throw exThrown;
} else {
throw new UiSelectorSyntaxException("Could not apply argument " + argument + " to UiSelector method");
}
}
//到这里一切OK的话 那么ourMethod就为public com.android.uiautomator.core.UiSelector com.android.uiautomator.core.UiSelector.resourceId(java.lang.String)
//arg 为 com.seewo.teachercare:id/pass_msg_head_name_textView
//下来看到invoke就知道了通过反射 通过对应的函数名反射UiSelector中的函数。返回selector.
try {
return (UiSelector)ourMethod.invoke(selector, arg);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new UiSelectorSyntaxException("problem using reflection to call this method");
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new UiSelectorSyntaxException("problem using reflection to call this method");
}
}
重新回到 while 循环处,还记得刚才我们的 text 还剩下 .checked(true),那么就继续
下来大家肯定就明白了,method 为 checked 参数为 true,调用 UiSelector 中的 checked 方法。
以上的解释不知道能清楚不