UIAutomator2常用类之UiObject2

        UiAutomator 涉及到的类有: UiObject、UiObject2、UiDevice、UiWatcher、BySelector、AccessibilityNodeInfo、Gestures、GestureController、Instrumentation

一、UiObject和UiObject2

        UiObject2代表一个UI控件。它绑定到特定的视图实例,如果底层视图对象被破坏,它可能会变得陈旧。 因此,如果 UI 发生重大变化,可能需要调用 UiDevice#findObject(BySelector)来获取新的 UiObject2 实例。

        UiObject 代表一个UI控件。 它不以任何方式直接绑定到视图作为对象引用。 UiObject 包含有助于在运行时根据其构造函数中指定的 {@link UiSelector} 属性定位匹配视图的信息。 一旦你创建了一个 UiObject 的实例,它就可以被重用于匹配选择器标准的不同视图。

        UiObject是UI Automator测试框架早期的重要类,UiObject2是其改进版。

        区别:参考文章”UiObject与UiObject2触发UiWatcher代码时机探究

        UI Automator测试框架最常使用UiObject与UiObject2,这两个类产生的对象,都表示符合指定条件的控件,当没有找到控件时,会触发UiDevice中所有注册的UiWatcher对象,我们可以在UiWatcher的实现类中,编写没有找到控件时的处理逻辑,比如没有找到某个控件,可能因为弹出的系统对话框挡住我们需要查找的控件,此时就可以在UiWatcher的实现类中,编写关闭这关闭系统弹窗、或者关闭某个业务弹窗的业务逻辑代码。

    UiObject、UiObject2触发UiWatcher代码的时机是不同的,先剧透一下它们各自触发UiWatcher代码的时机

        1、UiObject执行操作控件的方法时,才会触发UiWatcher的代码。比如调用UiObject的click()方法时,可以触发UiWatcher的代码,而使用UiDevice的findObject(UiSelector)去查找控件时,并不会导致UiWatcher代码的触发

        2、UiObject2,则是UiDevice的findObject(BySelector)方法获取时就会触发UiWatcher的代码,即执行查找控件时,即会触发触发UiWatcher的代码

        我们可以来看看UiObject2的源码。首先来看看UiObject2类内部都用到了哪些依赖类:

        UiDevice:表示UI操作的设备,里面封装了设备信息、获取设备控件和设备手势操作函数;

        BySelector: 查找UiDevice设备上控件的条件集合对象,通过它可以在设备上找到目标控件;

        AccessibilityNodeInfo:可访问节点信息,设备屏幕上能看到的所有控件(包括View和布局)都可以抽象成一个个节点,并且节点中可以嵌套节点从而组成控件树(也即节点树)。AccessibilityNodeInfo包含了节点信息,比如:控件id, text和宽度、父节点、子节点信息,是否可点击,是否勾选等,并封装了ui操作方法。总之UiObject2对象关于控件属性的一些信息都是从AccessibilityNodeInfo身上获取的。完全可以把UiObject2理解成AccessibilityNodeInfo的一个代理

        Gestures:手势对象,ui操作,比如点击、滑动这些操作都可以抽象成一个手势;

        GestureController:用来执行手势动作的。

下面来具体分析UiObject2中具体的源码

1、构造器

        可以看到,UiObject2是一个包内私有的函数,意味着我们不可以直接通过new的方式创建一个UiObject2对象。而是通过UiDevice#findObject(BySelector)的方式创建UiObject2对象。即UiDevice通过查找器对象BySelector来在设备上查找符合条件的UI控件。

/** Package-private constructor. Used by {@link UiDevice#findObject(BySelector)}. */
UiObject2(UiDevice device, BySelector selector, AccessibilityNodeInfo cachedNode) {
    mDevice = device;
    mSelector = selector;
    mCachedNode = cachedNode;
    mGestures = Gestures.getInstance(device);
    mGestureController = GestureController.getInstance(device);
    mDisplayMetrics = mDevice.getInstrumentation().getContext().getResources()
            .getDisplayMetrics();
}

2、getAccessibilityNodeInfo()获取控件最新的节点信息

        首先,UiObject2代表的控件信息保存在mCachedNode属性上,它是一个AccessibilityNodeInfo对象。

        1、函数中先判断mCachedNode对象是否为null,如果为null代表这个节点已经被回收了,则抛出异常;

        2、然后调用getDevice().waitForIdle()等待设备处于空闲状态,即当前手机页面元素没有变化;

        3、调用mCachedNode.refresh()来刷新控件的状态从而获取控件上的最新信息,比如textview控件上文本可能发生变化,调用refreash()后获取最新的文本;

        4、如果refresh()返回false,代表控件已过时,说明该控件已经不在控件树中,该控件应该被回收;

        5、refresh()返回false,则调用绑定在UiObject2上的UiWatcher的checkForCondition()处理当控件找不到的异常情况;等checkForCondition()之后,再检查一遍mCachedNode是否存在,如果还不存在,则直接报错;

        6、refresh()返回true,代表在设备控件树上找到了这个控件,并更新了控件信息;

        7、直接返回mCachedNode。

private AccessibilityNodeInfo mCachedNode;


/**
 * Returns an up-to-date {@link AccessibilityNodeInfo} corresponding to the {@link android.view.View} that
 * this object represents.
 */
private AccessibilityNodeInfo getAccessibilityNodeInfo() {
    if (mCachedNode == null) {
        throw new IllegalStateException("This object has already been recycled");
    }

    getDevice().waitForIdle();
    if (!mCachedNode.refresh()) {
        getDevice().runWatchers();

        if (!mCachedNode.refresh()) {
            throw new StaleObjectException();
        }
    }
    return mCachedNode;
}

3、获取父控件getParent()

        内部调用太复杂了,没看懂。先不管了,只要只是是获取当前控件的父控件即可。

/** Returns this object's parent, or null if it has no parent. */
public UiObject2 getParent() {
    AccessibilityNodeInfo parent = getAccessibilityNodeInfo().getParent();
    return parent != null ? new UiObject2(getDevice(), mSelector, parent) : null;
}

4、获取子控件个数

        先是获更新当前控件的最新信息,其中就包括了mChildNodeIds也会更新。mChildNodeIds是AccessibilityNodeInfo中的一个数组类型对象,保存当前节点的子节点信息。

/** Returns the number of child elements directly under this object. */
public int getChildCount() {
    return getAccessibilityNodeInfo().getChildCount();
}

/**
 * Gets the number of children.
 *
 * @return The child count.
 */
public int getChildCount() {
    return mChildNodeIds == null ? 0 : mChildNodeIds.size();
}

5、获取当前控件内部符合条件一个或所有子控件

/**
 * Searches all elements under this object and returns the first object to match the criteria,
 * or null if no matching objects are found.
 */
public UiObject2 findObject(BySelector selector) {
    AccessibilityNodeInfo node =
            ByMatcher.findMatch(getDevice(), selector, getAccessibilityNodeInfo());
    return node != null ? new UiObject2(getDevice(), selector, node) : null;
}

/** Searches all elements under this object and returns all objects that match the criteria. */
public List<UiObject2> findObjects(BySelector selector) {
    List<UiObject2> ret = new ArrayList<UiObject2>();
    for (AccessibilityNodeInfo node :
            ByMatcher.findMatches(getDevice(), selector, getAccessibilityNodeInfo())) {

        ret.add(new UiObject2(getDevice(), selector, node));
    }

    return ret;
}

6、获取控件可见区域包括外边距的可见区域

/** Returns the visible bounds of this object in screen coordinates. */
public Rect getVisibleBounds() {
    return getVisibleBounds(getAccessibilityNodeInfo());
}

/** Returns the visible bounds of this object with the margins removed. */
private Rect getVisibleBoundsForGestures() {
    Rect ret = getVisibleBounds();
    ret.left = ret.left + mMarginLeft;
    ret.top = ret.top + mMarginTop;
    ret.right = ret.right - mMarginRight;
    ret.bottom = ret.bottom - mMarginBottom;
    return ret;
}

7、获取控件的类名、描述信息、应用包名、资源id

        可以发现,其实这些信息都保存在UiObject2对象持有的AccessibilityNodeInfo属性对象上

/**
 * Returns the class name of the underlying {@link android.view.View} represented by this
 * object.
 */
public String getClassName() {
    CharSequence chars = getAccessibilityNodeInfo().getClassName();
    return chars != null ? chars.toString() : null;
}

/** Returns the content description for this object. */
public String getContentDescription() {
    CharSequence chars = getAccessibilityNodeInfo().getContentDescription();
    return chars != null ? chars.toString() : null;
}

/** Returns the package name of the app that this object belongs to. */
public String getApplicationPackage() {
    CharSequence chars = getAccessibilityNodeInfo().getPackageName();
    return chars != null ? chars.toString() : null;
}

/** Returns the fully qualified resource name for this object's id. */
public String getResourceName() {
    CharSequence chars = getAccessibilityNodeInfo().getViewIdResourceName();
    return chars != null ? chars.toString() : null;
}

8、UI手势操作

        可以看到在控件上执行UI操作,需要用到GestureController手势控制对象。这个对象在UiObject2的构造器中进行了初始化。具体的UI动作,比如点击、滑动都是抽象成了Gestures类。

具体的如何实现打算单独写一篇文章总结。

/** Clicks on this object. */
public void click() {
    mGestureController.performGesture(mGestures.click(getVisibleCenter()));
}
/** Package-private constructor. Used by {@link UiDevice#findObject(BySelector)}. */
UiObject2(UiDevice device, BySelector selector, AccessibilityNodeInfo cachedNode) {
    mDevice = device;
    mSelector = selector;
    mCachedNode = cachedNode;
    mGestures = Gestures.getInstance(device);
    mGestureController = GestureController.getInstance(device);
    mDisplayMetrics = mDevice.getInstrumentation().getContext().getResources()
            .getDisplayMetrics();
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值