Files
cmautobuy/docs/task/110-持久设备工作线程复用uiautomator2会话.md
T
2026-08-10 18:01:50 +08:00

100 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 110 使用持久设备工作线程跨任务复用 uiautomator2 会话
- 类型:重构
- 父级大工单:#1
- 所属 MVP / 版本:MVP 后续 / 任务执行性能优化
- 状态:已完成(待验收)
- 日期:2026-08-10
- Gitea 工单:<http://ilaer.eicp.net:8418/chengma/cmautobuy/issues/110>
## 背景与目标
Client 原来每轮自动获取都会新建 Worker 和 QThread,正式采集、采购和核单适配器
也会重新调用 `uiautomator2.connect()`。目标是让同一 Android 设备由固定工作线程
串行控制,并在 90 秒内只复用 Device 连接;页面、控件树、商品、价格、规格和采购
安全状态仍必须每个任务重新读取。
#109 测得连接本身只占端到端约 1.4%,原结论是 No-Go。用户随后明确要求继续实施
#110,因此工单已记录该决策变更;性能门槛保留为结果评价和回退依据,不再作为
开始实施的阻塞条件。
## 最终方案
- `PDDTaskPageEvent` 在首次设备任务时懒创建一个固定 QThread 和长生命周期
`ClaimTaskWorker`。自动获取、手动重新采集、采购和不可逆阶段后的只读核单都通过
Qt 队列信号串行进入该 Worker;不在 Worker 内运行无限循环。
- `PersistentPddDeviceService` 只缓存同一 serial 的 Device 连接。每次 `connect()`
都重新调用 `app_current()` 做轻量健康检查,并返回全新的任务会话。旧连接失效时
丢弃后最多重连一次,不缓存 XML、Selector、坐标或业务字段。
- 官方采集、dry-run 采购、live 采购和核单工厂从当前执行上下文取得同一个持久设备
服务;自定义测试工厂和原有每任务服务边界仍可使用,方便整体回退。
- 停止自动获取、保存或删除 Android 设备、serial 变化、90 秒空闲到期和窗口关闭时,
都把释放命令排入设备线程。窗口关闭先协作停止当前命令,再释放并执行
`quit()/wait()`,不使用 `terminate()`。
- 设置页保存/删除设备后发出稳定信号,由主窗口装配到任务页的释放入口。
- 批量重新上报继续使用原来的独立网络 Worker,不访问 Android 设备。
- 未修改数据库、Admin 接口、任务状态或 `pdd_data`;真实下单默认开关和不可逆阶段
只核单的硬边界保持不变。
与建单方案的差异:没有新增单独的执行器文件,而是在现有
`ClaimTaskWorker/PDDTaskPageEvent` 边界内改成长生命周期实现,减少初级程序员需要追踪
的类和文件。#109 已证明连接复用无法达到原性能目标,因此本次只确认连接复用和
线程安全成立,不把端到端性能项写成通过。
## 改了哪些
- `client/src/pdd_device_service.py`:持久连接、TTL、每任务健康检查、单次重连和线程归属。
- `client/src/pdd_ui_event.py`:固定 QThread、串行命令路由、停止/释放和窗口关闭流程。
- `client/src/collect_task_service.py`:正式采集使用当前设备线程绑定的连接服务。
- `client/src/pdd_u2_purchase_adapter.py`:dry-run/live 采购使用同一线程内连接服务。
- `client/src/pdd_u2_purchase_reconcile_adapter.py`:不可逆阶段核单使用同一线程内连接服务。
- `client/src/settings_ui_event.py`、`client/src/ui_main.py`:设备配置变化后通知任务页释放旧连接。
- `client/test/test_pdd_device_service.py`:连接复用、TTL、serial 变化、单次重连和跨线程拒绝测试。
- `client/test/test_pdd_ui_event.py`:连续自动获取复用同一固定 QThread 测试。
- `client/test/test_settings_ui_event.py`:设备配置变化信号测试。
- `docs/client/02-architecture.md`:记录持久设备线程、90 秒 TTL 和安全释放规则。
## 验收结果
| 验收标准 | 结果 |
|---|---|
| #109 基线和用户覆盖原 No-Go 门槛的决定已记录 | 通过 |
| 同一 serial 在 90 秒内连续任务只调用一次 `uiautomator2.connect()` | 通过,单元测试及真机只读 5 次验证 |
| Device 的创建、调用和释放固定在同一线程 | 通过 |
| 自动获取、手动重试、采购和核单不会并发控制同一设备 | 通过 |
| 每个任务重新读取应用状态,不复用页面和业务数据 | 通过 |
| TTL、停止、设备配置变化、serial 变化和窗口关闭安全释放 | 通过 |
| 缓存连接健康检查失败后最多重连一次 | 通过 |
| 不可逆阶段后只允许核单,不重新下单 | 通过,既有采购恢复测试覆盖 |
| 热启动阶段中位耗时降低 30% 或 1.5 秒 | 未通过;主要耗时仍是每任务必须执行的 `app_current()` |
| 保留可回退边界,未改变数据库、Admin 和任务数据 | 通过 |
| Client 全量测试、语法和离屏冒烟通过 | 通过 |
| 真实下单仍默认关闭,未执行下单或付款 | 通过 |
## 测试
- 执行的命令:
- `C:/Python310/python.exe -m py_compile src/collect_task_service.py src/pdd_device_service.py src/pdd_u2_purchase_adapter.py src/pdd_u2_purchase_reconcile_adapter.py src/pdd_ui_event.py src/settings_ui_event.py src/ui_main.py`
- `C:/Python310/python.exe -m unittest discover -s test -p "test_*.py"`
- `$env:QT_QPA_PLATFORM="offscreen"; C:/Python310/python.exe -c "from src.ui_main import MainWindow; from PyQt5.QtWidgets import QApplication; app=QApplication([]); w=MainWindow(); print('OK'); w.pddTaskPageEvent.shutdown(); w.settingsPage.eventBinder.shutdown(); app.quit()"`
- `adb devices`
- 真机只读脚本:同一 Wi-Fi serial 连续创建 5 个 `PersistentPddDeviceService` 任务会话,只读取 `app_current()`。
- `git diff --check`
- 结果:Client 全量 316 项通过;语法、差异检查和离屏主窗口冒烟通过;ADB 设备
`192.168.0.173:5555` 在线;5 个真机会话只有 1 次底层连接,线程 ID 始终为
22788。5 次会话健康检查耗时为 11730.6、10756.5、10589.1、10738.6、
10826.3 ms,设备均报告当前包名 `com.android.settings`。
- **没验证到的部分**:没有领取真实 Admin 任务连续执行完整采集或采购;没有执行
真实下单、创建订单或付款;没有在真机等待 90 秒观察 QTimer 到期,而是由单元测试
覆盖 TTL;没有在真机主动制造断连、验证码、风控、登录失效或不可逆阶段崩溃。
## 遗留问题
连接复用已经成立,但没有达到原性能验收值。当前主要瓶颈是该 OPPO/ColorOS 上
`app_current()` 每次约 10.6 秒且可能错误报告系统设置页。不能为了提速删除每任务
应用和页面安全校验;若继续优化,应另建应用状态检测诊断工单。
## 相关提交
- `70cedc3` refactor: 复用持久设备工作线程 (#110)