ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

UI Automator Viewer实战:解析微信支付宝界面层级与自动化定位

UI Automator Viewer实战:解析微信支付宝界面层级与自动化定位

我最早接触到UI Automator Viewer这个工具,其实是踩了Layout Inspector的坑以后才认真去研究的。当时某公司让我们做一款 App 的 UI 自动化测试,界面元素定位始终搞不定,一直用的 Android Studio 里的 Layout Inspector 要么连不上,要么对第三方应用显示不出来。后来换用 UI Automator Viewer 之后,问题才真正解决:它不依赖应用是否可调试,只要能跑起来,就能抓取界面层级。

这篇博文就把我在实际工作中用 UI Automator Viewer 分析微信、支付宝这些第三方 App 布局的经验完整整理一遍,包括环境准备、完整实操、属性解析、高频报错,以及如何把抓取结果接到自动化测试脚本里。适合正在做 UI 自动化测试的新人、需要做竞品界面分析的开发者,以及想了解 Android 无障碍视图机制的读者。

1. 为什么放着 Layout Inspector 不用,偏要去折腾 UI Automator Viewer

先聊清楚一个问题:Android Studio 自带的 Layout Inspector 明明界面更漂亮、层级更直观,为什么还要用 UI Automator Viewer 这个看起来老掉牙的工具。

1.1 Layout Inspector 的先天局限

Layout Inspector 是 Android Studio 开发环境下用的调试工具,它底层依赖的是应用进程内部的视图树信息。也就是说,它只能查看 Debuggable 的应用。你自己开发的项目,或者通过 Debug 模式启动的应用,可以正常查看。但换成市场里下载的微信、支付宝这种 release 包,Layout Inspector 几乎什么都拿不到。

我实测下来,Layout Inspector 对非 Debuggable 应用的表现通常有两种:

  • 连接不上目标进程,提示类似 “The layout inspector could not be attached”;
  • 能打开一个空白的界面,看不到任何 View 节点。

换句话说,你没法在 Android Studio 里用 Layout Inspector 去分析第三方 App 的界面。做竞品调研、无障碍适配、UI 自动化脚本开发的时候,这条路直接堵死了。

1.2 UI Automator Viewer 的替代优势

UI Automator Viewer 是 Android SDK 里自带的工具,它的底层机制和 Layout Inspector 完全不同。它通过 Android 系统的 accessibility 服务能力去获取当前窗口的界面层级,相当于系统从无障碍通道打包了一份界面信息给你。

它的几个突出优势:

  • 不要求应用可调试,普通安装的 release 包也能抓取界面层级;
  • 不需要额外装什么插件,SDK 里拿出来就能用;
  • 能直接看到 View 的resource-id、text、content-desc、bounds等核心属性,这些正是写 UI 自动化测试脚本时最需要的定位信息;
  • 轻量,比 Layout Inspector 省内存,列举上百个节点的界面时依然流畅。

也因为走的是无障碍通道,很多手机厂商在系统优化时会限制部分后台应用的 accessibility 数据,个别情况下需要开启特定的“USB调试(安全设置)”选项才能读取完整 UI,这点在后面常见问题里我会细说。

2. 开工前的环境准备,这些坑提前避掉

UI Automator Viewer 的使用流程本身不复杂,但环境不出问题,操作就能减少一大半干扰。

2.1 装好 Android SDK 和 platform-tools

新版本的 Android Studio 里,很多老工具默认不再打包到 IDE 目录里了,所以你有可能遇到一个尴尬情况:命令行敲uiautomatorviewer提示找不到命令。我的解决方法是单独下载 Android SDK 命令行工具,或者直接找旧版 SDK 里的 tools 目录。

如果你用的是完整的 SDK 目录,uiautomatorviewer的路径如下:

  • Windows:sdk\tools\bin\uiautomatorviewer.bat
  • macOS/Linux:sdk/tools/bin/uiautomatorviewer

正常情况下,先配置好ANDROID_HOME环境变量,然后在命令行里直接执行启动脚本就行。如果启动时报 Java 相关错误,说明 JDK 环境没配置好,或者 JDK 版本和工具不兼容。我通常用的 JDK 8 或 JDK 11 都能正常跑。

2.2 连接设备的关键检查项

工具本身是连接 ADB 设备来截图的,所以设备连不上,后面一切都白搭。

在命令行里先用adb devices确认设备状态。正常会看到类似:

List of devices attached <序列号> device

如果显示unauthorized,那就去真机上弹窗点“允许 USB 调试”。如果压根没显示设备,检查数据线是否支持数据传输(市面上很多线只能充电),或者换一个 USB 口。

多台设备连接时,需要在工具里选择设备。或者在启动前用adb -s 设备序列号操作对应设备。真机调试时还有个容易忽略的细节:部分国产 ROM 出于隐私保护,默认会在连接电脑调试时屏蔽 UI 无障碍信息抓取,导致 uiautomatorviewer 截出来是黑屏或者只剩状态栏。这种情况下,需要到开发者选项里开启类似“USB调试(安全设置)”的开关,不同品牌的名称可能不太一样,核心意思是允许通过 USB 模拟点击和读取 UI 信息。

模拟器用户也要注意,uiautomatorviewer 对模拟器同样是走 ADB,所以只要adb devices能识别,基本能用。但一些虚拟机里跑的游戏或者特殊图形引擎应用,界面可能不是传统 View 结构,抓出来的数据量也很小,这点后面会单独讲。

3. 手把手实操:UI Automator Viewer 完整使用流程

环境准备好以后,后面这一步一步的流程就是日常用的核心了。

3.1 启动工具和截取界面

启动 uiautomatorviewer 后,界面是一个很复古的 Java Swing 窗口,左边是手机截图,右边是节点树,上方一排小图标。

我习惯的操作流程是:

  1. 先把 App 切到想分析的页面;
  2. 打开 uiautomatorviewer;
  3. 点击左上角那个类似“设备截图”的图标(一个手机加绿色刷新箭头的图标);
  4. 等待 1~3 秒,工具会自动通过 ADB 触发uiautomator dump并抓取当前窗口层级;
  5. 左侧出现截图,右侧出现完整的 View 节点树。

这里要提醒一个高频菜鸟错误:直接打开工具就截图,但手机屏幕是熄灭的,或者停留在锁屏界面。这种情况下抓到的层级就是锁屏页,不是你想分析的页面。正确做法是先让手机保持亮屏、停留在目标页面,再点截图按钮。

3.2 从节点树里读懂界面结构

抓取成功后,右侧的节点树会显示整个窗口的 View 层级。默认是一棵从上到下的树,根节点通常是android.widget.FrameLayout,下面一层一层展开,包含各种LinearLayout、RelativeLayout、RecyclerView、TextView、ImageView之类的节点。

点击树里的任意节点,左侧截图里对应的区域会高亮显示。这个功能非常实用,尤其是你想知道界面上某个按钮、某个输入框到底对应哪个 View 节点时,挨个点击就能对应起来。

如果节点树很深、很多,可以直接在左侧截图里点击元素,然后右侧会自动选中对应的树节点,操作效率更高。

3.3 关键属性的含义与取值技巧

选中一个节点后,界面下方面板会显示它的属性列表。日常用到最多的是这几个:

属性含义定位价值
text控件显示的文本内容用文本定位元素最直观
resource-id布局里定义的资源 ID自动化测试中最稳定的定位方式
classView 的具体类型判断控件类型很有用
package所属应用包名确认元素归属
content-desc无障碍内容描述对于没有文本的图标按钮很关键
bounds控件在屏幕上的坐标范围手写坐标点击、区域校验必备

举例来说,界面上一个“登录”按钮,它的属性常常长这样:

text: "登录" resource-id: "com.example.app:id/btn_login" class: "android.widget.Button" bounds: [108,1836][972,1980]

bounds的格式是[左,上][右,下],中心点坐标 X = (108+972) / 2 = 540,Y = (1836+1980) / 2 = 1908。这个中心点可以直接用于 Appium 的坐标点击或者无障碍服务的注入点击。

4. 实战场景:分析微信好友列表和支付宝首页 Tab

理论知识讲多了没意思,拿真实界面走一遍流程才有感觉。

4.1 分析微信某界面布局实录

以微信通讯录界面为例。先用手机打开到“通讯录”页,点 uiautomatorviewer 里的截图按钮。抓取完成后,右侧节点树会先出现一个整体的扁平层级,然后在中间部分看到 RecyclerView 相关的节点。

再看节点属性。你会发现通讯录界面上每个好友的条目通常长这样:

  • 外层是一个android.widget.RelativeLayout,bounds 覆盖整个列表项的高度;
  • 里面有一个android.widget.TextView,text 就是好友昵称;
  • 旁边可能有一个android.widget.ImageView,content-desc 可能为空,因为它只是头像图片。

这里有个非常容易被忽略的坑:微信这种大厂应用,很多控件的resource-id是空的。resource-id只显示package名加一个斜杠,或者直接空着。所以你不能指望靠resource-id去精确定位微信里的元素,反而是text属性比较可靠。要拿“朋友圈”入口这种非文本区域时,就得靠content-desc或者镜像坐标了。

微信页面里动态加载的会话列表尤其有意思。你切换聊天记录、翻动列表时,底层的 View 复用非常明显,dump 出来的树结构和界面上看到的数量不完全对应。这是 RecyclerView 的复用机制导致的,不是工具抓取出了 bug。分析这种场景时,要把注意力放在“可复用的 item 模板”上,而不是单个具体数据条目。

4.2 分析支付宝首页 Tab 实录

再看支付宝首页。首页结构相对稳定,底部那几个 Tab 是典型的定位目标。

抓取首页后,点选底部 Tab 里任意一项,属性面板里一般能看到:

text: "首页" class: "android.widget.TextView" bounds: [0, 2130][270, 2280]

如果你是在写自动化用例,想点击“理财”Tab,直接用 text 来匹配就足够了。但需要注意,如果页面同时出现两个 text 完全相同的控件,直接按 text 定位会命中多个元素。这时候要么用bounds过滤,要么先定位父节点再往下找子节点。

支付宝首页还有一个很典型的情况:顶部搜索框并不是原生 EditText,很多版本里是个假搜索框(可能是 TextView 加背景样式模拟的)。你抓取下来发现class是android.widget.TextView,text却是空或者一串“搜索”提示文案。这种情况千万别硬按 EditText 去定位,按实际 class 来反而轻松。

再说一个特殊细节:支付宝很多常用的页面是原生 View 和 WebView 混排的,比如活动运营页。uiautomatorviewer 在 dump 时会把 WebView 整体当作一个android.webkit.WebView节点,内部的网页 DOM 结构它是看不到的。想分析 WebView 内部的元素,需要另开chrome inspect或者用它自带的 chromedriver 调试机制,这一步不在本文范围,但你知道这个边界能少走很多弯路。

5. 高频报错与排查方法(踩坑合集)

用这个工具快两年了,遇到过不少报错,挑几个典型的说说。

5.1 连不上设备、截图一直转圈

最常见的错误是点击截图按钮后,左侧一直显示加载状态,等多久都不出图。这种问题八成不在工具本身,而在 ADB 连接状态。

我的排查路径是:

  1. 看命令行里adb devices是否能识别设备;
  2. 如果识别了但状态是unauthorized,重插数据线,在手机上重新允许 USB 调试;
  3. 如果状态是device但依然白屏,执行adb kill-server和adb start-server,然后重启 uiautomatorviewer 再试。

还有一种场景:电脑上装了多个平台工具版本,某个模拟器自带的 ADB 版本和 SDK 里的不一致,也会导致连接不上。统一用同一个platform-tools目录下的adb能解决大部分问题。

5.2 节点树乱码、卡顿和界面显示不全

有时候抓取成功,但节点树里的中文文本显示成乱码。这是 Java 工具的编码问题,Windows 环境下尤其容易出现。可以在系统环境变量里加JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF-8,然后重启工具,基本能解决。

界面节点特别多的时候,右侧树会卡顿。微信的朋友圈、淘宝的首页这种重量级页面,一次能抓几百个节点,展开全树卡到不行。我的经验是:先用左侧截图点击目标区域,让它自动定位到对应节点,然后只展开那一小段分支,比在树里一点一点找高效太多。

还有一种情况是 dump 不完全。比如某些页面里有弹窗或者正在播放的视频区域,dump 结果里可能只有部分节点,另外一部分节点动态渲染还没完成。处理方式是多截几次图,等页面完全静止后再抓。

5.3 特殊应用(Flutter/WebView/游戏)的应对思路

很多新 App 是 Flutter 或 React Native 开发的,它们的 UI 不是传统 View 体系。uiautomatorviewer 抓 Flutter 应用时,节点树往往非常“贫瘠”——可能只有一个FlutterView节点,下面没有任何子节点。

这不代表工具废了,而是 Flutter 的自绘机制摆在那里,系统无障碍层面拿不到控件级信息。这种情况下,有几个替代方案:

  • 用 Flutter 官方的无障碍协议,或者专门的 Flutter 测试驱动引擎;
  • 用 Appium 的 Flutter Driver 扩展;
  • 直接退回坐标点击方案,从 UI 布局截图里人工确认按钮位置,用adb shell input tap x y去交互。

WebView 混排也是高频场景。uiautomatorviewer 只能看到 WebView 节点,看不到网页内部元素。自动化测试里建议采用混合上下文切换,在 WebView 上下文中用前端定位方式来操作。

游戏类和自定义绘制类应用更直接,本质上整个屏幕就是一个大 View,uiautomatorviewer 拿到的可能只有 SurfaceView 或 TextureView 节点。做这类应用的 UI 测试,基本都是坐标模拟点击加图像识别,没有任何捷径。

6. 进阶玩法:把 UI dump 和自动化测试串起来

只会用鼠标点截图、看节点树,那还是停留在入门阶段。真正高效的做法是把 uiautomatorviewer 的结果拿到脚本里去用。

6.1 命令行 dump UI XML

uiautomatorviewer 图形界面背后,其实就是在设备上执行了uiautomator dump然后解析 XML。你完全可以在命令行里手动完成这件事:

adb shell uiautomator dump /sdcard/window_dump.xml adb pull /sdcard/window_dump.xml ./window_dump.xml

Windows 下也可以用adb exec-out uiautomator dump /dev/tty > dump.xml,但有些低版本设备不支持这个写法,还是走 sdcard 中转兼容性最好。

拿到 XML 后,你可以用 Python 的xml.etree.ElementTree解析所有节点,批量提取 text、resource-id、bounds。比如写一个脚本把所有按钮的中心点打印出来,生成一个页面元素清单。这对于做多页面自动化用例、维护 UI 元素档案特别有用。

6.2 和 Appium、Airtest 等框架的联动思路

uiautomatorviewer 抓出来的元素信息,和 Appium 等框架使用的定位策略是通用的。

  • Appium 里mobile: inspect本质上触达的信息源也是 accessibility 节点,所以你在 uiautomatorviewer 里看到resource-id、text的那套逻辑,搬到 Appium 的findElementById、findElementByText上完全成立。
  • 如果拿到的resource-id为空,直接改用 XPath,比如//android.widget.TextView[@text='登录']。
  • 配合图像识别做兜底:坐标定位 +adb shell input tap,能实现一种轻量级的“无依赖自动化”。

我个人的建议是:别把 uiautomatorviewer 当成唯一的分析工具,它更适合做前期的“侦察兵”。真正跑自动化回归时,还是要用能持续集成的框架,uiautomatorviewer 提供的是定位素材,不是执行引擎。

6.3 一个实用脚本示例:批量提取页面按钮坐标

这里给一个简单的 Python 脚本,解析 uiautomator dump 出的 XML,筛选出所有可点击节点并输出中心坐标:

import xml.etree.ElementTree as ET def center_from_bounds(bounds_str): # bounds 格式如 [108,1836][972,1980] parts = bounds_str.replace('[', '').replace(']', ',').split(',') left, top, right, bottom = map(int, parts) return (left + right) // 2, (top + bottom) // 2 tree = ET.parse('window_dump.xml') for node in tree.iter('node'): if node.get('clickable') == 'true': bounds = node.get('bounds') text = node.get('text') rid = node.get('resource-id') cx, cy = center_from_bounds(bounds) print(f'text={text} | id={rid} | 中心点=({cx},{cy})')

这个脚本在我维护页面元素清单的时候很有用,几分钟就能把一整个页面的可点击元素全列出来。

7. 一点个人使用的经验总结

回到开头那个问题,为什么我还是愿意用 uiautomatorviewer 去分析微信、支付宝这类应用的 UI 布局?表面上是 Layout Inspector 不能用,更深层的原因是:uiautomatorviewer 基于无障碍机制的渲染方式,决定了它在任何普通第三方应用面前都稳定可用。它的界面虽然老,但该给的信息一个不少,而且导出 XML 的兼容性很好,方便我做后续自动化处理。

工具用得越久,我越觉得它更适合当“地基”。你想分析任意 App 的界面,用它;你想快速确认某个元素有没有resource-id,用它;你想在一堆节点里找一个控件的精确坐标,用它。等把这些基础信息摸清楚了,后续上 Appium、Airtest、或者自研的平台,心里都会很有底。

最后分享一个很实用的小技巧:分析某个 App 的时候,如果发现 uiautomatorviewer 抓出来的截图和真实界面有偏差,尤其是深色模式或者全屏页面,可以先在手机上把界面切到浅色模式,再抓一次。部分控件在深色背景下的文字对比度会影响视觉识别,但深色模式下的树结构和浅色模式下是基本一致的,浅色模式下更容易看清节点边界。抓完再切回深色模式,不影响定位结果。

如果你现在正被 Layout Inspector 的局限卡住,又拿微信、支付宝这类应用没辙,直接试试这条老路子,绝对会有豁然开朗的感觉。用熟了这套工具链,以后不管遇到什么 App 的界面分析需求,你都不会再慌。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进