我的Android应用在被终止时会重复显示两次通知
以下是我在Flutter上开发的应用。iOS运作正常,但Android应用在处于终止状态时会显示两次通知。
我排查后发现,只有Google Play Store应用会显示两次通知,我在本地的调试或发行版构建中都无法重现这种情况。
非常感谢任何帮助或解答。
以下是我的Flutter代码
main.dart
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Future.delayed(const Duration(milliseconds: 300));
await Firebase.initializeApp();
FlutterError.onError = (details) {
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
print("notification main.dart called");
// FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
await PushNotificationService().initNotifications();
await Get.putAsync(() => StorageService().init());
Get.put(ScrollControllerHelper());
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]).then((_) {
runApp(MyApp());
});
}
class MyApp extends StatelessWidget {
final CheckAuthController userController = Get.put(CheckAuthController());
MyApp({super.key});
@override
Widget build(BuildContext context) {
ScreenUtil.init(context);
return GetMaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
title: Strings.appTitle,
initialRoute: userController.isOnBoardingDisplayed == true ? RouteConstants.dashboard : RouteConstants.onboarding,
initialBinding: userController.isOnBoardingDisplayed == true ? DashboardBinding() : OnboardingBindings(),
getPages: Pages.getPages,
theme: ThemeData(
scaffoldBackgroundColor: ColorConstants.appBackgroundColor,
),
defaultTransition: Transition.rightToLeft,
translations: AppTranslation(),
locale: const Locale(Constants.languageGujarati),
fallbackLocale: const Locale(Constants.languageGujarati),
supportedLocales: const [
Locale(Constants.languageEnglish),
Locale(Constants.languageGujarati),
Locale(Constants.languageHindi),
],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
);
}
}
push_notification_service.dart
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:prime_debut_flutter/routes/routes.dart';
import 'package:prime_debut_flutter/views/dashboard/dashboard_controller.dart';
class PushNotificationService {
final Set<String> _handledMessageIds = {};
PushNotificationService._internal();
static final PushNotificationService _instance =
PushNotificationService._internal();
factory PushNotificationService() => _instance;
final _firebaseMessaging = FirebaseMessaging.instance;
bool _isInitialized = false;
final androidChannel = const AndroidNotificationChannel(
'android_channel', 'android_notification_channel',
description: 'notification', importance: Importance.high);
final _localNotifications = FlutterLocalNotificationsPlugin();
void handleMessage(RemoteMessage? message) async {
//when app is minimized and notification come and tap
print("notification in push service detail = $message");
var messageData = message?.data;
print("notification in push service detail = $messageData");
Map? notificationPayload = messageData;
print("notification in push service detail = $notificationPayload");
Map? dataPayload = notificationPayload;
print("notification in push service detail = $dataPayload");
String? postId = dataPayload?['id'];
String? notificationId = dataPayload?['notification_id'];
print("notification in push service postId = $postId");
print("notification in push service notificationId = $notificationId");
if (message == null) return;
var controller = Get.find<DashboardController>();
controller.postId = postId ?? "";
controller.notificationId = notificationId;
controller.getHomePost(isFromNotification: true,isFromReload: false,isForceRefresh: false);
controller.apiReadNotification();
}
void handleMessageMinimize(RemoteMessage? message) async {
//when app is minimized and notification come and tap
print("notification in push service detail = $message");
var messageData = message?.data;
print("notification in push service detail = $messageData");
Map? notificationPayload = messageData;
print("notification in push service detail = $notificationPayload");
Map? dataPayload = notificationPayload;
print("notification in push service detail = $dataPayload");
String? postId = dataPayload?['id'];
String? notificationId = dataPayload?['notification_id'];
print("notification in push service postId = $postId");
if (message == null) return;
var controller = Get.find<DashboardController>();
controller.postId = postId ?? "";
controller.notificationId = notificationId;
controller.getHomePost(isFromNotification: true, isFromReload: false,isForceRefresh: false);
controller.apiReadNotification();
Routes.goToDashboardView(isFromNotification: true, postId: postId,notificationId: notificationId);
}
Future initLocalNotifications() async {
const android = AndroidInitializationSettings('app_logo');
const ios = DarwinInitializationSettings();
const settings = InitializationSettings(android: android, iOS: ios);
await _localNotifications.initialize(
settings,
onDidReceiveNotificationResponse: _onReceived,
onDidReceiveBackgroundNotificationResponse: _onReceived,
);
final platform = _localNotifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await platform?.createNotificationChannel(androidChannel);
_localNotifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()?.requestNotificationsPermission();
}
Future initPushNotifications() async {
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
FirebaseMessaging.instance.getInitialMessage().then((message){
print("notification in push service getInitialMessage = $message");
handleMessageMinimize(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((message){
print("notification in push service onMessageOpenedApp = $message");
//when app is minimized and notification come and tap
handleMessageMinimize(message);
});
FirebaseMessaging.onBackgroundMessage(handleBackgroundMessage);
print("notification in FirebaseMessaging.onMessage");
FirebaseMessaging.onMessage.listen((message) {
final data = message.data;
final dedupeKey = (data['notification_id'] ??
message.messageId ??
'${data['id']}_${message.sentTime?.millisecondsSinceEpoch ?? ''}')
.toString();
if (dedupeKey.isNotEmpty && _handledMessageIds.contains(dedupeKey)) return;
if (dedupeKey.isNotEmpty) _handledMessageIds.add(dedupeKey);
final nidStr = (data['notification_id'] ?? '').toString();
final int notifId = int.tryParse(nidStr)
?? (nidStr.isNotEmpty
? nidStr.hashCode
: DateTime.now().millisecondsSinceEpoch.remainder(100000));
print("========== FCM ==========notifId = $notifId");
print("messageId: ${message.messageId}");
print("from: ${message.from}");
print("sentTime: ${message.sentTime}");
print("notification in local notification ${message.notification?.title}");
final notification = message.notification;
if (notification != null) {
_localNotifications.show(
notifId,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
androidChannel.id, androidChannel.name,
channelDescription: androidChannel.description,
icon: 'app_logo',showWhen: false,)),
payload: jsonEncode(message.toMap()));
}
});
}
Future<void> initNotifications() async {
print("notification initNotifications called");
if (_isInitialized) {
print("PushNotificationService already initialized");
return;
}
_isInitialized = true;
await _firebaseMessaging.requestPermission();
String? apnsToken;
if (Platform.isMacOS || Platform.isIOS) {
apnsToken = await _firebaseMessaging.getAPNSToken();
}
if ((Platform.isIOS && apnsToken != null) || Platform.isAndroid) {
generateFcmToken();
_firebaseMessaging.subscribeToTopic('local_news_set_up');
print("topic subscribed");
}
await initLocalNotifications();
await initPushNotifications();
}
generateFcmToken() async{
final fcmToken = await _firebaseMessaging.getToken();
print("Fcm token --- ${fcmToken}");
}
void unSubscribe(){
_firebaseMessaging.unsubscribeFromTopic('local_news_set_up');
}
}
void _onReceived(NotificationResponse details) async {
//when app is opened at that time navigation occurred
print("notification 1 detail = $details");
var messageData = details.payload;
Map notificationPayload = (jsonDecode(messageData!));
Map dataPayload = (notificationPayload["data"]);
var postId = dataPayload['id'];
String? notificationId = dataPayload['notification_id'];
print("Raw payload: $postId and notificationId = $notificationId");
var controller = Get.find<DashboardController>();
controller.postId = postId;
controller.notificationId = notificationId;
controller.homePostListPage = 1;
controller.getHomePost(isFromNotification: true, isFromReload: false,isForceRefresh: false);
controller.apiReadNotification();
}
Future<void> handleBackgroundMessage(RemoteMessage message) async {
print("notification 2 detail = $message");
print("message back-- ${message.notification}");
}
解决方案
// ✅ ROOT CAUSE
// Duplicate notifications happen when:
// 1. FCM "notification" payload is present → Android shows it automatically
// 2. You ALSO show notification manually using flutter_local_notifications
// Result = 2 notifications (especially in terminated state)
// ❌ PROBLEM (Your current approach)
FirebaseMessaging.onMessage.listen((message) {
final notification = message.notification;
if (notification != null) {
// This creates duplicate notification
_localNotifications.show(
0,
notification.title,
notification.body,
...
);
}
});
// ✅ SOLUTION 1 (RECOMMENDED - Use DATA ONLY payload)
// 🔹 Send this from server (NO "notification" block)
{
"to": "<FCM_TOKEN>",
"data": {
"title": "Hello",
"body": "World",
"id": "123"
}
}
// 🔹 Handle in Flutter
FirebaseMessaging.onMessage.listen((message) {
final data = message.data;
_localNotifications.show(
0,
data['title'],
data['body'],
const NotificationDetails(
android: AndroidNotificationDetails(
'channel_id',
'channel_name',
importance: Importance.max,
priority: Priority.high,
),
),
);
});
// ✅ SOLUTION 2 (If you want to keep "notification" payload)
// 🔹 Then DO NOT show manually
FirebaseMessaging.onMessage.listen((message) {
// ❌ REMOVE manual show()
// Android will automatically display notification
});
// ⚠️ IMPORTANT NOTES
// 1. In TERMINATED state:
// - "notification" payload → handled by Android system (auto display)
// - Your Dart code does NOT run
// 2. In FOREGROUND:
// - You must show notification manually (if using data-only)
// 3. Deduplication logic WILL NOT WORK for terminated state
// because system notifications bypass Dart layer
// ✅ FINAL RECOMMENDATION (Production)
// ✔ Use ONLY "data" payload
// ✔ Handle all notifications manually in Flutter
// ✔ Remove "notification" payload completely
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。