共享静态字段返回哈希码不一致问题排查
问题描述
我正在开发一款简单应用,用于捕获Firebase Cloud Messaging(FCM)推送的通知并存储到内置收件箱中:
InboxController负责处理每条通知的存储逻辑。backgroundMessageReceived用于接收后台消息。backgroundMessageTapped用于处理消息点击事件。
要求仅允许存在一个InboxController实例,因此通过静态字段实现单例模式作为该控制器的唯一引用。
代码实现
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; Future<void> backgroundMessageReceived(RemoteMessage message) { final inboxController = InboxController.getInstance(); print('InboxController2: ${inboxController.hashCode}'); return Future<void>.value(); } void backgroundMessageTapped(RemoteMessage message) { final inboxController = InboxController.getInstance(); print('InboxController3: ${inboxController.hashCode}'); } Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); final fba = await Firebase.initializeApp(); final fcm = FirebaseMessaging.instance; FirebaseMessaging.onBackgroundMessage(backgroundMessageReceived); FirebaseMessaging.onMessageOpenedApp.listen(backgroundMessageTapped); const project = MaterialApp( home: InboxScreen(), ); runApp(project); } class InboxController { InboxController._(); static InboxController? _instance; factory InboxController.getInstance() { return _instance ??= InboxController._(); } } class InboxScreen extends StatefulWidget { const InboxScreen({super.key}); @override State<InboxScreen> createState() { return _InboxScreenState(); } } class _InboxScreenState extends State<InboxScreen> { InboxController? inboxController; void assignInitValues() { inboxController = InboxController.getInstance(); print('InboxController1: ${inboxController.hashCode}'); } @override void initState() { assignInitValues(); super.initState(); } @override Widget build(BuildContext context) { return const Scaffold( body: Placeholder(), ); } }
预期输出
InboxController1: 476298463 InboxController2: 476298463 InboxController3: 476298463
实际输出
InboxController1: 476298463 InboxController2: 650985069 InboxController3: 476298463
疑问
Firebase Cloud Messaging库调用backgroundMessageReceived时,InboxController.getInstance返回的实例哈希码与其他调用不一致,这表明创建了两个InboxController实例,请问这是怎么回事?
解答
这是因为FCM的**后台消息处理函数运行在独立的隔离区(Isolate)**中,和Flutter主应用所在的隔离区不是同一个上下文。
Dart中的静态变量是隔离区本地的,每个隔离区都会拥有自己的静态变量副本。当backgroundMessageReceived在后台隔离区被调用时,它访问的InboxController._instance是该隔离区独有的,和主应用隔离区的_instance不是同一个对象,因此会创建新的实例,导致哈希码不同。
而onMessageOpenedApp的回调是运行在主应用的隔离区中的,所以它获取到的实例和主屏幕中创建的是同一个。
解决方法
- 避免在后台消息处理中直接使用单例:如果需要在后台处理中操作数据,应该使用共享存储(比如
SharedPreferences、本地数据库如Hive/SQFlite)来存储通知,而不是依赖内存中的单例。 - 使用隔离区通信:如果必须和主应用的单例交互,可以通过
SendPort和ReceivePort在后台隔离区和主隔离区之间传递消息,让主隔离区的单例来处理存储逻辑。
举个简单的调整示例,改用本地存储处理后台消息:
import 'package:shared_preferences/shared_preferences.dart'; Future<void> backgroundMessageReceived(RemoteMessage message) async { final prefs = await SharedPreferences.getInstance(); List<String> notifications = prefs.getStringList('notifications') ?? []; notifications.add(message.data.toString()); await prefs.setStringList('notifications', notifications); print('后台通知已存储'); return Future<void>.value(); }
这样不管是主隔离区还是后台隔离区,都能访问到同一个共享存储,保证数据一致性。
内容的提问来源于stack exchange,提问作者YGranja
相关产品推荐
相关产品推荐

