You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

共享静态字段返回哈希码不一致问题排查

问题描述

我正在开发一款简单应用,用于捕获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的回调是运行在主应用的隔离区中的,所以它获取到的实例和主屏幕中创建的是同一个。

解决方法

  1. 避免在后台消息处理中直接使用单例:如果需要在后台处理中操作数据,应该使用共享存储(比如SharedPreferences、本地数据库如Hive/SQFlite)来存储通知,而不是依赖内存中的单例。
  2. 使用隔离区通信:如果必须和主应用的单例交互,可以通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:45:03