Flutter FCM通知在应用处于前台时,onTap回调不起作用;在后台时则可以工作

移动开发 2026-07-09

我正在使用Firebase Cloud Messaging与本地通知实现Flutter的推送通知。

问题在于:

  • 当应用处于 后台已终止 时,点击通知可以正常工作。
  • 但当应用已经在前台运行时,我会收到通知,但点击它却没有任何反应。
  • 点击回调中的日志/输出语句不会被打印。

在前台时,我是手动显示通知。

目前的行为

前台

  • 通知会出现
  • 点击回调不会触发
  • 打印/日志未执行

后台

  • 通知会出现
  • 点击回调能够正确触发

Flutter包

firebase_messaging:
flutter_local_notifications:

来自后端的Firebase载荷

{
  "message": {
    "token": "DEVICE_TOKEN",
    "notification": {
      "title": "Test Title",
      "body": "Test Body"
    },
    "data": {
      "type": "chat",
      "id": "123",
      "taks_id: "123",
      "taskable_id: "123"
    }
  }
}
import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:mre/utils/notification_router.dart';

final class LocalNotificationHelper {
  static final FlutterLocalNotificationsPlugin _plugin =
      FlutterLocalNotificationsPlugin();

  static Future<void> init() async {
    const android = AndroidInitializationSettings('@mipmap/retiq');
    const ios = DarwinInitializationSettings();

    const settings = InitializationSettings(android: android, iOS: ios);

    await _plugin.initialize(
      settings,
      onDidReceiveNotificationResponse: (response) {
        if (response.payload != null) {
          final data = jsonDecode(response.payload!);
          NotificationRouter.handle(data);
        }
      },
    );

    await _createChannels();
  }

  static Future<void> _createChannels() async {
    const channel = AndroidNotificationChannel(
        'high_importance_channel', 'High Importance Notifications',
        importance: Importance.high,
        enableLights: true,
        sound: RawResourceAndroidNotificationSound("notification"));

    await _plugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);
  }

  static Future<void> showNotification(RemoteMessage message) async {
    final data = message.data;

    final title = data['title'] ?? message.notification?.title;
    final body = data['body'] ?? message.notification?.body;

    final androidDetails = AndroidNotificationDetails(
      'high_importance_channel',
      'High Importance Notifications',
      importance: Importance.high,
      priority: Priority.high,
    );

    const iosDetails = DarwinNotificationDetails();

    await _plugin.show(
      DateTime.now().microsecond,
      title,
      body,
      NotificationDetails(
        android: androidDetails,
        iOS: iosDetails,
      ),
      payload: jsonEncode(data),
    );
  }
}
import 'dart:convert';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:mre/utils/notification_helper.dart';
import 'package:mre/utils/notification_router.dart';

class NotificationService {
  static final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  static String? _lastHandledMessageId;

  static Future<void> init() async {
    await _requestPermission();

    /// Foreground messages
    FirebaseMessaging.onMessage.listen(_onForegroundMessage);

    /// App opened from background
    FirebaseMessaging.onMessageOpenedApp.listen(_onMessageOpened);

    /// App opened from terminated state
    final initialMessage = await _messaging.getInitialMessage();
    if (initialMessage != null) {
      _handleNavigation(initialMessage);
    }
  }

  static Future<void> _requestPermission() async {
    await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );
  }

  /// Foreground → show local notification
  static void _onForegroundMessage(RemoteMessage message) {
    LocalNotificationHelper.showNotification(message);
  }

  /// Background tap
  static void _onMessageOpened(RemoteMessage message) {
    _handleNavigation(message);
  }

  /// Navigation handler (DEDUPLICATED)
  static void _handleNavigation(RemoteMessage message) {
    final messageId = message.messageId ?? jsonEncode(message.data);

    if (_lastHandledMessageId == messageId) return;

    _lastHandledMessageId = messageId;

    NotificationRouter.handle(message.data);
  }
}
import 'package:flutter/material.dart';
import 'package:mre/screens/opn_form/view/opn_form_view.dart';
import '../core/mk_route.dart';
import "../utils/context.dart";

class NotificationRouter {
  static void handle(Map<String, dynamic> data) {
    final context = GlobalContext.instance.navigatorKey?.currentContext;

    if (context == null) return;

    final type = data['type'] ?? "";

    switch (type) {

      case 'POS_ACCESS_REQUEST':
        Navigator.pushNamed(context, RoutePage.accessRequest);
        break;
      case 'TASK':
        Navigator.of(context).push(MaterialPageRoute(
          builder: (context) {
            return OpnFormScreen(
              taskableId: data["taskable_id"],
              taskId: data['task_id'],
            );
          },
        ));
        break;
    }
  }
}
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  /// Disable rotation
  if (Platform.isIOS || Platform.isAndroid) {
    SystemChrome.setPreferredOrientations(
        [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
  }
  runApp(AppRoot());
  _initializeAsync();
}

void _initializeAsync() async {
  await FirebaseIntegration().invoke();
  await Future.wait([
    CrashlyticsIntegration().invoke(),
    UpGraderIntegration().invoke(),
    LocalNotificationHelper.init(),
    NotificationService.init()
  ]);
  _requestLocationPermission();
}

解决方案

问题是在你的 main() 中存在一个 竞态条件。你在 LocalNotificationHelper.init() 尚未完成就调用了 runApp(),这意味着在点击发生时 onDidReceiveNotificationResponse 未注册。

dart

runApp(AppRoot());   // app starts immediately
_initializeAsync(); // init runs later, not awaited

将关键初始化放在 runApp() 之前:

dart

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await FirebaseIntegration().invoke();
  await LocalNotificationHelper.init(); // must finish before app renders
  await NotificationService.init();

  runApp(AppRoot());

  // defer non-critical stuff
  unawaited(CrashlyticsIntegration().invoke());
  unawaited(UpGraderIntegration().invoke());
}

后台/终止的点击之所以能工作,是因为FCM会在操作系统层面处理这些,在你的应用代码实际运行之前就已经处理完毕。前台点击直接通过 flutter_local_notifications 处理,因此回调必须在任何点击发生之前注册。

此外,请确保已经为后台通知点击注册了 @pragma('vm:entry-point') 顶层函数,否则在发布构建时它会被tree-shaken(树摇优化)掉:

dart

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse response) {
  if (response.payload != null) {
    final data = jsonDecode(response.payload!);
    NotificationRouter.handle(data);
  }
}

// inside LocalNotificationHelper.init():
await _plugin.initialize(
  settings,
  onDidReceiveNotificationResponse: (response) { ... },
  onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
);
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章