UiAutomator源码学习(3)-- UiObject

时间:2024-01-27 09:48:50

UiAutomator是由谷歌在Android4.1版本发布时推出的一款用Java编写的UI自动化测试框架。
基于Accessibility服务,该工具提供了对外开放的的api,可以使用这些api对安卓应用进行一系列的自动化测试操作。例如:打开app、点击、滑动、键盘输入、长按以及常用的断言等一系列模拟ui操作。

UiAutomator是Android自动化测试框架,其最大的特点就是可以跨进程操作,基本用法:

device = UiDevice.getInstance(getInstrumentation());
 device.pressHome();

    // Bring up the default launcher by searching for a UI component
    // that matches the content description for the launcher button.
 UiObject allAppsButton = device
            .findObject(new UiSelector().description("Apps"));

    // Perform a click on the button to load the launcher.
 allAppsButton.clickAndWaitForNewWindow();
UiDevice提供了很多操作的接口,在上一章介绍过。本章介绍UIObject和UiSelector
UiObject代表了Android应用中定义的任意的View控件,可以根据
UiSelector属性在运行时找到匹配的视图,可以对应不同的view。由此可以看出UIObject是视图对象,UiSelector是获取视图对象的匹配规则。
看在UiDevice中的findObject方法:
  /**
     * Returns a UiObject which represents a view that matches the specified selector criteria.
     *
     * @param selector
     * @return UiObject object
     */
    public UiObject findObject(UiSelector selector) {
        return new UiObject(this, selector);
    }

这个findObject方法返回了一个 UIObject对象,看UIObject的构造函数:

 /**
     * Package-private constructor. Used by {@link UiDevice#findObject(UiSelector)} to construct a
     * UiObject.
     */
    UiObject(UiDevice device, UiSelector selector) {
        mDevice = device;
        mUiSelecto

这是一个包内可见的构造函数,所以如果我们想自己new UIObject,就会提示

'UiObject(androidx.test.uiautomator.UiDevice, androidx.test.uiautomator.UiSelector)' is not public in 'androidx.test.uiautomator.UiObject'. Cannot be accessed from outside package

所以我们只能用UiDevice通过UiSelector去查找界面中的UIObject。在UIObject这个类中我们也可以看到有获取UIObject属性的方法,如:

 /**
     * Retrieves the <code>className</code> property of the UI element.
     *
     * @return class name of the current node represented by this UiObject
     * @throws UiObjectNotFoundException if no match was found
     * @since API Level 18
     */
    public String getClassName() throws UiObjectNotFoundException {
        Tracer.trace();
        AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
        if(node == null) {
            throw new UiObjectNotFoundException(mUiSelector.toString());
        }
        String retVal = safeStringReturn(node.getClassName());
        Log.d(LOG_TAG, String.format("getClassName() = %s", retVal));
        return retVal;
    }
       AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
方法里调用的是QueryController中的方法findAccessibilityNodeInfo(getSelector());

 在这个方法中有一个同步的代码块查找根节点:

 synchronized (mLock) {
            AccessibilityNodeInfo rootNode = getRootNode();
            if (rootNode == null) {
                Log.e(LOG_TAG, "Cannot proceed when root node is null. Aborted search");
                return null;
            }
            // Copy so that we don't modify the original's sub selectors
            UiSelector uiSelector = new UiSelector(selector);
            return translateCompoundSelector(uiSelector, rootNode, isCounting);
        }

这里是UiAutomator得到节点的核心方法。

getRootNode()最终调用的是UiAutomation的getRootInActiveWindow方法(与AccessibilityService交互)来获取根节点rootNode。根节点获取之后,通过递归

translateCompoundSelector遍历 子节点。
 1  /**
 2      * A compoundSelector encapsulate both Regular and Pattern selectors. The formats follows:
 3      * <p/>
 4      * regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]]
 5      * <br/>
 6      * pattern_selector = ...CONTAINER=By[..] PATTERN=By[instance=x PATTERN=[regular_selector]
 7      * <br/>
 8      * compound_selector = [regular_selector [pattern_selector]]
 9      * <p/>
10      * regular_selectors are the most common form of selectors and the search for them
11      * is straightforward. On the other hand pattern_selectors requires search to be
12      * performed as in regular_selector but where regular_selector search returns immediately
13      * upon a successful match, the search for pattern_selector continues until the
14      * requested matched _instance_ of that pattern is matched.
15      * <p/>
16      * Counting UI objects requires using pattern_selectors. The counting search is the same
17      * as a pattern_search however we're not looking to match an instance of the pattern but
18      * rather continuously walking the accessibility node hierarchy while counting matched
19      * patterns, until the end of the tree.
20      * <p/>
21      * If both present, order of parsing begins with CONTAINER followed by PATTERN then the
22      * top most selector is processed as regular_selector within the context of the previous
23      * CONTAINER and its PATTERN information. If neither is present then the top selector is
24      * directly treated as regular_selector. So the presence of a CONTAINER and PATTERN within
25      * a selector simply dictates that the selector matching will be constraint to the sub tree
26      * node where the CONTAINER and its child PATTERN have identified.
27      * @param selector
28      * @param fromNode
29      * @param isCounting
30      * @return AccessibilityNodeInfo
31      */
32     private AccessibilityNodeInfo translateCompoundSelector(UiSelector selector,
33             AccessibilityNodeInfo fromNode, boolean isCounting) {
34         // Start translating compound selectors by translating the regular_selector first
35         // The regular_selector is then used as a container for any optional pattern_selectors
36         // that may or may not be specified.
37         if(selector.hasContainerSelector())
38             // nested pattern selectors
39             if(selector.getContainerSelector().hasContainerSelector()) {
40                 fromNode = translateCompoundSelector(
41                         selector.getContainerSelector(), fromNode, false);
42                 initializeNewSearch();
43             } else
44                 fromNode = translateReqularSelector(selector.getContainerSelector(), fromNode);
45         else
46             fromNode = translateReqularSelector(selector, fromNode);
47         if(fromNode == null) {
48             if (DEBUG)
49                 Log.d(LOG_TAG, "Container selector not found: " + selector.dumpToString(false));
50             return null;
51         }
52         if(selector.hasPatternSelector()) {
53             fromNode = translatePatternSelector(selector.getPatternSelector(),
54                     fromNode, isCounting);
55             if (isCounting) {
56                 Log.i(LOG_TAG, String.format(
57                         "Counted %d instances of: %s", mPatternCounter, selector));
58                 return null;
59             } else {
60                 if(fromNode == null) {
61                     if (DEBUG)
62                         Log.d(LOG_TAG, "Pattern selector not found: " +
63                                 selector.dumpToString(false));
64                     return null;
65                 }
66             }
67         }
68         // translate any additions to the selector that may have been added by tests
69         // with getChild(By selector) after a container and pattern selectors
70         if(selector.hasContainerSelector() || selector.hasPatternSelector()) {
71             if(selector.hasChildSelector() || selector.hasParentSelector())
72                 fromNode = translateReqularSelector(selector, fromNode);
73         }
74         if(fromNode == null) {
75             if (DEBUG)
76                 Log.d(LOG_TAG, "Object Not Found for selector " + selector);
77             return null;
78         }
79         Log.i(LOG_TAG, String.format("Matched selector: %s <<==>> [%s]", selector, fromNode));
80         return fromNode;
81     }
遍历的条件是selector.hasContainerSelector(),UiSelector稍后会详细的解读。

在UiObject中,还有对对象的操作方法,如click swipe等.这些方法调用的和UiDevice中的方法一样,都是

getAutomatorBridge().getInteractionController().clickNoSync(x, y);如果不一样可能也是多了一些超时参数之类的重载方法。
所以之后的流程可以参考上篇 UiDevice的解读,这里不再复述。
UiDevice: https://www.cnblogs.com/yuan1225/p/13254235.html