3. Android交易页面架构

交易链路是Android应用中最复杂、状态最敏感、页面跳转最频繁的场景之一。从行情列表点击买入,到下单确认、支付验证、结果回执,再到返回持仓或订单详情,整条链路往往跨越5~8个页面,涉及登录态校验、风控拦截、超时重试、结果回传等多种分支。如果页面架构职责划分不清,就会出现"返回后数据错乱""重复下单""页面栈越堆越深"等典型问题。本节围绕Activity、Fragment、Compose与Navigation组件,梳理它们在交易链路中的职责边界与页面栈管理策略。

一、四类组件的职责边界

Activity是系统级容器与任务入口。在交易链路中,它承担三类不可替代的职责:一是作为任务栈的宿主,承载整个交易流程的窗口与生命周期;二是作为跨进程交互的锚点,例如拉起支付SDK、指纹/人脸验证、系统分享等需要Activity上下文的能力;三是作为深链与外部跳转的落点,从推送、H5或其它App跳入交易页时,必须有一个稳定的Activity作为入口。交易链路通常采用"单Activity + 多页面"或"少量Activity + 多Fragment/Compose页面"的架构,避免每个页面一个Activity导致任务栈碎片化。

Fragment是传统View体系下的页面单元,在交易链路中适合承载生命周期敏感、需要与宿主深度交互的页面,例如下单确认页需要监听返回键拦截、需要在onPause时保存草稿、需要与Activity共享ViewModel。Fragment的优势在于生命周期回调完整、与View系统耦合自然;劣势是嵌套层级深、状态恢复复杂。

Compose是声明式UI,在交易链路中适合承载状态驱动、UI随数据变化频繁重组的页面,例如行情列表、订单簿、下单表单的实时校验反馈。Compose页面本身不是页面栈的单元,它需要配合Navigation组件才能参与导航。Compose的优势是状态与UI绑定清晰、重组粒度细;劣势是与传统Fragment混用时生命周期语义需要额外对齐。

Navigation组件是页面栈的管理者,负责定义目的地(Destination)、动作(Action)、参数(Argument)与返回栈(Back Stack)。它统一了Fragment与Compose的导航模型,使交易链路的跳转关系可以集中声明、集中校验。

交易链路页面栈与组件职责分工 TradeActivity(宿主容器 · 任务栈锚点) Navigation 组件 · NavHost / NavController 目的地声明 · 参数传递 · 返回栈管理 · 深链解析 返回栈(自底向上) ① 行情列表页 Compose · 状态驱动 · 高频重组 Destination: market_list ② 下单确认页 Fragment · 返回键拦截 · 草稿保存 · 共享ViewModel Destination: order_confirm ③ 支付验证页 Compose · 拉起支付SDK · 结果回传 Destination: pay_verify 栈顶 结果回传 Activity 职责 · 任务栈宿主与窗口管理 · 支付SDK / 生物识别锚点 · 深链与外部跳转落点 · 全局登录态与风控拦截 · 进程重建时的状态恢复 Fragment 职责 · 生命周期敏感页面承载 · 返回键拦截与草稿保存 · 与Activity共享ViewModel · 复杂View交互与动画 · 嵌套导航子图 Compose 职责 · 状态驱动UI与重组 · 实时行情/订单簿渲染 · 表单校验即时反馈 · 与Navigation集成导航 · 轻量级页面快速迭代

二、页面栈管理的核心策略

交易链路的页面栈管理,核心是回答三个问题:什么页面该入栈、什么页面该出栈、返回时数据如何回传。

第一,入栈策略。行情列表→下单确认→支付验证,这三步是典型的线性入栈。但要注意:下单确认页不应重复入栈。如果用户从行情页多次点击"买入",应通过launchSingleTop或popUpTo避免堆叠多个确认页。Navigation中可配置:

navController.navigate(
    R.id.action_market_to_confirm,
    bundleOf("symbol" to "BTC/USDT", "side" to "BUY"),
    navOptions {
        launchSingleTop = true
        popUpTo(R.id.market_list) { inclusive = false }
    }
)

第二,出栈策略。支付成功后,不应让用户按返回键回到支付验证页或下单确认页,否则可能触发重复下单。正确做法是:支付成功页入栈时,用popUpTo(R.id.order_confirm, inclusive = true)把确认页和验证页一起弹出,只保留行情列表作为返回落点。这样用户从结果页返回,直接回到行情列表,链路干净。

第三,数据回传。交易链路中,支付结果需要回传给下单确认页或订单详情页。传统做法是setFragmentResult或SavedStateHandle;Compose中则通过previousBackStackEntry.savedStateHandle写入结果,前一页面观察该状态。Navigation的SavedStateHandle是跨页面回传的推荐方式,它不依赖页面实例存活,进程重建后仍可恢复。

交易链路中,凡是涉及资金变动的页面,返回栈必须"干净"——即用户返回时不应回到任何可能触发二次提交的中间页。这是页面栈管理的硬性约束。

三、混合架构下的状态对齐

实际项目中,交易链路往往是Fragment与Compose混用的。例如下单确认页用Fragment承载(需要返回键拦截和草稿保存),支付验证页用Compose承载(需要实时状态反馈)。混用时,最大的风险是生命周期语义不对齐:Fragment的onPause与Compose的DisposableEffect释放时机不同,可能导致支付SDK回调时页面已销毁。

解决方式是:把交易链路的核心状态提升到Activity级别的ViewModel,Fragment和Compose页面都只做UI渲染与事件转发。ViewModel持有订单草稿、支付状态、风控结果等数据,页面销毁重建时从ViewModel恢复。这样,无论页面是Fragment还是Compose,状态都不依赖页面实例,页面栈的入栈出栈不会丢失交易上下文。

另一个关键点是返回键的统一处理。交易链路中,支付验证页的返回键应被拦截并弹出"确认放弃支付"对话框;下单确认页的返回键应保存草稿后再退出。在Fragment中通过OnBackPressedDispatcher注册回调;在Compose中通过BackHandler实现。两者都注册到同一个OnBackPressedDispatcher,由Navigation统一调度,避免多个页面同时拦截导致行为混乱。

四、一个可参考的职责划分表

页面/环节 承载组件 入栈/出栈策略 状态归属
行情列表 Compose 栈底,常驻 MarketViewModel
下单确认 Fragment launchSingleTop,防重复入栈 TradeViewModel(Activity级)
支付验证 Compose 入栈时popUpTo确认页 TradeViewModel + PayState
支付结果 Compose popUpTo确认页inclusive,返回落点为行情列表 TradeViewModel
订单详情 Fragment 独立子图,可从结果页或持仓页进入 OrderViewModel

这张表的核心思想是:组件选型服务于页面特性,页面栈策略服务于交易安全,状态归属服务于进程重建。三者对齐后,交易链路才能在频繁跳转、超时重试、进程被杀等异常场景下保持数据一致与操作安全。

不要为了统一技术栈而强行把所有交易页面改成Compose或Fragment。支付SDK、生物识别、系统分享等能力对Activity/Fragment上下文有强依赖,Compose页面需要通过LocalContext和rememberLauncherForActivityResult桥接,桥接层越薄越稳定。

总结来说,Activity是交易链路的"地基",负责窗口、任务栈与系统能力锚点;Fragment是"承重墙",承载生命周期敏感、交互复杂的页面;Compose是"轻质隔断",适合状态驱动、快速迭代的页面;Navigation是"施工图",统一声明页面关系与返回栈规则。四者各司其职,交易链路才能在复杂业务与异常场景下保持稳定。