4. FCM 后台通知点击路由解析

FCM 后台通知点击路由解析 学习卡片

后台通知点击的 Intent 数据链路

当应用处于后台时,FCM 通知由系统托盘展示,点击后系统会启动应用主入口 Activity,并把通知携带的 data 以 Intent extras 形式注入。与前台 onMessageReceived 手动构建 PendingIntent 不同,后台点击的 Intent 由 FCM SDK 与系统共同组装,路由解析必须从 getIntent().getExtras() 中读取。

关键约定:后台通知的 data 键值会原样进入 Intent extras,但 google.message_id、from、collapse_key 等由 SDK 附加;自定义业务字段(如 type、target_id)需在服务端 payload 的 data 中显式声明。冷启动与热启动的差异在于:冷启动时 Activity 首次创建,Intent 即携带数据;热启动时 Activity 已存在,新 Intent 通过 onNewIntent 送达,必须调用 setIntent(intent) 更新,否则后续读取会拿到旧数据。

服务端 FCM Payload notification: title / body data: type = "order_detail" target_id = "A1024" 系统通知托盘 应用处于后台 系统展示通知 点击 → 启动主入口 data 注入 Intent extras 冷启动路径 Activity 首次创建 onCreate(intent) intent.extras 含 data ✓ 直接解析路由 热启动路径 Activity 已存在 onNewIntent(intent) 必须 setIntent(intent) ✓ 再解析路由 RouteDispatcher.dispatch(extras) 读取 type / target_id → 选择目标页面 OrderDetailActivity PromotionActivity MainActivity Intent Extras 结构 type String target_id String google.message_id SDK 附加 from SDK 附加 collapse_key SDK 附加 ⚠ 热启动需 setIntent()

路由解析实现

在 MainActivity 中统一处理冷启动与热启动,核心是提取 extras 后交给路由分发器。以下代码可直接放入项目运行,RouteDispatcher 根据 type 字段决定跳转目标。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 冷启动:首次创建时解析
        handleNotificationIntent(intent)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        // 热启动:必须更新 Intent,否则 getIntent() 仍是旧数据
        setIntent(intent)
        handleNotificationIntent(intent)
    }

    private fun handleNotificationIntent(intent: Intent?) {
        val extras = intent?.extras ?: return
        val type = extras.getString("type") ?: return
        val targetId = extras.getString("target_id")

        when (type) {
            "order_detail" -> {
                startActivity(Intent(this, OrderDetailActivity::class.java).apply {
                    putExtra("order_id", targetId)
                })
            }
            "promotion" -> {
                startActivity(Intent(this, PromotionActivity::class.java).apply {
                    putExtra("promo_id", targetId)
                })
            }
            else -> {
                // 未知类型,留在主页
            }
        }
    }
}

热启动陷阱:若 onNewIntent 中不调用 setIntent(intent),后续任何 getIntent() 调用都会返回启动时的旧 Intent,导致第二次点击通知时路由到错误页面或丢失数据。这是后台通知路由最常见的 bug。

冷启动与热启动的验证方法

用 adb 模拟两种场景,观察 Logcat 中路由日志确认行为正确:

  • 冷启动验证:先 adb shell am force-stop 包名 杀掉进程,再通过 FCM 控制台发送带 type=order_detail 的通知,点击后应进入 OrderDetailActivity 且 order_id=A1024。
  • 热启动验证:保持应用在后台(按 Home 键),发送 type=promotion 的通知,点击后应进入 PromotionActivity;再发送 type=order_detail 的通知,点击后应正确切换到 OrderDetailActivity,验证 setIntent 生效。

调试技巧:在 handleNotificationIntent 入口打印 Log.d("Route", "extras=$extras"),可直观看到冷/热启动时 extras 的差异。若热启动时 extras 为空或为旧值,检查 onNewIntent 是否被正确覆写且调用了 setIntent。