UiAutomator笔记之UiObject API(四)

本文详细介绍了UiObject的各种操作方法,包括点击与长按、拖拽与滑动、输入与清除文本、获取对象属性及判断、手势操作等,并提供了具体示例。

一、UiObject 功能

UiObject用来代表一个组件对象,它提供一系列方法和属性来模拟在手机上的实际操作。如:文本的输入和清除、点击、长按、拖动、滑动,也可获取组件的属性、判断组件对象是否存在等。


二、API详细介绍

1、点击与长按

(1)相关API

返回值 API 说明
boolean click() 点击对象
boolean clickAndWaitForNewWindow(long timeout) 点击对象,等待新窗口出现,参数为等待超时时长
boolean clickAndWaitForNewWindow() 点击对象,等待新窗口出现
boolean clickBottomRight() 点击对象的右下角
boolean clickTopLeft() 点击对象的左上角
boolean longClick() 长按对象,对对象执行长按操作
boolean longClickBottomRight() 长按对象的右下角
boolean longClickTopLeft() 长按对象的左上角

(2)示例

new UiObject(new Selector().resourceId("xxxxx").click();//对指定资源id的组件执行点击操作

2、拖拽与滑动
(1)区别
拖拽:将控件从当前位置移动到指定位置
滑动:向某一方向(上、下、左、右)移动一小段距离

(2)相关API
返回值 API 说明
boolean dragTo(UiObject destObj, int steps) 拖拽对象到另一个对象位置上,步长可设置拖动的速度
boolean dragTo(int destX, int destY, int steps) 拖拽对象到屏幕某个坐标位置上,步长可设置拖动速度
boolean swipeDown(int steps) 拖动对象往下滑动
boolean swipeLeft(int steps) 拖动对象往左滑动
boolean swipeRight(int steps) 拖动对象往右滑动
boolean swipeUp(int steps) 拖动对象往上滑动


3、输入文本与清除文本

(1)相关API
返回值 API 说明
boolean setText(String text) 在对象中输入文本(实现方式:先清除文本再输入)
void clearTextField() 清除编辑框中的文本(实现方式:长按再清除)


(2)补充说明

clearTextField()的内部实现方式是先长按文本框然后全选删除,导致有些编辑框无法通过调用该方法清除文本内容。这时最好自己写代码实现清除文本功能。

示例代码:

//将光标移动到行尾,使用backspace进行逐个删除

UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_MOVE_END);

//判断条件中w是编辑框为空时所显示的hint文本对象;当hint出现时,说明该编辑框内的文本已清空

while(!w.exists()){
UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_DEL);
}

4、获取对象的属性与属性的判断


(1)获取对象的属性-相关API
返回值 API 说明
Rect getBounds() 获得对象矩形坐标,矩形左上角坐标与右下角坐标
int getChildCount() 获得下一级子类数量
String getClassName() 获得对象类名属性的类名文本
String getContentDescription() 获得对象的描述属性的描述文本
String getPackageName() 获得对象包名属性的包名文本
String getText() 获得对象的文本属性中的文本
Rect getVisibleBounds() 返回可见视图的范围,如果视图的部分是可见的,只有可见部分报告的范围

(2)获取父类与子类节点-相关API

返回值 API 说明
UiObject getChild(UiSelector selector) 获得对象的子类对象,可以递归获取子孙当中某个对象
UiObject getFromParent(UiSelector selector) 从父类获取子类,按照UiSeletor获取兄弟类(递归)


(3)属性的判断-相关API
返回值 API 说明
boolean isCheckable() 检查对象的checkable属性是否为true
boolean isChecked() 检查对象的checked属性是否为true
boolean isClickable() 检查对象的clickable属性是否为true
boolean isEnabled() 检查对象的enabled属性是否为true
boolean isFocusable() 检查对象的focusable属性是否为true
boolean isFocused() 检查对象的focused属性是否为true
boolean isLongClickable() 检查对象的longclickable属性是否为true
boolean isScrollable() 检查对象的scrollable属性是否为true
boolean isSelected() 检查对象的selected属性是否为true


5、手势的操作


(1)手势相关操作
两指平移 
多指平移 
两指合拢 o---> <---o
两指扩张 <---oo--->

(2)相关API
返回值 API 说明
boolean performMultiPointerGesture(PointerCoords[]... touches) 执行单手指触控手势,可定义任意手势,与形状

boolean

performTwoPointerGesture(Point startPoint1, Point startPoint2, 
Point endPoint1, Point endPoint2, int steps)

执行任意两个手指触控手势,模拟两个手指手势
boolean pinchIn(int percent, int steps) 手势操作,两点向内收缩
boolean pinchOut(int percent, int steps) 手势操作,两点向外张开

6、判断对象是否存在

(1)相关API

返回值 API 说明
boolean waitForExists(long timeout) 等待对象出现
boolean waitUntilGone(long timeout) 等待对象消失
boolean exists() 检查对象是否存在


<think> 我们正在处理一个Android UI测试中的问题:在调用`UiObject2.getText()`时抛出`androidx.test.uiautomator.StaleObjectException`异常。该异常出现在包含`videoNext`和`checkTittleVideo`方法的测试代码中。 ### 异常原因分析 `StaleObjectException`通常表示UI元素的状态已经发生变化,导致之前获取的`UiObject2`对象不再有效。这通常发生在以下情况: 1. **UI布局更新**:在获取`UiObject2`对象后,界面发生了重新渲染(例如,视频切换导致标题更新),此时旧对象对应的底层`AccessibilityNodeInfo`已失效。 2. **元素被回收**:在滚动列表或动态更新界面时,旧元素可能被系统回收,导致通过`findObject`获取的对象变成“陈旧”状态。 3. **异步操作未等待完成**:在点击“下一首”按钮后,没有等待界面完全刷新就尝试获取新的标题元素,此时元素可能处于中间状态。 在您提供的代码中,`checkTittleVideo`方法执行以下步骤: 1. 点击屏幕唤醒设备。 2. 查找标题元素(`tittle`)。 3. 点击“下一首”按钮(`nextSong`)。 4. 等待2秒(`SystemClock.sleep(2000)`)。 5. 再次查找标题元素(`tittle2`)并比较文本。 尽管有2秒的等待,但以下原因可能导致异常: - 等待时间不足:视频切换可能需要超过2秒。 - 等待是固定的,而实际加载时间可能因视频大小或设备性能而异。 - 在等待期间,标题元素可能被销毁并重新创建,导致之前获取的`tittle`对象失效(即使您是在点击后重新查找的,但如果在查找`tittle2`之前界面还在变化,也可能导致新获取的对象很快失效)。 ### 解决方案 #### 1. **优化等待机制** 使用动态等待(轮询)代替固定等待,确保元素稳定后再操作。例如,使用`UiDevice.wait`方法: ```java // 点击下一首后,等待新标题出现 device.wait(Until.hasObject(By.res("com.roadrover.video:id/path_name")), 5000); // 最多等5秒 ``` #### 2. **重新获取元素** 在每次需要使用元素前,重新查找元素。避免将元素存储在变量中并在后续使用(因为界面变化可能导致变量引用的元素失效)。在您的代码中,已经重新查找了`tittle2`,这是正确的。 但注意:在循环中,前一次循环获取的元素可能在下一次循环时失效。因此,在循环体内,每次都需要重新查找元素。 #### 3. **异常处理** 捕获`StaleObjectException`并重试: ```java try { downText = tittle2.getText(); } catch (StaleObjectException e) { Log.e(TAG, "StaleObjectException, retrying..."); // 重试:重新查找元素 tittle2 = device.findObject(By.res("com.roadrover.video:id/path_name")); if (tittle2 != null) { downText = tittle2.getText(); } else { // 处理元素找不到的情况 } } ``` #### 4. **确保界面稳定** 在操作前,确保界面没有正在进行的动画或加载。可以通过等待某个条件(如元素存在、元素文本变化)来实现。 ### 修改后的代码示例 以下是修改后的`checkTittleVideo`方法,结合了动态等待和异常处理: ```java @Test public void checkTittleVideo() { device.click(1000, 500); // 唤醒 SystemClock.sleep(1000); // 短暂等待唤醒动画 // 查找标题元素 UiObject2 tittle = device.wait(Until.findObject(By.res("com.roadrover.video:id/path_name")), 2000); if (tittle == null) { Log.e(TAG, "checkTittleVideo: no tittle"); return; } String upText = tittle.getText(); UiObject2 nextSong = device.findObject(By.res("com.roadrover.video:id/mediacontroller_next")); if (nextSong == null) { Log.e(TAG, "checkTittleVideo: no nextSong"); return; } nextSong.click(); // 动态等待新标题出现(等待标题元素更新,且文本不等于原来的upText) boolean found = device.wait(Until.gone(By.text(upText)), 5000); if (!found) { Log.e(TAG, "Old title still present after 5 seconds"); } // 重新获取标题元素 UiObject2 tittle2 = device.wait(Until.findObject(By.res("com.roadrover.video:id/path_name")), 5000); if (tittle2 == null) { Log.e(TAG, "checkTittleVideo: no tittle2"); return; } // 处理可能的StaleObjectException String downText; try { downText = tittle2.getText(); } catch (StaleObjectException e) { // 重试一次 tittle2 = device.findObject(By.res("com.roadrover.video:id/path_name")); if (tittle2 == null) { Log.e(TAG, "StaleObjectException and failed to refind"); return; } downText = tittle2.getText(); } if (!upText.equals(downText)) { Log.i(TAG, "checkTittleVideo: upText=" + upText); Log.i(TAG, "checkTittleVideo: downText=" + downText); assert true; } else { Log.e(TAG, "checkTittleVideo: Text did not change! upText=" + upText + ", downText=" + downText); assert false; } } ``` ### 关键点说明 - 使用`device.wait`方法替代固定等待,提高稳定性。 - 通过`Until.gone(By.text(upText))`确保旧标题消失,再获取新标题。 - 捕获`StaleObjectException`并在发生时重试一次。 ### 总结 `StaleObjectException`的根本原因是UI元素状态变化导致对象失效。通过动态等待界面更新、及时重新获取元素以及添加异常重试机制,可以有效解决此问题[^2][^4]。 §§相关问题§§ 1. 如何在Android UI测试中处理界面异步加载? 2. UiAutomator中`wait`方法的使用场景有哪些? 3. 如何优化Android UI测试的稳定性? </think>### `StaleObjectException` 异常原因分析与解决方案 在 Android UI 测试中,当调用 `UiObject2.getText()` 时出现 `androidx.test.uiautomator.StaleObjectException` 异常,通常是由以下原因导致的: --- ### 一、异常原因分析 #### 1. **UI 元素状态失效(核心原因)** [^2][^4] 当界面刷新后(如视频切换),之前通过 `findObject()` 获取的 `UiObject2` 对象对应的底层 `AccessibilityNodeInfo` 已失效。此时尝试操作该对象会抛出 `StaleObjectException`。 在您的代码中: ```java UiObject2 tittle = device.findObject(By.res("com.roadrover.video:id/path_name")); // ...点击 nextSong 后界面刷新... downText = tittle2.getText(); // 此时 tittle2 可能已失效 ``` #### 2. **异步操作未同步** [^4] 视频切换是异步操作,`SystemClock.sleep(2000)` 的固定等待可能不足: - 低端设备加载时间可能超过 2 秒 - 网络视频需要额外缓冲时间 - 动画过渡期间元素不可访问 #### 3. **循环中的对象复用问题** 在 `videoNext` 的 50 次循环中,未重新获取对象引用: ```java // 每次循环未重置对象 upText = null; downText = null; // 但 tittle/tittle2 对象仍引用旧内存地址 ``` --- ### 二、解决方案 #### 1. **重新获取对象引用(关键修复)** 在每次操作前重新查找对象: ```java // 修改后代码片段 nextSong.click(); // ✅ 重新查找对象而非复用旧引用 UiObject2 tittle2 = device.wait(Until.findObject( By.res("com.roadrover.video:id/path_name")), 5000); // 增加等待条件 if (tittle2 != null) { downText = tittle2.getText(); // 使用新获取的对象 } ``` #### 2. **使用动态等待替代固定等待** [^4] ```java // 替换 SystemClock.sleep(2000) device.wait(Until.gone( By.res("com.roadrover.video:id/loading_indicator")), // 等待加载消失 5000); // 超时5秒 ``` #### 3. **添加异常重试机制** ```java int retry = 0; while (retry < 3) { try { downText = tittle2.getText(); break; // 成功则退出重试 } catch (StaleObjectException e) { Log.w(TAG, "Retry due to stale object"); tittle2 = device.findObject(...); // 重新获取对象 retry++; } } ``` #### 4. **优化循环结构** ```java @Test public void videoNext() { for (int n = 0; n < 50; n++) { // ✅ 每次循环都重新获取初始状态 UiObject2 currentTitle = device.findObject( By.res("com.roadrover.video:id/path_name")); // ✅ 使用局部变量而非类成员 checkTitleVideo(currentTitle); } } ``` --- ### 三、完整修复示例 ```java @Test public void checkTitleVideo(UiObject2 initialTitle) { device.click(1000, 500); // 1. 动态等待界面稳定 device.wait(Until.gone(By.res("com.roadrover.video:id/progress_bar")), 5000); // 2. 使用新对象引用 UiObject2 nextBtn = device.wait(Until.findObject( By.res("com.roadrover.video:id/mediacontroller_next")), 3000); nextBtn.click(); // 3. 带重试机制的文本获取 String newText = null; for (int i = 0; i < 3; i++) { try { UiObject2 newTitle = device.wait(Until.findObject( By.res("com.roadrover.video:id/path_name")), 3000); newText = newTitle.getText(); break; } catch (StaleObjectException e) { SystemClock.sleep(1000); // 等待界面刷新 } } // 4. 验证逻辑 if (initialTitle.getText().equals(newText)) { Log.e(TAG, "Title not changed: " + newText); assert false; } } ``` --- ### 、预防措施 1. **对象生命周期管理** - 每次操作后重新获取对象引用 - 避免将 `UiObject2` 存储在成员变量中 2. **增强等待策略** [^4] ```java // 最佳实践:等待特定条件 device.wait(Until.hasObject(By .res("com.roadrover.video:id/path_name") .textStartsWith("Video")), 5000); ``` 3. **设备性能适配** ```java // 根据设备性能调整超时 long timeout = isLowEndDevice() ? 8000 : 3000; ``` 4. **使用 Espresso 替代 UIAutomator(如需复杂同步)** [^3][^4] ```java // Espresso 自动处理异步同步 onView(withId(R.id.path_name)) .check(matches(withText("Expected Title"))); ``` > 通过重新获取对象引用 + 动态等待 + 异常重试,可有效解决 `StaleObjectException`[^2][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值