HarmonyOS 7 EasyGo + Navigation:平行视界右栏路由栈与临时全屏恢复【鸿蒙心迹】
平行视界把大屏应用拆成左右两栏以后,我遇到的第一个麻烦不是布局,而是路由:右栏打开附件临时全屏,再返回时,系统看起来回来了,业务路由栈却可能已经丢了一层。

一、这次我从一条“返回错误”日志开始查
Demo 叫 MailDesk,是一个邮件阅读应用。
在大屏设备上,我让它使用平行视界的导航类分栏:左侧邮件列表保持稳定,右侧显示当前邮件详情。当前测试邮件是:
MAIL-20260930-108
分栏模式记为:
NAVIGATION
ratio = 1:2
正常链路很简单:
Inbox → MailDetail
问题出在附件。
用户在 MailDetail 里点击 产品评审材料.pdf,我希望附件进入一个临时全屏 AttachmentPreview。看完以后返回,应该重新回到左右分栏,而且右栏仍然是原来的 MailDetail。
第一版偶尔会出现这种情况:
AttachmentPreview → Inbox
也就是全屏页退出后,右栏详情丢了。
开始我怀疑是平行视界恢复慢,后来对照 HiLog 才发现,真正出错的是我们自己的导航状态:打开全屏页时把“当前页面”覆盖掉了,却没有留下进入全屏前的右栏快照。
HarmonyOS 7 的平行视界面向折叠屏、平板等大屏场景,官方介绍了左右推挤、固定覆盖等分栏方式,也支持 1:1、1:2、2:1 等比例和拖拽调整。能力负责的是分栏体验,但应用自己的 Navigation 路由和业务选中态仍然要管理好。
所以这篇我不展开讲如何配置整套 EasyGo,而只盯住一个具体问题:临时全屏前后,右栏路由怎么不丢。
二、布局状态和路由状态一开始就不能混成一个字符串
我最早写过这样的变量:
@State currentPage: string = 'Inbox'
在单栏页面里勉强够用,到了平行视界就不够了。
因为此时至少同时存在三种信息:
- 左栏是谁;
- 右栏是谁;
- 有没有临时全屏页面覆盖在上面。
如果只用一个 currentPage,打开 AttachmentPreview 后它会变成附件页;等附件页关闭时,代码已经不知道之前右栏是什么。
后来我把状态拆成一个快照。
这段代码解决什么问题:把分栏路由和临时全屏路由拆开记录。
export interface RouteSnapshot {
routeStack: string[]
currentRoute: string
selectedMailId: string
splitMode: 'NAVIGATION' | 'SHOPPING'
splitRatio: '1:1' | '1:2' | '2:1'
fullscreenRoute?: string
}
export class ParallelRouteStore {
snapshot: RouteSnapshot = {
routeStack: ['Inbox'],
currentRoute: 'Inbox',
selectedMailId: '',
splitMode: 'NAVIGATION',
splitRatio: '1:2'
}
save(): RouteSnapshot {
return JSON.parse(JSON.stringify(this.snapshot)) as RouteSnapshot
}
}
这里 splitMode 是 MailDesk 自己为了调试定义的业务枚举,并不是我在模拟某个官方字段。这样做的目的,是把 EasyGo 分栏策略和应用当前业务页面同时记录下来。
当右栏打开 MailDetail 时,快照变成:
routeStack = [Inbox, MailDetail]
currentRoute = MailDetail
selectedMailId = MAIL-20260930-108
splitMode = NAVIGATION
splitRatio = 1:2
到这里,页面到底长什么样已经可以从状态推回来。
三、临时全屏之前,先保存“返回以后要恢复什么”
附件预览是一次特殊跳转。
它和普通右栏详情跳转不一样:用户只是暂时离开分栏场景,并没有放弃当前邮件上下文。
所以我在打开附件前先保存当前快照。
这段代码解决什么问题:进入全屏附件前保存右栏路由,避免返回时只能猜。
export class ParallelRouteController {
private stack: string[] = ['Inbox']
private lastSplitSnapshot?: RouteSnapshot
openMailDetail(mailId: string): void {
this.stack = ['Inbox', 'MailDetail']
AppStorage.setOrCreate('selectedMailId', mailId)
console.info(
`[MailDesk] route=MailDetail stack=${JSON.stringify(this.stack)}`
)
}
openAttachmentPreview(fileId: string): void {
this.lastSplitSnapshot = this.captureSplitSnapshot()
this.stack.push('AttachmentPreview')
AppStorage.setOrCreate('fullscreenRoute', 'AttachmentPreview')
AppStorage.setOrCreate('previewFileId', fileId)
console.info(
`[MailDesk] fullscreen AttachmentPreview fileId=${fileId}`
)
}
}
这里没有把临时全屏理解成“新的主业务页面”,而是把它当成覆盖在当前分栏任务上的一个短生命周期页面。
这个判断很重要。
如果用户从邮件列表主动进入另一个一级模块,比如日历,那么当然应该重新组织导航栈;但附件预览、图片放大、文档阅读这类页面,退出后通常应该回到原来的业务上下文。
四、返回不是简单 pop,一定要看被 pop 的是什么
第一版 Bug 就发生在这里。
我当时统一调用 pop(),然后根据栈顶页面重新渲染。问题是全屏页进入期间,平行视界自身的布局变化和页面生命周期可能让 UI 重建,如果只依赖当前组件里的局部状态,恢复时信息不完整。
后来我专门为临时全屏做了一条恢复路径。
这段代码解决什么问题:关闭 AttachmentPreview 时恢复进入全屏前的分栏快照。
closeAttachmentPreview(): void {
const snapshot = this.lastSplitSnapshot
if (!snapshot) {
this.stack = ['Inbox']
return
}
this.stack = [...snapshot.routeStack]
AppStorage.setOrCreate('selectedMailId', snapshot.selectedMailId)
AppStorage.setOrCreate('splitMode', snapshot.splitMode)
AppStorage.setOrCreate('splitRatio', snapshot.splitRatio)
AppStorage.setOrCreate('fullscreenRoute', '')
this.lastSplitSnapshot = undefined
console.info(
`[MailDesk] restore route=${snapshot.currentRoute} ` +
`stack=${JSON.stringify(this.stack)} ratio=${snapshot.splitRatio}`
)
}
这段代码的关键不是多存几个字段,而是恢复动作有明确来源:恢复保存过的状态,不重新猜状态。
测试里,从全屏页返回后的结果应该始终是:
currentRoute = MailDetail
stack = [Inbox, MailDetail]
splitMode = NAVIGATION
ratio = 1:2
如果右栏内容是靠“默认打开第一封邮件”重新生成的,即使页面看起来也有邮件详情,那仍然是错误恢复。

DevEco Studio 截图里我保留了这条完整链路。右侧大屏模拟器是 MailDesk 的双栏邮件页,底部日志依次记录打开 MAIL-20260930-108、进入 AttachmentPreview、再恢复到 MailDetail。
五、平行视界下,右栏选中态其实也是路由的一部分
这个问题是附件修好以后才暴露出来的。
全屏返回正常了,但左栏有时没有继续高亮 MAIL-20260930-108。右栏显示 A 邮件,左栏却选中了 B 邮件,功能没崩,体验却很奇怪。
原因是我只保存了页面名,没有把 selectedMailId 当成导航上下文。
大屏双栏和手机单页最大的不同,是多个区域同时向用户表达“我现在在哪”。
因此,右栏路由和左栏选择必须来自同一份状态源。
我后来不再让 InboxPage 自己保存选中邮件,而是统一读取:
@StorageLink('selectedMailId')
private selectedMailId: string = ''
点击邮件以后先更新 selectedMailId,再执行右栏跳转。全屏返回时也从快照恢复它。
这样“路由”和“选择”就不会各走各的。
六、临时全屏不能顺手把分栏比例恢复成默认值
平行视界允许灵活分栏。官方介绍中给出了 1:1、1:2、2:1 等常用比例,并支持用户调整。
这意味着用户可能已经把右栏拉得更宽。
如果附件全屏返回后,应用把分栏重新初始化成默认 1:1,虽然内容没丢,用户刚才调整过的工作区却被重置了。
MailDesk 测试固定使用 1:2,所以我把比例也放进快照。正式项目如果允许拖拽,还应该保存实际比例或者保存能重新计算比例的状态。
布局状态不一定都要永久写数据库,但在一次连续任务里,至少应该跨过临时全屏。
七、我最后把“全屏前、全屏中、全屏后”直接做进调试页
为了验收方便,我做了一个手机端的路由状态页。
运行数据如下:
邮件 ID:MAIL-20260930-108
当前模式:NAVIGATION
分栏比例:1:2
路由栈:[Inbox, MailDetail]
临时全屏:AttachmentPreview
时间线是:
10:24 打开 MailDetail
10:25 打开 AttachmentPreview
10:26 关闭附件,恢复 MailDetail

图三显示的是最终恢复结果。
最下方不是一句模糊的“恢复成功”,而是把结果写得很具体:
MailDetail(右栏)
NAVIGATION(1:2)
[Inbox, MailDetail]
我越来越喜欢这种调试页。它不需要开发者在演示时不停切 DevEco Studio 看日志,也能让测试同学快速判断“页面看起来对”之外,路由栈到底对不对。
八、全局返回和右栏返回要先定义产品语义
平行视界还有一个容易被忽略的问题:返回键到底退谁。
在手机单栏里,返回通常就是退出当前页面。在双栏里,左栏可能一直停在 Inbox,右栏却已经走了多层:
MailDetail
→ ContactDetail
→ MailDetail
→ AttachmentPreview
这时候如果没有明确规则,全局返回很容易一会儿退出右栏,一会儿退出整个页面。
MailDesk 的约定是:
临时全屏存在时,优先关闭全屏;右栏有业务子层级时,优先回退右栏;右栏已经回到首层详情时,再由应用决定是保持双栏还是退出当前模块。
真正项目当然可以有不同规则,但一定要先定义,再实现。不能让组件层谁先收到返回事件谁就处理。
九、右栏连续点击时,还要防止同一详情被重复压栈
附件全屏修完以后,我又做了一轮连续点击测试。
在邮件列表里快速点击同一封邮件两次,如果每次都无条件 push,栈会变成:
[Inbox, MailDetail, MailDetail]
视觉上仍然是同一封邮件,所以这个问题很容易漏过去。真正暴露是在用户按返回时:第一次返回仍然停在同一详情,看起来像返回键失效。
因此我把“打开右栏详情”从单纯 push 改成了业务去重:当前右栏已经是 MailDetail,而且 selectedMailId 没有变化时,只刷新内容,不增加新的路由层级。
如果用户从 A 邮件切到 B 邮件,导航模式下也不一定要把 A、B 都压进栈。是否保留右栏历史,要根据产品语义决定。
MailDesk 的定位更像桌面邮件客户端:左侧列表负责切换选择,右侧详情只是当前选择的投影。所以 A → B 更适合替换右栏内容,而不是产生:
Inbox → MailA → MailB → MailC
否则用户连续查看十封邮件后,按十次返回才能退出详情,和大屏“快速浏览”的目标正好相反。
这件事让我意识到,平行视界不是把手机 Navigation 原样拉到大屏。左右栏同时存在以后,页面层级本身就需要重新定义。
十、进程恢复时,我不直接复用上一次的全屏状态
会话内恢复和进程级恢复也不是一回事。
如果用户正在 AttachmentPreview 全屏查看附件,然后系统回收进程,应用下一次重新打开时,是否应该直接回到全屏附件?这个答案未必是“是”。
MailDesk 目前只持久化稳定业务状态:
selectedMailId
splitMode
splitRatio
stableRoute
临时全屏 AttachmentPreview 被我当成会话状态,不作为冷启动默认恢复点。
原因很简单:附件文件可能已经失效,权限可能变化,下载缓存可能被清理。冷启动时强行恢复一个临时页面,反而容易进入错误状态。
所以重启以后,我先恢复到:
[Inbox, MailDetail]
然后确认 MAIL-20260930-108 仍然存在,再渲染右栏。
如果产品确实要求恢复附件预览,那也应该把附件资源有效性检查放在恢复前,而不是看到上次 route 名叫 AttachmentPreview 就直接 push。
这种区分可以让路由状态更稳定:稳定业务上下文可以持久化,短生命周期覆盖页只服务当前会话。
十一、分栏比例的恢复也要有设备边界
本文测试一直使用 1:2,但真实设备上不能把这个比例当成绝对规则。
用户可能在大平板上把右栏拉得很宽,换到更窄的折叠屏以后,原来的像素宽度可能已经不适用。所以保存“比例”比保存“左右各多少像素”更有迁移性。
即便保存的是比例,恢复时仍然要做约束。
例如当前窗口宽度不足以舒适显示 1:2,应用可以回落到当前设备更合理的预设值。恢复用户工作区的含义不是机械恢复每一个数字,而是在当前环境允许的范围内尽量恢复用户的上下文和偏好。
我会把这类恢复分成两步:
读取历史偏好
→ 按当前窗口能力重新校验
→ 得到本次实际分栏
日志里同时打印“请求比例”和“实际比例”,调试时就能看出是历史状态错误,还是当前设备做了合理降级。
十二、最终回归我不再只测“打开和返回”
MailDesk 最后的回归链路是:
打开 Inbox,选择 MAIL-20260930-108;确认右栏出现 MailDetail;调整一次分栏比例;打开附件全屏;返回;切另一封邮件;再切回 108;再次打开附件;返回;最后执行全局返回。
每一步都记录当前:
selectedMailId
currentRoute
fullscreenRoute
routeStack
splitMode
splitRatio
只有这些值和 UI 同时正确,我才认为这一轮通过。
大屏应用特别容易出现“看起来还能用”的状态 Bug。左栏有列表、右栏有内容,页面没有崩,测试很容易顺手点过去。但只要选中态、返回栈或比例恢复不一致,用户连续操作几次以后就会感觉这个应用“不听话”。
所以平行视界的调试重点不只是每一帧长什么样,而是用户操作五分钟以后,路由状态还能不能解释得通。
十三、我给这个问题留下的三个工程判断
第一个判断:分栏是布局能力,Navigation 是业务导航,两者不能互相替代。
EasyGo 能帮助应用获得更自然的大屏分栏体验,但应用仍然需要清晰维护当前业务页面和选中态。
第二个判断:临时全屏是一种覆盖状态,不一定是新的主路由上下文。
附件预览、图片查看、扫码等场景,如果退出后应该回到原任务,就值得在进入前保存快照。
第三个判断:恢复要恢复用户刚才的工作区,而不是恢复一个“看起来差不多”的默认页面。
当前邮件、右栏层级、分栏比例、左栏选中态,这些共同组成了用户的上下文。
HarmonyOS 7 的平行视界把大屏里的多任务体验做得更自然,官方给出的购物模式、导航模式和可调比例已经提供了很好的系统级基础。应用真正要补上的,是业务状态和路由恢复。
这次问题修完以后,我没有再把“附件能打开”当成验收结束点。
我真正关注的是:打开之前用户在哪,打开之后系统变成什么样,关闭以后能不能原封不动回到那个任务里。
当这条链路跑顺以后,平行视界才不是简单的“一分为二”,而是一个可以连续工作的应用空间。
参考资料
- HarmonyOS 7 平行视界能力解读:https://developer.huawei.com/consumer/cn/forum/topic/0201221235973021541
- 多设备通用适配指南:https://developer.huawei.com/consumer/cn/multidevice/adaptive-apps/
- HarmonyOS 示例代码中心:https://developer.huawei.com/consumer/cn/samples/
更多推荐



所有评论(0)