首页 > 代码库 > uiautomator的xpath选择器

uiautomator的xpath选择器

官方的uiautomator是没有xpath选择器的,这里介绍一种利用xpath查找控件的方法。

首要的问题是,如何获取界面元素根节点。

先来看UiDevice的这段代码:

public void dumpWindowHierarchy(String fileName) {
        AccessibilityNodeInfo root =
                getAutomatorBridge().getQueryController().getAccessibilityRootNode();
        if(root != null) {
            AccessibilityNodeInfoDumper.dumpWindowToFile(
                    root, new File(new File(Environment.getDataDirectory(),
                            "local/tmp"), fileName));
        }
    }

root即是所要获取的根节点。

接下来看getAutomatorBridge()干了什么:

    UiAutomatorBridge getAutomatorBridge() {
        return mUiAutomationBridge;
    }

很简单,直接返回了一个成员变量。我们看看这个变量是啥:

    // provides access the {@link QueryController} and {@link InteractionController}
    private final UiAutomatorBridge mUiAutomationBridge;

getAutomatorBridge()方法是包内可见的,也就是说在代码中不能直接调用。就算定义了一个com.android.uiautomator.core包内的类,也没法调用,因为android.jar并没有对外暴露这个API.

不过可以通过反射的方式,获取UiDevice实例对象的getAutomatorBridge方法,再行调用就可以了。

另外,这个方法返回的是UiAutomatorBridge类型,这个类型也是包内可见,没问题,再用反射就是。

就这样一层一层剥,终于获取到AccessibilityNodeInfo类型的根节点,代码如下:

        UiDevice dev = UiDevice.getInstance();
        Method getAutomatorBridge = UiDevice.class.getDeclaredMethod("getAutomatorBridge");
        Object bridge = getAutomatorBridge.invoke(dev);
        
        Class<?> UiAutomatorBridge_class = dev.getClass().getClassLoader().loadClass("com.android.uiautomator.core.UiAutomatorBridge");
        Method getQueryController = UiAutomatorBridge_class.getDeclaredMethod("getQueryController");
        Object qc = getQueryController.invoke(bridge);
        
        Class<?> QueryController_class = dev.getClass().getClassLoader().loadClass("com.android.uiautomator.core.QueryController");
        Method getAccessibilityRootNode = QueryController_class.getDeclaredMethod("getAccessibilityRootNode");
        AccessibilityNodeInfo root = (AccessibilityNodeInfo) getAccessibilityRootNode.invoke(qc);

获取界面根节点后,事情就好办了。参见这个类:com.android.uiautomator.core.AccessibilityNodeInfoDumper,它从根节点出发遍历控件树,读取控件属性,并生成对应的XML节点,过程很简单,不贴代码了。

需要注意的是,生成的XML除了根节点,每个节点名称都是node,控件类型则放在class属性中。可以重写这个过程,把class作为XML节点名称,这样就可以利用xpath进行定位了。

XML示例片断如下:

<hierarchy>
<
android.widget.FrameLayout instance="0" index="0" text="" resource-id="" package="com.android.settings" content-desc=""
    checkable
="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
    long-clickable
="false" password="false" selected="false" bounds="[0,0][1080,1920]">
  <
android.widget.FrameLayout instance="1" index="0" text="" resource-id="miui:id/action_bar_overlay_layout" package="com.android.settings" content-desc=""
      checkable
="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1920]">
    <
android.widget.FrameLayout instance="2" index="0" text="" resource-id="android:id/content" package="com.android.settings" content-desc="" checkable="false" checked="false" clickable="false"
      enabled
="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][1080,1920]">
<!-- 省略 -->
</hierarchy>

注意这里增加了一个很重要的属性instance,它对应于UiSelector.instance(int),表示同类型节点的深度优先次序编号。

在生成XML遍历控件树时,是以深度优先次序进行的,只要记住遍历过节点每种类型的个数,就能很方便地得到instance属性。

后面就简单了,用xpath定位节点,获取节点的名称和instance属性,就可以利用UiSelector.className(...).instance(...)进行控件定位。

还有一种笨方法,在xpath定位到节点后,一路沿parent向上,直到根节点,并把路径记下来。再从根节点出发,沿路径返回,利用子节点的index属性,通过UiObject.getChild(...)来依次获取子节点,就这样一路向下,直到目标节点。这个方法比较笨,个人觉得它只适合于前一种方法找不到的情况下的一种补充手段。

uiautomator的xpath选择器