• 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控件。

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

    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。

    1. private AccessibilityNodeInfo mCachedNode;
    2. /**
    3. * Returns an up-to-date {@link AccessibilityNodeInfo} corresponding to the {@link android.view.View} that
    4. * this object represents.
    5. */
    6. private AccessibilityNodeInfo getAccessibilityNodeInfo() {
    7. if (mCachedNode == null) {
    8. throw new IllegalStateException("This object has already been recycled");
    9. }
    10. getDevice().waitForIdle();
    11. if (!mCachedNode.refresh()) {
    12. getDevice().runWatchers();
    13. if (!mCachedNode.refresh()) {
    14. throw new StaleObjectException();
    15. }
    16. }
    17. return mCachedNode;
    18. }

    3、获取父控件getParent()

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

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

    4、获取子控件个数

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

    1. /** Returns the number of child elements directly under this object. */
    2. public int getChildCount() {
    3. return getAccessibilityNodeInfo().getChildCount();
    4. }
    5. /**
    6. * Gets the number of children.
    7. *
    8. * @return The child count.
    9. */
    10. public int getChildCount() {
    11. return mChildNodeIds == null ? 0 : mChildNodeIds.size();
    12. }

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

    1. /**
    2. * Searches all elements under this object and returns the first object to match the criteria,
    3. * or null if no matching objects are found.
    4. */
    5. public UiObject2 findObject(BySelector selector) {
    6. AccessibilityNodeInfo node =
    7. ByMatcher.findMatch(getDevice(), selector, getAccessibilityNodeInfo());
    8. return node != null ? new UiObject2(getDevice(), selector, node) : null;
    9. }
    10. /** Searches all elements under this object and returns all objects that match the criteria. */
    11. public List findObjects(BySelector selector) {
    12. List ret = new ArrayList();
    13. for (AccessibilityNodeInfo node :
    14. ByMatcher.findMatches(getDevice(), selector, getAccessibilityNodeInfo())) {
    15. ret.add(new UiObject2(getDevice(), selector, node));
    16. }
    17. return ret;
    18. }

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

    1. /** Returns the visible bounds of this object in screen coordinates. */
    2. public Rect getVisibleBounds() {
    3. return getVisibleBounds(getAccessibilityNodeInfo());
    4. }
    5. /** Returns the visible bounds of this object with the margins removed. */
    6. private Rect getVisibleBoundsForGestures() {
    7. Rect ret = getVisibleBounds();
    8. ret.left = ret.left + mMarginLeft;
    9. ret.top = ret.top + mMarginTop;
    10. ret.right = ret.right - mMarginRight;
    11. ret.bottom = ret.bottom - mMarginBottom;
    12. return ret;
    13. }

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

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

    1. /**
    2. * Returns the class name of the underlying {@link android.view.View} represented by this
    3. * object.
    4. */
    5. public String getClassName() {
    6. CharSequence chars = getAccessibilityNodeInfo().getClassName();
    7. return chars != null ? chars.toString() : null;
    8. }
    9. /** Returns the content description for this object. */
    10. public String getContentDescription() {
    11. CharSequence chars = getAccessibilityNodeInfo().getContentDescription();
    12. return chars != null ? chars.toString() : null;
    13. }
    14. /** Returns the package name of the app that this object belongs to. */
    15. public String getApplicationPackage() {
    16. CharSequence chars = getAccessibilityNodeInfo().getPackageName();
    17. return chars != null ? chars.toString() : null;
    18. }
    19. /** Returns the fully qualified resource name for this object's id. */
    20. public String getResourceName() {
    21. CharSequence chars = getAccessibilityNodeInfo().getViewIdResourceName();
    22. return chars != null ? chars.toString() : null;
    23. }

    8、UI手势操作

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

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

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

  • 相关阅读:
    【quartus13.1/Verilog】swjtu西南交大:计组课程设计
    虹科校园大使招募令
    MySQL数据库基本操作
    基于正负序双dq旋转坐标系锁相环 DDSRF-PLL模型
    加密算法笔记
    《docker基础篇:8.Docker常规安装简介》包括:docker常规安装总体步骤、安装tomcat、安装mysql、安装redis
    ChatGLM2-6B 部署
    SecureCRT 特点介绍 SecureCRT的安装和使用
    T-SQL——数字辅助表
    Docker、Jenkins、Git 自动化部署 SpringBoot 项目(从零到搭建完成)
  • 原文地址:https://blog.csdn.net/liuqinhou/article/details/125987684