MainActivity 同任务栈单实例修复方案

日期:2026-07-23 目标:MainActivity 代码已实施 自动化验证通过 待指定设备真机回归 用户已授权实施
保证 MainActivity 在同一任务栈内只有一个实例;冷启动继续执行现有初始化和菜品全量同步, 从其他页面或通知回到已有 Main 时不重复初始化、不重新拉取全部菜品。

1. 方案状态

2. 修复目标

  1. 冷启动首次进入 Main 时继续执行现有初始化和菜品全量同步。
  2. 从设置页、启动页、业务页、AI 就绪回退和 MQTT 通知回到 Main 时优先复用已有实例。
  3. 回到已有 Main 时不重新执行 onCreate()
  4. 返回键不会再次显示旧的 Main 实例。
  5. Main 销毁时正确释放动态注册的 AlarmReceiver

3. 当前问题与原因

MainActivity 使用默认 standard 启动模式

原 Manifest 没有指定启动模式。普通启动 Main 时,如果已有 Main 不在栈顶,系统会创建新实例。 Main 是首页和初始化容器,没有同时保留多个实例的业务需求。

设置页会重新创建 Main

Main₁ → Setting → Main₂

设置页结束自身后,任务栈中可能同时保留两个 Main。

MQTT 通知的 singleTop 保护不足

FLAG_ACTIVITY_SINGLE_TOP 只能复用栈顶 Main;Main 被业务页覆盖时点击通知仍会创建新实例。

重复实例会重复初始化和同步

AlarmReceiver 缺少销毁注销

Main 原来只注册动态接收器,没有在 onDestroy() 中进行配对注销。

4. 设计决策

建立同任务栈单实例约束

为 Main 配置 android:launchMode="singleTask"。相比 singleTop, 它能够复用任务栈中非栈顶的 Main,并清理其上方页面。

统一回首页 Intent

Main 提供 createHomeIntent(Context),统一添加:

FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP

启动页、设置页、MQTT 通知和 AI 就绪回退统一复用该入口。

onNewIntent 不重复初始化

Main 的 onNewIntent() 只保存最新 Intent,不调用设备初始化、MQTT 判断或菜品同步。

保持冷启动菜品同步语义

本次保留 syncAllAsync(),不改为六小时缓存同步,也不把同步移动到 onResume()。本次只消除重复 Main 引起的重复同步。

补齐 AlarmReceiver 生命周期

记录注册状态,并在 onDestroy() 中安全注销。

5. 实施范围

业务代码

非目标

6. 实施步骤

  1. Manifest 增加 Main 的 singleTask 兜底。
  2. Main 增加统一首页 Intent 和 onNewIntent()
  3. 设置页、启动页、通知和 AI 就绪回退统一使用首页 Intent。
  4. Main 销毁时注销动态注册的 AlarmReceiver。
  5. 运行单元测试、Debug 构建、Manifest 和入口静态检查。

7. 验收场景

  1. 冷启动只创建一个 Main,初始化和菜品同步各执行一次。
  2. Main → 设置页 → Main 时复用原 Main,不再次同步菜品。
  3. Main → 业务页 → 普通返回时恢复原 Main。
  4. Main → 业务页 → 点击 MQTT 通知时清理业务页并复用原 Main。
  5. Main 不存在时可以正常创建并初始化一次。
  6. 连续十次设置页往返后,任务栈始终只有一个 Main。
  7. Main 销毁时无 Receiver 泄漏和重复注销异常。

8. 风险与控制

singleTask 会清理 Main 上方页面: 只在明确“返回首页”的路径启动 Main。通知点击本身就是进入首页,清理上方页面符合目标行为。

当前 Main 是固定入口卡片,没有依赖设置结果重建的动态列表。后续如果增加刷新需求, 应通过显式 extra 在 onNewIntent() 中处理,不能重新执行全部初始化。

设置页继续依据 FIRST_INIT_FINISH_TAG 区分 LaunchActivity 和 Main, 以保护首次配置流程。

9. 自动化验证结果

当前检测到 3 台在线设备,但用户未指定安装和回归目标,因此没有安装 APK。 设置页返回、通知点击和任务栈实例数量仍需在指定设备上完成真机验收。

10. 结论

通过 singleTask 硬性兜底、统一首页 Intent、onNewIntent 隔离重复初始化和 Receiver 生命周期补齐, 保证 Main 在同一任务栈内只有一个实例。只有真正创建 Main 时才执行初始化和菜品全量同步; 从其他页面或通知返回已有 Main 时不会重复拉取全部菜品。