由于是从 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 方法。
以上的解释不知道能清楚不


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