- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
本文以 tool/DEVICE_UI_MAP.md 为核心,系统讲解 PureLive 仓库中基于 device_ui_map.json 的“无截图”Android UI 回归体系:坐标与语义双层缓存、按分辨率与方向自动选型、ADB 驱动点击/滑动/多步流程,以及配套的设备租约与冒烟脚本。读完本文,你将掌握android_ui.ps1的完整命令用法、device_ui_map.json的字段语义,以及如何在新增机型或布局调整后安全维护这套 UI 测试基线。
一、设计目标:无截图、可复现、防误点的 UI 回归
PureLive 的 Android UI 回归工作流建立在一个朴素但有效的观察上:UI 自动化最常见的失败不是点错坐标,而是把坐标发给了错误的应用。因此 tool/android_ui.ps1 刻意回避了截图 + 图像识别 + 持续集成重放的传统路线,改为:
- 持久化坐标:把已实测过、可稳定识别的控件坐标写入
device_ui_map.json,测试运行时直接复用; - 语义兜底:对每个坐标点尽量记录 Flutter/UIAutomator 暴露的语义标签(如“菜单”“设置”“原画”),必要时用语义树重新解析后再点击;
- 前台守卫:每次动作前通过
dumpsys activity activities读取topResumedActivity,确认com.mystyle.purelive确实在前台,否则直接中止,绝不把缓存坐标打到别的应用上。
这一设计带来的直接收益是:正常回归运行时不截屏、不跑图像识别,既避免了 UIAutomator 在弹幕持续刷新的直播间里等不到 idle 帧的问题,也把“失败才留证据”的成本降到最低。
二、持久化基线:device_ui_map.json 的顶层结构
device_ui_map.json目前为schemaVersion: 2,目标包名为com.mystyle.purelive,默认 profile 为k90pro_portrait_1200x2608。顶层只有两类成员:profiles(按机型/方向/分辨率维护的完整控件地图)与defaultProfile(降级缩放基准)。
每个 profile 内部固定包含四个分区:
| 分区 | 作用 | 数据形态 |
|---|---|---|
points | 可点击/可识别的 UI 控件点 | { x, y, label, semantic?, bounds? } |
gestures | 命名滑动手势 | { x1, y1, x2, y2, durationMs, label } |
sequences | 多步动作流程 | 由tap/tapSemantic/assertSemantic/swipe/wait五种原子动作组成的数组 |
screens | 屏幕快照目录(语义节点清单,非图片) | { recordedAt, appVersion, versionCode, nodes: [...] } |
以保留的 PJZ110(OnePlus 13)竖屏 profile 为例,首页平台栏的实测点形如:
"home.bilibili_tab": { "x": 430, "y": 480, "label": "首页平台栏:哔哩哔哩", "semantic": "哔哩哔哩", "bounds": [249, 384, 618, 576] }字段约定值得展开说明:
x/y:控件中心点的物理像素坐标,以该 profile 声明分辨率(如1440x3168)为基准记录;label:面向维护者的中文可读描述,如“首页状态:已开播”“设置向下滚动两次后:本地互动体验”;semantic:单个字符串或字符串数组。数组用于支持中英双语别名,例如设置页的"semantic": ["播放器内核…", "播放器内核"]、直播间音频开关的["切换到纯音频模式", "切换到视频模式"];bounds:可选项,记录该控件的[left, top, right, bottom]实际命中区域,供语义解析与面积排序使用。仅记录坐标而不带语义的点(如live.show_controls、nav.slot1~slot4)则只有x/y/label。
gestures的例子(PJZ110 竖屏)如下,包含完整的起终点与时长:
"settings.scroll_down": { "x1": 720, "y1": 2680, "x2": 720, "y2": 760, "durationMs": 420, "label": "设置列表向下滚动一屏" }sequences则由若干“一步一动作”的步骤组成,比如打开小窗弹幕设置页:
"open_pip_danmaku_settings": [ { "tapSemantic": ["菜单", "Menu"], "waitMs": 350 }, { "tapSemantic": ["设置", "Settings"], "waitMs": 600 }, { "tapSemantic": ["小窗弹幕", "PiP Danmaku"], "waitMs": 600 }, { "assertSemantic": ["样式预览", "Style Preview"] } ]注意最后一步是assertSemantic——只断言、不点击,用于确认到达了目标页面。
已录制的机型基线
当前主测机型为K90 Pro(序列号25102RKBEC,代号myron,Android 17),通过网络 ADB 接入,维护两套 profile:
- 竖屏
1200 x 2608(k90pro_portrait_1200x2608); - 横屏
2608 x 1200(k90pro_landscape_2608x1200)。
其竖屏 profile 的notes字段明确记载:K90 Pro 的首批坐标是从保留的 PJZ110 测量值按比例迁移而来,因此任何点的首次“状态改变性使用”之前,都必须先跑-Validate并优先-VerifySemantics,在对应路由被物理验证后重新记录屏幕快照——避免把缩放坐标当成实测证据。
仓库同时保留了 PJZ110(OnePlus 13)的实测 profile 作为归档回归基线:
- 竖屏
1440 x 3168:覆盖首页、开播/录播/未开播筛选、可见平台标签、房间卡片、侧边抽屉、完整设置列表、直播间顶栏、播放器控制、弹幕标签页、本地弹幕发送、底部导航与滚动手势; - 横屏
3168 x 1440:覆盖直播间顶栏、视频区域、音频/投屏/小窗(PiP)控制、清晰度/线路控制与完整右侧弹幕面板; - 稳定的屏幕目录:首页、抽屉、设置页顶部/中部/底部与直播间基础态。
一个重要的边界在文档中被反复强调:动态房间标题与实时弹幕文本不视为稳定控件,不应进入坐标地图。
三、运行前置条件:设备租约、唤醒与身份核对
由于同一部 Android 实机被 BiliRoaming、小红书(Xiaohongshu)与 PureLive 三个任务按固定biliroaming -> xhs -> purelive轮转共享,任何会改变实机状态的操作都必须先通过 tool/run_android_device_test_turn.ps1 获取purelivelane 的设备轮次(turn),具体规则见 docs/ANDROID_DEVICE_TEST_ROTATION.md。典型调用:
.\tool\run_android_device_test_turn.ps1 ` -CommandLine '.\tool\android_ui.ps1 -Validate'run_android_device_test_turn.ps1内部会:向仓库上级目录寻找共享协调器shared-device-test-rotation\Invoke-DeviceTestTurn.ps1;调用 tool/wake_android_device.ps1 执行唤醒与“常亮”租约(StayAwake),并在命令结束后只对成功获取常亮的目标恢复原始设置;随后把PURELIVE_ADB_SERIAL注入环境后执行测试正文。它支持-Pass(本轮无设备工作,直接交棒)、-NoRotation(跳过轮转协调但保留唤醒、前台校验与失败清理)与显式-Serial IP:PORT。
文档还强调了三条硬性边界:
- 同一无线设备同时暴露 mDNS 别名时,必须传精确的 IP 串号(如
192.168.1.2:5555); - 配对端口与配对码是临时的,严禁写入仓库;
- UI 回归工作流不要求 root,也绝不从设备侧管理器应用推断 root 能力。
拿到轮次之后,android_ui.ps1自身仍会执行强制的前台包守卫:先Wake-AndDismissKeyguard处理锁屏(K90 Pro 配置为十分钟自动锁屏,脚本会读取dumpsys window policy判断是否仍锁定,必要时按屏幕比例滑动解锁),再通过am start -W拉起应用并轮询观察topResumedActivity迁移(最多 12 次、每次 250 ms),而不是依赖固定延时——因为 Flutter 冷启动时 ActivityManager 可能先返回桌面。
四、命令速查:android_ui.ps1 全参数详解
android_ui.ps1使用 PowerShell 参数集(ParameterSet)区分动作,-Serial默认取$env:ANDROID_SERIAL。完整命令清单如下。
校验与罗列
# 校验 JSON 引用完整性,并列出当前 profile 的全部点/手势/序列/快照 .\tool\android_ui.ps1 -Validate .\tool\android_ui.ps1 -List-Validate对应脚本中的Test-Map:检查每个点的坐标是否越出 profile 边界、每个序列步骤是否恰好声明一个动作、tap引用的点与swipe引用的手势是否存在、语义动作是否为空、wait动作是否携带合法waitMs,最后再整体ConvertFrom-Json复验可解析性。
预览或执行缓存动作流程
.\tool\android_ui.ps1 -Sequence toggle_audio -DryRun .\tool\android_ui.ps1 -Sequence toggle_audio # 从首页直达常测设置页 .\tool\android_ui.ps1 -Sequence open_pip_danmaku_settings .\tool\android_ui.ps1 -Sequence open_local_interaction_settings .\tool\android_ui.ps1 -Sequence open_general_settings-DryRun只打印将要执行的每一步(含坐标与来源),不真正下发 ADB 命令;去掉后按序执行tap/tapSemantic/assertSemantic/swipe/wait五种动作,每一步的waitMs在动作之后生效。
语义解析、复核与坐标维护
# 从无障碍语义树解析一个可见控件并点击(不截屏) .\tool\android_ui.ps1 -TapSemantic '关闭菜单' # 点击前先用当前语义树复核缓存坐标 .\tool\android_ui.ps1 -Tap live.audio_toggle -VerifySemantics # 记录/修正一个坐标点,后续测试直接复用 .\tool\android_ui.ps1 -Record settings.example -X 1200 -Y 900 ` -Label '设置:示例入口' # 将当前路由上所有稳定可操作控件存入屏幕目录 .\tool\android_ui.ps1 -Snapshot settings.example .\tool\android_ui.ps1 -RemoveSnapshot settings.example # 仅在请求动作失败时保存截图与 XML 证据 .\tool\android_ui.ps1 -Sequence toggle_audio -CaptureOnFailure参数细节:
-TapSemantic:只走语义树,命中Find-SemanticNode后点击节点中心;候选可传多个(脚本用Format-SemanticCandidates输出'A' or 'B'),找不到即报错。-VerifySemantics:仅对tap生效——先Get-UiNodes拉语义树,若能按该点的semantic候选命中节点,则使用节点中心坐标(来源标记为semantic),否则回退到缓存坐标(来源cache)。-Record:按“当前实测分辨率 → profile 基准分辨率”的比例把(X, Y)换算为基准坐标后写入对应 profile 的points;坐标换算逻辑见脚本Record分支。-Snapshot:调用 UIAutomator 拉取当前 UI 层级(uiautomator dump --compressed),把**所有有身份标识(text/描述/resource-id 任一非空)且可操作(clickable/long-clickable/scrollable/checkable)**的节点连同 bounds、center、状态位写入该 profile 的screens;默认跳过***:形式的密码语义。快照同时记录appVersion、versionCode、orientation与topPackage,用于追溯。-CaptureOnFailure:动作抛错时,Save-FailureEvidence会在.dart_tool\device-ui-failures下按yyyyMMdd-HHmmss-<动作>.png/.xml保存screencap与 UIAutomator XML。
自动隐藏控件与“两步点击”快速路径
播放器控制栏这类自动隐藏控件在缓存地图中采用两步流程:先点live.show_controls唤出控制层,等待 700 ms,再点目标控件。toggle_audio序列正是如此:
"toggle_audio": [ { "wait": true, "waitMs": 2600 }, { "tap": "live.show_controls", "waitMs": 300 }, { "tap": "live.audio_toggle", "waitMs": 0 } ]文档特别说明:这条快速路径刻意跳过 UI 层级 dump,因为层级转储可能比自动隐藏的控制层存活时间还长,反而引入不稳定。
五、竖屏全屏手势:v3.0.22 的两个命名序列
PureLive v3.0.22 引入竖屏直播全屏展示后,UI 地图补充了两个可复用的手势序列。它们只能在已验证坐标的设备上、进入稳定识别的竖屏直播间后使用:
.\tool\android_ui.ps1 -Sequence enter_portrait_fullscreen .\tool\android_ui.ps1 -Sequence restore_portrait_panelenter_portrait_fullscreen:从竖屏弹幕面板的手柄区(live.portrait_sheet_handle,K90 Pro 竖屏坐标为(600, 1646),语义标签“下滑进入竖屏全屏”)向下滑动进入沉浸展示;restore_portrait_panel:从屏幕底部(K90 Pro 竖屏为live.portrait_fullscreen_restore_zone,即(600, 2527))向上滑动恢复弹幕栏。
在device_ui_map.json中它们对应的手势定义(以 K90 Pro 竖屏 profile 为准)为:
"live.portrait_fullscreen_down": { "x1": 600, "y1": 1646, "x2": 600, "y2": 2511, "durationMs": 650, "label": "竖屏直播弹幕面板下滑进入竖屏全屏" }, "live.portrait_fullscreen_up": { "x1": 600, "y1": 2438, "x2": 600, "y2": 1904, "durationMs": 420, "label": "竖屏全屏提示条上滑恢复弹幕栏(避开系统底部手势区)" }两个序列本身“只记录确定坐标与等待时间”。文档明确划定了边界:直播源是否为稳定竖屏仍由 PureLive 应用自身状态机判定,脚本不会强制普通直播进入该模式。
六、深层原理:坐标选型、缩放与语义解析
自动选型与降级缩放
Resolve-Profile的优先级是:显式-Profile参数 → 按当前dumpsys window displays或wm size读取的实际分辨率 + 方向精确匹配 → 回退到defaultProfile并按比例缩放。回退前会先检查默认 profile 的方向是否与当前一致,方向不一致时直接报错,绝不盲目套用——这对应维护规则第 1 条“竖屏坐标不得盲目搬到横屏”。
缩放公式为(见Resolve-Point):
x = round(point.x / profile.width * 当前屏宽) y = round(point.y / profile.height * 当前屏高)swipe手势的起终点同样按此比例换算,durationMs缺省时默认 350 ms。
语义树解析的工程细节
Get-UiNodes有若干值得借鉴的实现要点:
- 使用
shell timeout 10 uiautomator dump --compressed限定转储时间上限——直播间弹幕持续变化,UIAutomator 可能永远等不到 idle 帧,Android 内置超时防止整个回归被卡死; - 转储文件通过
adb pull以字节流方式读取,并显式按 UTF-8 解码——因为 Windows PowerShell 5.1 若用adb shell cat捕获输出,会按活动 OEM 代码页解码,导致中文语义标签损坏; Find-SemanticNode按候选语义逐条匹配节点的content-desc或text,支持"描述\n..."前缀形式,并按“可点击优先、命中面积小者优先”排序取第一个。
语义断言防止“假成功”
对路由敏感的序列,文档要求使用双语tapSemantic候选 + 末尾assertSemantic。assertSemantic读取目标页的无障碍树,若找不到预期的“仅该页面存在”的标签(例如打开播放器内核设置后断言“核心内核设置 / Core Kernel Settings”,打开小窗弹幕设置后断言“样式预览 / Style Preview”),序列就以错误终止——从而保证过期的坐标或未变化的屏幕永远不会被报告为一次成功的路由。
播放器状态的验证边界
文档维护规则第 5 条是一条重要纪律:每次动作后要通过应用日志/语义树验证播放器状态,一次成功的点击本身不等于一次成功的播放结果。坐标地图只管“点到”,播放是否生效由专门的冒烟脚本(见下文)负责闭环验证。
七、配套冒烟脚本:PiP 弹幕设置验证示例
android_ui.ps1是底层驱动,上层业务验证由各冒烟脚本组合完成。文档给出的示例是在已获取的设备轮次内,验证 PiP 预览的开启/关闭与持久化:
.\tool\android_pip_danmaku_settings_smoke.ps1 ` -Serial '192.168.1.2:5555' ` -EvidenceDirectory 'local-artifacts/diagnostics/pip-danmaku-settings'从源码看,tool/android_pip_danmaku_settings_smoke.ps1 的验证纪律包括:
- 必传
-Serial,且先校验目标身份——getprop ro.product.model必须等于25102RKBEC、ro.product.device必须等于myron,身份不符直接中止(见Get-Identity); - 证据目录统一落在
local-artifacts/diagnostics/<场景名>; - 场景开始/结束时都会
uiautomator dump与screencap双通道采集 UI 状态,并在finally中清理设备侧临时文件; - 文档进一步说明:该脚本会在验证后恢复精确的规范 Hive 文件并停止应用,确保测试不污染后续轮次。
八、维护规则与回归纪律
DEVICE_UI_MAP.md的维护规则是整条工作流的“宪法”,逐条落地如下:
- 竖屏/横屏 profile 严格分离,绝不盲目旋转竖屏坐标——选型与缩放逻辑已在第六节说明;
- Flutter 暴露语义标签时就记录,但保留实测坐标用于快速执行——坐标是主路径,语义是兜底与复核,二者缺一不可;
- 滚动相关点位加
_after_scroll2之类的后缀,并把所需滑动编码进命名序列——例如settings.network_proxy_after_scroll1、settings.general_after_scroll2、settings.backup_after_scroll2,对应序列open_local_interaction_settings连续两次settings.scroll_down后再tapSemantic "本地互动体验"; - 布局变更后刷新受影响的屏幕快照,实机回归前先跑
-Validate; - 每次动作后通过应用日志/语义验证播放器状态,成功点击不等于播放结果;
- 导航序列在存在稳定标签时以目标页专属
assertSemantic收尾,并为本地化路由提供中英双语别名。
结合源码还可以补充一条工具链纪律:-Record/-Snapshot写回device_ui_map.json时,Save-Map会先ConvertTo-Json -Compress,再用脚本内自实现的Format-JsonText重排为缩进 JSON,并以无 BOM 的 UTF-8落盘——同样是为了避开 Windows PowerShell 5.1 对无 BOM UTF-8 文件的 ANSI 误读问题(快照节点过滤表达式中也保留了“仅 ASCII”的注释约束)。
九、从零接入一台新机型的操作路径
结合上文全部机制,将一台新 Android 机型接入这套回归体系的标准流程是:
- 接入并核对身份:网络 ADB 连接后,显式指定
-Serial IP:PORT,先用只读命令核对ro.product.model/ro.product.device,确认不是按设备列表顺序猜测目标; - 获取设备轮次:通过 tool/run_android_device_test_turn.ps1 进入
purelivelane; - 建立 profile 骨架:在
device_ui_map.json的profiles中新增竖屏与横屏两个 profile,声明分辨率与方向; - 迁移或实测坐标:可参考既有机型按比例迁移首批坐标,但必须随后逐点用
-VerifySemantics/-TapSemantic物理复核;状态改变性动作首次使用前先-Validate; - 用
-Record修正偏差、用-Snapshot记录屏幕目录,把复核过的点固化为缓存基线; - 验证关键序列:依次跑
open_settings、open_pip_danmaku_settings、toggle_audio、enter_portrait_fullscreen/restore_portrait_panel(竖屏直播场景)等命名序列,确认带assertSemantic的序列能在目标页正确收尾; - 纳入轮转:在正常轮次中以
-Validate开头、以带-CaptureOnFailure的业务序列结束,将新机型正式纳入每日回归。
整个过程从缓存坐标、语义兜底、前台守卫到失败取证都无需截图与图像识别,这正是 PureLive 这套“UI 地图 + ADB 驱动”方案在弹幕密集型直播应用中保持稳定与快速的关键所在。
十、相关资源索引
- 入口文档:tool/DEVICE_UI_MAP.md
- 核心驱动脚本:tool/android_ui.ps1
- UI 坐标/手势/序列/快照数据库:tool/device_ui_map.json
- 设备轮次包装器:tool/run_android_device_test_turn.ps1 与共享实机轮转规则 docs/ANDROID_DEVICE_TEST_ROTATION.md
- 设备唤醒/常亮租约:tool/wake_android_device.ps1
- 上层业务冒烟示例:tool/android_pip_danmaku_settings_smoke.ps1
- 实测回归基线文档:docs/ANDROID_DEVICE_REGRESSION_2026_08_28.md、docs/ANDROID_RUNTIME_AUDIT_3_1_8_K90PRO.md
- 音视频
- 直播
- 移动开发
【免费下载链接】pure_live
纯粹直播:哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/YY直播/Twitch直播/SOOP直播/M38自定义源应有尽有。
相关推荐
Folcolor编译指南:使用Visual Studio 2022构建完整项目
Folcolor编译指南:使用Visual Studio 2022构建完整项目 Folcolor 是一款出色的Windows文件夹着色工具,它能帮助您通过颜色快
桌面应用开发工具Front-End-Checklist 视觉回归测试实战:用 Playwright 截图对比拦截 CSS 回归
Front End Checklist 视觉回归测试实战:用 Playwright 截图对比拦截 CSS 回归 视觉回归测试(Visual Regression
fastlane frame_screenshots(frameit)实战指南:用 Framefile.json 为 iOS/macOS/Android 截图批量套上设备边框
fastlane frame_screenshots frameit 实战指南:用 Framefile.json 为 iOS/macOS/Android 截图批
开发工具CI/CD移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考