订单确认页看上去只是把商品名、金额、状态和时间从一个页面带到另一个页面真正的难点是这些值会经过Intent、Bundle、Parcelable并可能在进程被系统回收后重新创建。若领域状态本身可变、金额用浮点数、枚举没有稳定存储值页面重建后就很容易得到一个“看着能打开、数据却不可信”的订单。一句话结论把订单建成不可变、可比较且有稳定标识的领域数据把Bundle和Parcelable仅视为跨组件传输格式并在恢复时重新校验和格式化才能让 Android 的页面跳转与进程重建不破坏业务语义。先划清版本与事实边界本文以 Java 8 时间 API 和当前 Android Framework 的公开Intent、Bundle、Parcel、Parcelable设计为概念参照。Bundle的具体存储细节、Binder 限额和系统保存状态时机随 Android 版本及设备实现而不同它们不是稳定的领域模型契约。标为“源码事实”的内容描述公开 API 的职责标为“简化伪代码”的内容只展示建模关系标为“生产建议”的内容需要结合应用架构、数据库和服务端协议落地。从订单状态开始而不是从 extra 开始设想用户在订单详情页点击付款页面持有一个订单快照付款结果把状态从待支付推进到已支付随后详情页把“只够恢复页面”的参数交给物流页。订单状态不是一个随意拼接的字符串也不是把整个业务对象长期塞在Bundle里。提交订单支付成功支付超时发货签收CREATEDPENDING_PAYMENTPAIDCLOSEDSHIPPEDCOMPLETED这个图表达业务允许的迁移不代表 UI 可以直接把任意枚举写进状态。比如CLOSED不能跳回PAID页面按钮应根据当前状态和后端确认结果决定是否发起操作。String、包装类与空值的边界String是不可变对象适合订单号、货币代码、展示名称和稳定状态码。它的不可变性意味着拼接或替换会产生新对象不要指望调用方法会原地修改订单字段。Android 的Intent.putExtra和Bundle.putString传递的是值内容不能把它们当作共享、可观察的领域对象。Java 包装类如Integer、Long、Boolean让值能表示为对象并允许空值但自动装箱并没有消除空指针风险Long total null; long cents total;会在拆箱时抛出NullPointerException。业务层应把“未知”“未加载”“没有资格”建模为明确状态而不是让每个调用点猜测null的含义。进入Bundle前也应明确可选字段是否存在读取时用containsKey或给出可靠默认值。enum是状态集合不是数据库协议enum OrderState让编译器帮助穷尽分支并让状态迁移集中可读。不过name()是 Java 源码名重命名常量就会改变持久化文本。因此落盘、网络和跨版本恢复使用稳定 ID例如pending_payment再由工厂转换为枚举。未知 ID 不应悄悄映射为某个成功状态应该产生可见的恢复失败、重新拉取或兼容分支。时间、金额与资源格式化各做自己的工作Instant适合表达一个绝对时刻例如支付完成时间LocalDateTime没有时区不应独自代表服务端已发生的事件。显示时用用户时区把Instant转为ZonedDateTime再用DateTimeFormatter格式化。格式化器属于展示层领域模型保存机器可比较的时间值。金额不要使用double二进制浮点数无法精确表示许多十进制小数累加后会出现肉眼意外的尾差。订单领域常用最小货币单位long amountMinor加 ISO 货币代码或在确需十进制运算时使用BigDecimal和明确的RoundingMode。Android 资源字符串负责本地化的文案与参数位置例如getString(R.string.order_total, formattedAmount)它不替代金额计算也不应在领域层持有Context。Android 传输边界谁带数据谁恢复语义订单详情页OrderArgs MapperIntent ExtrasBundle物流页面参数校验订单仓库Intent描述一次组件启动请求extras 是其附带参数Bundle是按键保存多种受支持值的容器。它们适合传orderId、筛选条件、少量显示状态或可恢复的轻量值。到达目标页面后页面应先校验参数再用orderId让仓库读取当前订单而不是相信旧页面塞入的整个可变订单。Parcelable是 Android 为高效进程间或组件间编组提供的接口Parcel是有序的读写缓冲写入和读取字段的顺序及类型必须一致。它适合短生命周期、结构明确且体积小的传输快照。它并不提供长期持久化、版本迁移、加密或业务校验。Serializable使用反射式 Java 序列化常常分配更多对象且不适合 Android 热路径不要因“实现一个接口很省事”就把它作为默认方案。Immutable OrderParcelable SnapshotScreen LaunchSaved State BundleProcess StoppedScreen RecreatedRestore Order IdRepository Source这里的关键是边界配置变更、导航返回栈和系统保存状态都可能让页面重新创建进程死亡后普通 Java 对象及其内存引用不再存在。Bundle中保留下来的只是可编组的数据不是原对象的连续生命。恢复路径至少要处理字段缺失、版本变化、订单已取消或数据源不可用并把最终业务真相交给仓库或后端。源码锚点与责任表锚点层次与结论类型谁创建、谁持有、何时交付Intent组件启动请求可携带 extras源码事实调用页面创建系统根据 Intent 启动目标组件目标组件从 Intent 读取值Bundle以 key 保存受支持编组值的容器源码事实调用方或系统状态保存创建Intent 或保存状态持有恢复时读出Parcelable与Parcel类型定义跨 Android 边界的写入与读取协议源码事实类型实现协议Framework 或 Binder 在需要编组时使用接收端重建快照String、BigDecimal、Instant值的含义由领域层定义格式化不是存储源码事实加概念边界领域工厂创建不可变对象由订单快照持有展示层转换为文案OrderArgs只传订单 ID 和必要导航参数简化伪代码页面映射后写入 extras目标页校验并交给仓库OrderRepository以 ID 重新读取权威订单生产建议应用注入或页面范围创建数据层持有恢复和刷新时返回最新状态SavedStateHandle或数据库保存轻量恢复键、持久业务事实生产建议架构层管理不要把完整大对象塞入 Intent在重建后恢复表中的Bundle、Parcel和Parcelable是 Android Framework 可追踪的职责锚点OrderArgs是教学命名不是系统类。生产代码还应让网络 DTO、数据库实体、领域订单和 UI 展示模型各自负责自己的版本演进。紧凑订单模型状态、金额、时间都可比较下面是简化 Java 伪代码。它展示不可变快照、稳定状态 ID、最小货币单位和显示格式化的分层代码省略了 Android 的Parcelable.Creator样板和实际资源文件。enumOrderState{PENDING_PAYMENT(pending_payment),PAID(paid),CLOSED(closed);finalStringstableId;OrderState(StringstableId){this.stableIdstableId;}staticOrderStatefromStableId(Stringvalue){for(OrderStatestate:values()){if(state.stableId.equals(value))returnstate;}thrownewIllegalArgumentException(Unknown order state);}}finalclassOrder{finalStringid;finalOrderStatestate;finallongamountMinor;finalStringcurrencyCode;finalInstantpaidAt;Order(Stringid,OrderStatestate,longamountMinor,StringcurrencyCode,InstantpaidAt){if(idnull||id.isEmpty())thrownewIllegalArgumentException(id);if(amountMinor0)thrownewIllegalArgumentException(amountMinor);this.idid;this.statestate;this.amountMinoramountMinor;this.currencyCodecurrencyCode;this.paidAtpaidAt;}StringdisplayTotal(Localelocale){CurrencycurrencyCurrency.getInstance(currencyCode);BigDecimalmajorBigDecimal.valueOf(amountMinor,currency.getDefaultFractionDigits());NumberFormatformatterNumberFormat.getCurrencyInstance(locale);formatter.setCurrency(currency);returnformatter.format(major);}StringdisplayPaidAt(ZoneIdzone,Localelocale){if(paidAtnull)return未支付;returnDateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm,locale).withZone(zone).format(paidAt);}}这里的locale是调用方显式传入的展示 locale例如用户资料或应用语言设置它决定数字分组、小数点、货币符号位置和日期文字的本地化规则并不决定订单使用哪一种货币。currencyCode通过Currency.getInstance决定格式化的货币单位只有调用方特意传入设备默认 locale 时展示习惯才随设备默认设置变化。领域层仍保存货币代码和最小单位而不保存已经格式化的金额文本。final字段与不暴露 setter 让这个对象在构造后不再改变适合比较、缓存和 UI 渲染但不可变不等于自动正确。equals与hashCode必须一起重写且要先定义身份语义订单是否仅以id相等还是同一id且版本或全部字段相等。用于RecyclerView差分时通常分别比较“是否同一订单”和“内容是否相同”不要把 Java 默认引用相等误认成业务相等。**刻意省略**示例没有处理货币小数位因币种而异、税费和优惠拆分、BigDecimal运算时的舍入策略、状态迁移授权、网络并发更新、序列化版本、国际化资源字符串、数据库事务及Parcelable字段兼容。生产系统应由服务端或持久化层定义权威版本客户端只根据协议展示和请求操作。让传输模型比领域模型更小跨页面时可以显式定义一个只含恢复所需字段的快照。下面也是简化伪代码它传递orderId和一个可选来源标记目标页面不把这个Bundle保存为自己的领域状态。finalclassOrderArgs{staticfinalStringKEY_ORDER_IDorder_id;staticfinalStringKEY_SOURCEsource;staticIntenttoIntent(Contextcontext,StringorderId,Stringsource){returnnewIntent(context,ShippingActivity.class).putExtra(KEY_ORDER_ID,orderId).putExtra(KEY_SOURCE,source);}staticStringrequireOrderId(Intentintent){StringorderIdintent.getStringExtra(KEY_ORDER_ID);if(orderIdnull||orderId.isEmpty()){thrownewIllegalArgumentException(Missing order_id);}returnorderId;}}生产建议目标页把requireOrderId的失败转换为可诊断的导航错误或安全返回随后由 ViewModel 和仓库加载订单。需要通过savedInstanceState或SavedStateHandle恢复时保存 ID、筛选条件和小型 UI 状态即可。图片、长列表、数据库对象、文件内容和完整网络响应应以 ID、URI 或缓存键传递避免把 Binder transaction 做成隐形数据管道。五个会让订单不可靠的坑用double表示金额。浮点误差会在累加、折扣和比较时放大使用最小货币单位long或使用带明确精度与舍入规则的BigDecimal。把可变Bundle当领域模型。Bundle是传输容器键可以漏、类型可以错、生命周期可以结束用不可变Order表示业务事实用 mapper 隔开两层。持久化enum.name()。枚举重命名会改变恢复数据落盘和协议使用稳定 ID并对未知值制定兼容策略。在 Intent extras 里放大对象。大对象会增加编组成本并可能触及 Binder 事务大小限制传 ID、URI 或缓存键再从数据源读取。习惯性使用Serializable。它不是 Android 页面传参的默认选择短小明确的快照优先Parcelable真正需要恢复的业务事实优先 ID 加持久数据源。动手练习跨组件安全地恢复订单定义OrderState的稳定 ID并写一个覆盖未知 ID 的测试或错误分支。用long amountMinor和Instant paidAt构造不可变Order为订单身份和内容分别写比较方法或差分逻辑。从详情页只把orderId与一个来源标记写入 Intent。让物流页校验字段然后通过仓库按 ID 加载订单。在onSaveInstanceState或SavedStateHandle保存orderId模拟清除进程后重新进入页面验证页面会重新查询数据而非依赖旧对象引用。使用资源字符串接收已经格式化的金额和时间切换设备语言与时区确认领域数据没有被展示文案污染。验收点是删除任意页面内存中的Order引用后只要订单 ID 和数据源仍有效重建页面仍能显示正确状态当恢复参数缺失、未知或过期时页面不会把它伪装成一个有效订单。检查表、面试表达与下一站检查表订单 ID 是否稳定且已校验金额是否避免double状态是否使用稳定存储 ID领域对象是否不可变并定义了正确的相等语义Intent 是否只传轻量参数进程重建后是否从仓库恢复金额和时间是否仅在展示层用资源与 locale 格式化面试表达“我把Intent、Bundle和Parcelable看作 Android 组件边界的传输协议不把它们当领域模型。订单用不可变值对象保存稳定 ID、状态、最小货币单位和绝对时间状态枚举落盘用稳定 ID金额避免double。页面恢复只保存订单 ID再由仓库获取权威数据这样配置变化和进程死亡都不会把旧内存当成业务真相。”下一篇进入集合与泛型当单个Order的状态和相等语义清楚后才适合讨论如何用List、Map、缓存与泛型把多个订单安全地组织、比较和渲染。