Android Agent:切换应用后保持任务详情视图,不退回列表 #134

Closed
opened 2026-08-28 17:23:24 +08:00 by ila · 2 comments
Owner

所属与来源

  • 关联工单:#128 自动返回时保持用户上次选择的 Tab(已关闭,同一主题的上一层)、#130 Agent 从失败任务发起替代商品采集(本工单为其前置)。
  • 来源:用户于 2026-08-28 反馈:在 Agent 采集 Tab 打开某个采集任务详情后,切到 PDD App 再切回 Agent,界面不保持在任务详情,而是回到了任务列表。
  • 类型:Android Agent / 界面状态保持缺陷。
  • 设计证据:不新增页面、组件、导航与显示文案,仅恢复用户切换应用前所处的视图,属恢复既有预期行为的缺陷修复,不需要原型。
  • 工具回退说明:本工单通过 Gitea API 创建;当前会话未提供 Gitea MCP 工具,按 AGENTS.md「Gitea 交互与工单最小读取」记录回退原因。

当前事实与根因(提交 30d8238 复核)

根因在 android/app/src/main/java/cn/ilapage/goauto/agent/ui/TaskHistoryFragment.kt:137-140:

override fun onResume() {
    super.onResume()
    load()          // 无条件重新拉取并渲染列表
}

每次回到前台都会执行 load(),把列表重新渲染进 resultColumn,覆盖掉原本显示的详情内容。

结构上的成因:

  1. 列表与详情共用同一个容器 resultColumn,通过 removeAllViews() + 重新 addView 切换(见 renderDetail,:428)。这不是 Fragment 回退栈,也没有导航层级,「进入详情」只是在同一容器内换了一批 View。
  2. Fragment 内的状态变量只有列表相关的 selectedStatus、page、total、pageSignature(:93-98),没有任何变量记录「当前正在查看详情 #N」。因此 onResume 即使想恢复也无从判断,只能走默认的列表路径。

与 #128 的关系:#128 通过 AgentTabSelectionPolicy 与 MainActivity 的 savedInstanceState 保住了选中的 Tab(MainActivity.kt:21/34/60/93),属于 Activity 层级;本缺陷位于 Fragment 内部的视图状态,是不同层级,#128 未覆盖。当前表现为:切回后仍停在「采集」Tab,但 Tab 内退回了列表。

目标

  1. 从采集或采购任务详情切换到其他应用后再切回,仍停留在原来的任务详情。
  2. 详情内容在返回时刷新为最新数据,而不是展示切走前的陈旧快照。
  3. 不改变从详情返回列表、下拉刷新、筛选与分页的既有行为。

非目标

  • 不重构列表与详情的容器结构,不引入 Fragment 回退栈或导航组件。
  • 不修改 #128 的 Tab 保持逻辑。
  • 不修改任务详情的展示内容、接口与「重新采集」等既有按钮行为。
  • 不涉及采集执行、采购、服务端与数据库。

实施方案

  1. 在 TaskHistoryFragment 新增状态变量,记录当前是否处于详情视图及其任务号(例如 detailTaskId: Long?,为空表示列表视图)。
  2. 置位与清除的时机:
    • loadCollectionDetail() / loadPurchaseDetail() 进入详情时置位;
    • renderDetail 中「返回采集记录 / 返回采购记录」按钮点击、以及 load() 返回列表时清除;
    • 采集/采购结果广播触发的跳转(currentPageReceiver,:100-108)按其实际渲染结果同步维护该变量。
  3. onResume() 改为按该变量分支:非空则重新加载对应详情,为空则维持现有的 load()。
  4. 通过 onSaveInstanceState / onCreate(savedInstanceState) 持久化该变量,使进程被系统回收后重建仍能恢复详情;与 #128 的 Tab 恢复方式保持一致的写法。
  5. 复用既有的 requestGeneration 机制,避免恢复详情与其他请求竞态导致渲染错乱。
  6. 详情加载失败时按既有 showError 处理,并清除状态变量退回列表,不得反复重试或卡在空白页。

安全边界

  • 仅涉及本地界面状态,不改变任何任务状态、接口调用语义与服务端数据。
  • 不新增权限、不新增存储、不记录任务内容到本地。
  • 不涉及采购、下单、地址与支付。

验收标准

  • 采集任务详情下切到 PDD 再切回 Agent,仍停留在该任务详情,且内容为最新数据。
  • 采购任务详情下同样保持。
  • 处于列表视图时切走再切回,行为与本工单实施前一致(仍在列表,且刷新)。
  • 从详情点击「返回采集记录 / 返回采购记录」后切走再切回,停留在列表,不会又跳回详情。
  • 应用进程被系统回收后重建,仍能恢复到原详情。
  • 详情加载失败时给出既有错误提示并退回列表,不卡在空白页、不反复重试。
  • 下拉刷新、状态筛选、分页与「重新采集」按钮行为均无变化。
  • #128 的 Tab 保持行为不回退。

验证方式

  • cd android && .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug
  • 状态变量流转的纯函数或轻量单元测试:进入详情、返回列表、广播跳转三条路径下变量取值正确。
  • 真机 PKG110:分别在采集与采购详情下执行「切到 PDD 再切回」;并用开发者选项「不保留活动」验证进程回收后的恢复。
  • 真机、多设备与异常路径未覆盖部分如实回写。

依赖、并行与风险

  • 无前置依赖,可立即实施。
  • 本工单是 #130 的前置:#130 的动线为「进失败任务详情 → 点采集替代商品 → 切到 PDD → 切回查看匹配状态」,详情不保留则该流程不成立;且 #130 同样修改 TaskHistoryFragment.kt,两者同时进行会产生冲突。必须在 #130 开工前完成。
  • 与 #129 / #131(服务端 Go 代码)无文件重叠,可并行。
  • 风险:恢复详情会在每次回到前台触发一次详情接口请求。缓解:复用既有 requestGeneration 竞态保护;该请求频次与原先每次回前台拉取列表相当,不额外增加负担。
  • 回退:还原本工单提交即可,无数据影响。

文档影响

  • 预计无长期文档影响:不改变模块入口、接口、配置、状态机与验证方式,仅恢复界面状态保持这一既有预期行为。实施完成后在工单记录该结论并跳过 Wiki 更新与同步;若实施中改变了界面状态模型的长期架构描述,再按 Wiki-first 门禁更新 Architecture-and-Code-Map。

状态

待实施。

## 所属与来源 - 关联工单:#128 自动返回时保持用户上次选择的 Tab(已关闭,同一主题的上一层)、#130 Agent 从失败任务发起替代商品采集(本工单为其前置)。 - 来源:用户于 2026-08-28 反馈:在 Agent 采集 Tab 打开某个采集任务详情后,切到 PDD App 再切回 Agent,界面不保持在任务详情,而是回到了任务列表。 - 类型:Android Agent / 界面状态保持缺陷。 - 设计证据:不新增页面、组件、导航与显示文案,仅恢复用户切换应用前所处的视图,属恢复既有预期行为的缺陷修复,不需要原型。 - 工具回退说明:本工单通过 Gitea API 创建;当前会话未提供 Gitea MCP 工具,按 `AGENTS.md`「Gitea 交互与工单最小读取」记录回退原因。 ## 当前事实与根因(提交 30d8238 复核) 根因在 `android/app/src/main/java/cn/ilapage/goauto/agent/ui/TaskHistoryFragment.kt:137-140`: ```kotlin override fun onResume() { super.onResume() load() // 无条件重新拉取并渲染列表 } ``` 每次回到前台都会执行 `load()`,把列表重新渲染进 `resultColumn`,覆盖掉原本显示的详情内容。 结构上的成因: 1. 列表与详情**共用同一个容器** `resultColumn`,通过 `removeAllViews()` + 重新 `addView` 切换(见 `renderDetail`,`:428`)。这不是 Fragment 回退栈,也没有导航层级,「进入详情」只是在同一容器内换了一批 View。 2. Fragment 内的状态变量只有列表相关的 `selectedStatus`、`page`、`total`、`pageSignature`(`:93-98`),**没有任何变量记录「当前正在查看详情 #N」**。因此 `onResume` 即使想恢复也无从判断,只能走默认的列表路径。 与 #128 的关系:#128 通过 `AgentTabSelectionPolicy` 与 `MainActivity` 的 `savedInstanceState` 保住了**选中的 Tab**(`MainActivity.kt:21/34/60/93`),属于 Activity 层级;本缺陷位于 Fragment 内部的视图状态,是不同层级,#128 未覆盖。当前表现为:切回后仍停在「采集」Tab,但 Tab 内退回了列表。 ## 目标 1. 从采集或采购任务详情切换到其他应用后再切回,仍停留在原来的任务详情。 2. 详情内容在返回时刷新为最新数据,而不是展示切走前的陈旧快照。 3. 不改变从详情返回列表、下拉刷新、筛选与分页的既有行为。 ## 非目标 - 不重构列表与详情的容器结构,不引入 Fragment 回退栈或导航组件。 - 不修改 #128 的 Tab 保持逻辑。 - 不修改任务详情的展示内容、接口与「重新采集」等既有按钮行为。 - 不涉及采集执行、采购、服务端与数据库。 ## 实施方案 1. 在 `TaskHistoryFragment` 新增状态变量,记录当前是否处于详情视图及其任务号(例如 `detailTaskId: Long?`,为空表示列表视图)。 2. 置位与清除的时机: - `loadCollectionDetail()` / `loadPurchaseDetail()` 进入详情时置位; - `renderDetail` 中「返回采集记录 / 返回采购记录」按钮点击、以及 `load()` 返回列表时清除; - 采集/采购结果广播触发的跳转(`currentPageReceiver`,`:100-108`)按其实际渲染结果同步维护该变量。 3. `onResume()` 改为按该变量分支:非空则重新加载对应详情,为空则维持现有的 `load()`。 4. 通过 `onSaveInstanceState` / `onCreate(savedInstanceState)` 持久化该变量,使进程被系统回收后重建仍能恢复详情;与 #128 的 Tab 恢复方式保持一致的写法。 5. 复用既有的 `requestGeneration` 机制,避免恢复详情与其他请求竞态导致渲染错乱。 6. 详情加载失败时按既有 `showError` 处理,并清除状态变量退回列表,不得反复重试或卡在空白页。 ## 安全边界 - 仅涉及本地界面状态,不改变任何任务状态、接口调用语义与服务端数据。 - 不新增权限、不新增存储、不记录任务内容到本地。 - 不涉及采购、下单、地址与支付。 ## 验收标准 - [ ] 采集任务详情下切到 PDD 再切回 Agent,仍停留在该任务详情,且内容为最新数据。 - [ ] 采购任务详情下同样保持。 - [ ] 处于列表视图时切走再切回,行为与本工单实施前一致(仍在列表,且刷新)。 - [ ] 从详情点击「返回采集记录 / 返回采购记录」后切走再切回,停留在列表,不会又跳回详情。 - [ ] 应用进程被系统回收后重建,仍能恢复到原详情。 - [ ] 详情加载失败时给出既有错误提示并退回列表,不卡在空白页、不反复重试。 - [ ] 下拉刷新、状态筛选、分页与「重新采集」按钮行为均无变化。 - [ ] #128 的 Tab 保持行为不回退。 ## 验证方式 - `cd android && .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug` - 状态变量流转的纯函数或轻量单元测试:进入详情、返回列表、广播跳转三条路径下变量取值正确。 - 真机 PKG110:分别在采集与采购详情下执行「切到 PDD 再切回」;并用开发者选项「不保留活动」验证进程回收后的恢复。 - 真机、多设备与异常路径未覆盖部分如实回写。 ## 依赖、并行与风险 - 无前置依赖,可立即实施。 - **本工单是 #130 的前置**:#130 的动线为「进失败任务详情 → 点采集替代商品 → 切到 PDD → 切回查看匹配状态」,详情不保留则该流程不成立;且 #130 同样修改 `TaskHistoryFragment.kt`,两者同时进行会产生冲突。**必须在 #130 开工前完成。** - 与 #129 / #131(服务端 Go 代码)无文件重叠,可并行。 - 风险:恢复详情会在每次回到前台触发一次详情接口请求。缓解:复用既有 `requestGeneration` 竞态保护;该请求频次与原先每次回前台拉取列表相当,不额外增加负担。 - 回退:还原本工单提交即可,无数据影响。 ## 文档影响 - 预计无长期文档影响:不改变模块入口、接口、配置、状态机与验证方式,仅恢复界面状态保持这一既有预期行为。实施完成后在工单记录该结论并跳过 Wiki 更新与同步;若实施中改变了界面状态模型的长期架构描述,再按 Wiki-first 门禁更新 `Architecture-and-Code-Map`。 ## 状态 待实施。
Author
Owner

实施完成,待验收

已完成 #134 并推送到 main。

  • TaskHistoryFragment 记录当前详情任务号;进入详情/返回列表/当前页采集广播均沿用同一状态。
  • 回到前台时,列表状态刷新列表,详情状态重新请求对应采集或采购详情,因此不会被无条件 load() 覆盖。
  • 通过 onSaveInstanceState 恢复进程重建前的详情任务号;详情请求失败或凭据缺失时清除状态,避免循环恢复。
  • 保留既有 requestGeneration 竞态保护;未改变筛选、分页、重新采集、采购重试和 #128 Tab 保持。
  • 验证:.\gradlew.bat :app:testDebugUnitTest :app:assembleDebug,通过。
  • 新增状态流转单元测试:进入详情、返回列表、恢复任务号、广播式进入详情、非法编号忽略。
  • 未执行真机切换与“开发者选项-不保留活动”验证;留待 #130/#132 合并构建后的同轮真机验收。
  • 无长期文档影响:仅恢复既有视图状态,不改变接口、业务状态机、配置或模块入口,按工单跳过 Wiki 更新/同步。
  • 提交:4862d48 fix(#134): preserve task detail on app switch,已推送。

工单保持打开,状态为待验收。

## 实施完成,待验收 已完成 #134 并推送到 `main`。 - `TaskHistoryFragment` 记录当前详情任务号;进入详情/返回列表/当前页采集广播均沿用同一状态。 - 回到前台时,列表状态刷新列表,详情状态重新请求对应采集或采购详情,因此不会被无条件 `load()` 覆盖。 - 通过 `onSaveInstanceState` 恢复进程重建前的详情任务号;详情请求失败或凭据缺失时清除状态,避免循环恢复。 - 保留既有 `requestGeneration` 竞态保护;未改变筛选、分页、重新采集、采购重试和 #128 Tab 保持。 - 验证:`.\gradlew.bat :app:testDebugUnitTest :app:assembleDebug`,通过。 - 新增状态流转单元测试:进入详情、返回列表、恢复任务号、广播式进入详情、非法编号忽略。 - 未执行真机切换与“开发者选项-不保留活动”验证;留待 #130/#132 合并构建后的同轮真机验收。 - 无长期文档影响:仅恢复既有视图状态,不改变接口、业务状态机、配置或模块入口,按工单跳过 Wiki 更新/同步。 - 提交:`4862d48 fix(#134): preserve task detail on app switch`,已推送。 工单保持打开,状态为待验收。
Author
Owner

用户于 2026-08-29 明确确认本工单通过验收。验收结论已记录,现关闭工单。没有新的长期事实变化,本次不重复同步 Wiki。

用户于 2026-08-29 明确确认本工单通过验收。验收结论已记录,现关闭工单。没有新的长期事实变化,本次不重复同步 Wiki。
ila closed this issue 2026-08-29 20:43:49 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#134