Skip to content

fix(nav): 修复返回栈被压穿导致的白屏与返回键失效 - #45

Closed
Rely-xcy wants to merge 2 commits into
jieapi:mainfrom
Rely-xcy:pr/nav-backstack
Closed

Rely-xcy wants to merge 2 commits into
jieapi:mainfrom
Rely-xcy:pr/nav-backstack

Conversation

@Rely-xcy

@Rely-xcy Rely-xcy commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

导航返回栈:返回键失效 / 白屏(2 条修复)

概述

这批只修导航与返回栈:2 个提交,只动 app/src/main/java/com/aicode/MainActivity.kt 一个文件,
+35 / -2。

用户报告的现象是连在一起的:

  1. 进终端后连按返回 → 白屏,页面上没有任何可操作入口;
  2. 白屏状态下右滑(predictive back 的边缘手势)能切到设置页,但进了设置页就再也退不出来(返回键被吞);
  3. 修掉入口之后用户复测:「返回键点多几次又会触发」——因为入栈入口不止一个。

两条提交分别对应「入口收敛」与「最外层兜底」:前者让终端 / Git 不再每开一次就压一层返回栈;
后者在最外层加一个 BackHandler,保证只要当前不在聊天页且再弹就空栈,返回键改为回聊天页而不是弹空。

全部改动可从 upstream/main 干净摘取(无冲突),并已通过 CI 编译验证(见文末)。

分支:pr/nav-backstack(基于 upstream/main 6c615e38),HEAD adc12e8c,已推到 origin。

修复清单

按提交顺序,每条给出:症状 → 机理 → 改法,并附原始 commit subject。

1. 最外层返回兜底:栈将空时回聊天页,而不是弹空成白屏

  • 症状:返回栈里只剩当前目的地时按返回键,系统把栈弹空 → 白屏,且没有任何可操作入口(用户只能杀应用)。
  • 机理:入栈入口不止一个(终端 / Git / 浏览器 / 设置 / 凭据都用 navigate,侧栏入口还是裸 navigate),
    任何一个状态错乱都会让用户站在「栈根不是 chat」的位置;逐个修入口永远有漏网,predictive back 的边缘手势
    还能把人直接带进设置页。
  • 改法:AppNavigation 最外层加 BackHandler(enabled = canFallbackToChat),条件为
    「当前路由不是 chat」+「当前目的地位于 RESUMED」+「previousBackStackEntry == null(再弹就空)」,
    命中时 navigate("chat") { popUpTo(startDestinationRoute) { inclusive = true }; launchSingleTop = true }。
    聊天页自身被显式排除,返回键仍退出应用;抽屉 / 大屏右栏 / 设置页内部层级三处既有返回处理 enabled 不满足时才轮到它。
    顺带把「栈深度」判定从内部 backQueue 改为公开语义的 previousBackStackEntry == null。
    fix(nav): 兜底返回拦截,栈将空时回聊天页而不是弹空成白屏

2. 终端 / Git 不再压穿返回栈;从预览手势进设置页后能退出

  • 症状:每开一次终端 / Git 就往返回栈里压一层(chat → terminal → chat → terminal → …),
    连按返回逐层弹出,弹空即白屏;此时右滑能切到设置页,而设置页的返回处理依赖「栈里恰好有上一层」,
    栈已被弄乱 → 按返回无事发生,卡死在设置页。
  • 机理:openWorkbench 窄窗分支用裸 navigate(route);SettingsScreen.onNavigateBack 用裸 popBackStack(),
    两者都依赖返回栈完好。
  • 改法:openWorkbench 改为 navigate(route) { popUpTo("chat") { saveState = true }; launchSingleTop = true },
    无论开多少次终端栈里始终是 chat → terminal 一层;设置页返回改为
    navigate("chat") { popUpTo(startDestinationRoute) { inclusive = true }; launchSingleTop = true },不依赖栈是否完好。
    大屏不受影响(expanded 分支仍是右栏开合,不入栈)。
    fix(nav): 终端/Git 压穿返回栈导致白屏,且从预览手势进设置页后无法退出

为什么对上游有价值

症状 影响
返回栈压穿 → 白屏 用户被困死,只能杀应用;属于「一次误触就不可恢复」的缺陷
设置页返回键失效 连返回手势都退不出,等于把用户关在设置页里
修复只做入口收敛 入口有五六个,漏一个就复发(用户复测即命中),兜底才是一劳永逸的那一层

风险 / 边界(请 reviewer 重点看)

  1. 这是行为改动,不是纯 UI 调整:BackHandler 兜底改变了返回键语义——在非聊天页且返回栈只剩一层时,
    返回键现在是「回聊天页」而不是「退出应用」。两处保证退出路径不变:enabled 条件里显式排除 currentRoute == "chat";
    兜底 BackHandler 注册在 AppNavigation 前部,Compose 的返回分发是「后注册者优先」,页面级 BackHandler 仍优先于它。
    仍建议在真机上过一遍系统返回键与返回手势。
  2. 设置页返回改为 navigate("chat") 而非 popBackStack():会重建聊天页目的地条目。会话状态不丢,
    因为 AIAgentViewModel 是 hiltViewModel() 提在 NavHost 之外(MainActivity.kt:358,作用域是 Activity),
    不随目的地条目重建;但目的地条目重建本身是行为变化,值得复核。
  3. popUpTo("chat") { saveState = true } 收敛终端 / Git 入栈:这两个页面不再多次入栈,
    saveState 复用的是聊天页的保存状态。若后续依赖多层栈做跨页状态恢复,需要复核。
  4. previousBackStackEntry == null 作为「再弹就空」的判据:这是 Navigation Compose 的公开 API 语义
    (替代原先依赖内部 backQueue.size 的写法)。若上游将来在栈底追加别的目的地,popUpTo(startDestinationRoute)
    会一并清掉——startDestinationRoute 取自 navController.graph,随图变化,是预期行为。

本批明确不含

  • 不含后续批次的改动:键盘 / 滚动(pr/ui-scroll-input)、单点修复如 Shizuku、终端断线横幅、附件归因(pr/single-fixes)
    都不在本批。
  • 不含任何 fork 私有内容:更新源、包名 / applicationId 差异、CI 触发条件差异、FORK_PRIVATE 清单、
    备份 / 记忆相关改动,一律不在本批(本批只碰 MainActivity.kt)。

验证情况

  • 基线:upstream/main(6c615e38)。
  • 2 个提交逐个 cherry-pick,无冲突;结果文件与原始分支逐文件 blob 一致。
  • 静态核查:只改 MainActivity.kt,未新增 import;改动文件的括号配平与上游版本逐一对比无失衡;
    改动到的符号(BackHandler、previousBackStackEntry、navigate、launchSingleTop、saveState)
    在上游既有代码里已有同类用法可对照。
  • CI:beta.yml 手动 workflow_dispatch(分支 pr/nav-backstack),跑 :app:assembleUniversalBeta
    (JDK 17 + R8 + 资源压缩)。
    • run:https://github.com/Rely-xcy/AiCode/actions/runs/37188966622,completed / success,attempt 1。
    • Build universal beta APK 步骤 success,整个 job 08:27:21Z → 08:33:58Z(约 6 分 37 秒)。
    • 产物 aicode-beta-3978240d5796b348a8b420a8a072ba55167acc44,25,398,423 字节(说明不止编译过了,R8 与资源处理也跑完了)。
    • 三个分支全部绿:pr/nav-backstack / pr/ui-scroll-input / pr/single-fixes 各一次 workflow_dispatch,均 success。
  • 单测未跑:ci.yml 的单测门禁只在 push 到 main / fork/release 时触发,本轮只能跑 beta 编译;
    assembleUniversalBeta 不编译 app/src/test。

Summary by CodeRabbit

  • Bug Fixes
    • System Back from a resumed screen now returns to Chat when there’s no previous screen in the navigation history.
    • Returning to Chat from Settings, Terminal, Git, or Browser now avoids creating duplicate Chat screens and preserves navigation state.

用户指出(对的):光修入口不够,「返回键点多几次又会触发」——因为入口不止一个
(终端/Git/浏览器/设置/凭据都是 navigate 进来的,侧栏入口还是裸 navigate),
predictive back 的边缘手势还能把人带进栈根不是 chat 的怪状态。逐个修入口永远有漏网。

兜底层:MainActivity 最外层加一个 BackHandler,当
- 当前不在聊天页,且
- 没有上一层可弹(previousBackStackEntry == null,再弹就空栈)
时,返回键改为 navigate("chat") 清栈回聊天页。这样无论栈怎么被弄乱,
白屏都不可能出现;在聊天页时兜底不启用(enabled=false),返回键仍是正常的退出应用。

backQueue.size 那个写法废弃(Navigation Compose 的 backQueue 不是公开 API 语义),
改用 previousBackStackEntry == null 判「再弹就空」。

现有三层返回处理不受影响:抽屉开 → 收抽屉;大屏右栏开 → 收右栏;设置页内部层级 →
内部返回。它们 enabled 条件不满足时才轮到兜底。
用户报告:点击进入终端后,连按返回多次会触发白屏;此时右滑手势能切到设置页,
但一旦进了设置页就无法退出。

两段病因,都在窄窗(手机)导航栈上:

1. 返回栈被压穿 → 白屏。openWorkbench 窄窗分支用裸 navigate("terminal"/"git"/"browser"),
   每开一次就往栈里压一层:chat → terminal → chat → terminal → …。连按返回逐层弹出,
   弹空后(栈里没有任何目的地)就是白屏;此时 predictive-back 的右滑手势仍可触发
   邻近的设置页,把用户带进一个"栈根不是 chat"的怪状态。

   修:navigate 改为 popUpTo("chat") { saveState = true } + launchSingleTop——
   无论开多少次终端,返回栈始终是 chat → terminal 一层,返回键只回聊天页。

2. 进了设置页无法退出。SettingsScreen 的 onNavigateBack(内部层级退完后的最后一跳)
   原来是 popBackStack()——依赖栈里恰好有上一层;栈已被弄乱时它无事发生,留在设置页
   怎么按都出不去。

   修:改用 navigate("chat") { popUpTo(startDestinationRoute) { inclusive = true } } +
   launchSingleTop——不依赖返回栈是否完好,总能回聊天页并把栈清干净。

大屏不受影响:openWorkbench 的 expanded 分支仍是右栏开合(不入栈)。
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
CLAUDE.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 339c0166-1621-45ca-92d6-c297454d563f
📥 Commits

Reviewing files that changed from the base of the PR and between 6c615e3 and adc12e8.

📒 Files selected for processing (1)
  • app/src/main/java/com/aicode/MainActivity.kt

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

Navigation now returns to chat in specified back-handling and narrow-window paths. The changes update back-stack behavior for these routes.

Changes

Chat navigation

Layer / File(s) Summary
Return navigation to chat
app/src/main/java/com/aicode/MainActivity.kt
A back handler navigates to chat when the current route is resumed, is not chat, and has no previous entry. Narrow-window workbench navigation saves state while popping to chat. The Settings back callback clears the back stack when navigating to chat.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: jieapi

Merge Risk: ⚪ Minimal · up to adc12

No confirmed issue currently blocks merging. Check the system Back key and gesture on a device as planned.

Security Architecture Review

Security architecture risk: 🔵 Low · up to adc12

The changes are confined to existing navigation paths; no new privileged destination or increased authority was identified. Saved-page restoration, process recreation, and interrupted Back behavior remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated change affects navigation within the existing Android application. The compared changes do not establish increased terminal, browser, Git, or MCP authority, or newly attacker-selectable external destinations.

Trust Boundaries and Controls

  • inferred — The changed transitions select existing routes through internal UI callbacks and Back handling. No new attacker-controlled route input or bypass of the surrounding credential and onboarding paths was identified in the inspected changes; broader identity-policy coverage remains incomplete.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了导航返回栈修复的主要目标:避免返回栈异常导致白屏和返回键失效。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 1 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Rely-xcy Rely-xcy closed this Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant