【问题标题】:FlutterFire background isolate communicationFlutterFire 后台隔离通信
【发布时间】:2021-10-19 13:33:21
【问题描述】:

我正在开发一个 VoIP 应用程序,但遇到了后台处理程序问题。我想要的是:发送类型为“呼叫”的推送,然后应用程序将显示呼叫用户界面,稍后如果收到推送类型“挂断”,应用程序将关闭该呼叫用户界面。

在后台处理程序中,我使用单例全局类通过 StreamController 通知挂起事件,并且 CallScreen 小部件将侦听该流以自行关闭。

然后我发现 Flutterfire 将为后台处理程序启动另一个隔离(关闭屏幕),因此它将创建另一个 singleton 类 -> 我无法使用这个新的单例关闭我的 CallScreen UI类。

是否可以用flutterfire的背景隔离做这样的事情?

示例伪代码:

class SingletonGlobal {
  /// singleton class

  final hangUpStreamController = StreamController<HangUpEvent>.broadcast();

  void addHangupEvent(HangUpEvent event) {
    hangUpStreamController.add(event);
  }
}

class CallScreen extends StatefulWidget {
  //// ...
    @override
    void initState() {
      SingletonGlobal().hangUpStreamController.stream.listen((event) => closeCallUI(event));
    }
}

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  await appInit();

  if (message.data.type = 'hangup') {
    SingletonGlobal().addHangupEvent(hangUpEvent);
}

更新

我尝试了 Nitrodon 的解决方案。但正如我所提到的,_firebaseMessagingBackgroundHandler 将创建我的 SingleGlobal 类的另一个实例。我不知道为什么。

“SingletonGlobal#internal”在接收到 Firebase 后台处理程序后再次打印。这意味着它重新创建了 SingletonGlobal。

class SingletonGlobal {
  /// singleton class
  static SingletonGlobal? _instance;
  final _receivePort = ReceivePort();

  factory SingletonGlobal() => _instance ?? SingletonGlobal._internal();


  SingletonGlobal._internal() {
    print('SingletonGlobal#internal');
    IsolateNameServer.registerPortWithName(_receivePort.sendPort, 'global_singleton');
    _receivePort.listen((message) => hangUpStreamController.add);

    _instance = this;
  }

}

【问题讨论】:

    标签: firebase flutter firebase-cloud-messaging dart-isolates


    【解决方案1】:

    您必须使用 onMessageOpenedApp 事件。避免使用任何其他通知插件,因为它会忽略此事件。 顺便说一句,此事件仅在应用程序关闭并通过 FCM 插件的通知打开时才有效。它将为您提供 RemoteMessage 对象,您可以从中检索消息数据。

    【讨论】:

    • 它没有调用 onMessageOpenedApp 事件,因为它没有从终止状态打开应用程序。就我而言,我只是关掉屏幕 -> 接收后台推送通知。
    • 问题是isolate除了channel之外没有任何交流方式;并且firebase(隔离)不允许它。我能想到的最好的方法是使用存储/共享存储在隔离和应用程序之间进行通信
    【解决方案2】:

    隔离之间有多种通信方式。在这种情况下,由于你只需要监听主隔离上的事件,你可以注册一个端口来接收来自后台隔离的挂断事件:

    class SingletonGlobal {
      // Whatever private constructor you're using for this singleton
      SingletonGlobal._() {
        IsolateNameServer.registerPortWithName(_receivePort.sendPort, 'some port name');
        _receivePort.listen(hangUpStreamController.add);
      }
    
      final hangUpStreamController = StreamController<HangUpEvent>.broadcast();
    
      final _receivePort = ReceivePort();
    
      void addHangupEvent(HangUpEvent event) {
        hangUpStreamController.add(event);
      }
    }
    
    void _firebaseMessagingBackgroundHandler(RemoteMessage message) {
      // Do not try to access SingletonGlobal here.
    
      if (message.data.type = 'hangup') {
        // Since the background isolate is still in the same process, you
        // can send objects (which are copied) instead of just basic messages.
        IsolateNameServer.lookupPortByName('some port name')?.send(hangUpEvent);
      }
    }
    

    【讨论】:

    • 很好的解决方案,但是当收到“挂断”事件时。在“_receivePort.listen(hangUpStreamController.add);”中,hangUpStreamController 仍然引用不同于 CallScreen 小部件实际监听的实例。
    • _firebaseMessagingBackgroundHandler 现在看起来像什么?它不应该尝试直接访问GlobalSingleton,因为它位于不同的隔离区中。
    • 我的后台处理程序与您的建议相同:“IsolateNameServer.lookupPortByName('some port name')?.send(hangUpEvent);”
    • 我的问题是:它创建了一个新的 SingletonGlobal 类,所以注册一个同名的端口可能会覆盖旧单例中的旧端口?
    • @SonTieu 诀窍是永远不要尝试在后台隔离中访问SingletonGlobal。我看到你至少最初调用了一个名为appInit() 的方法。如果它创建或访问单例,请不要在后台隔离中调用它。
    猜你喜欢
    • 2012-10-07
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多