【问题标题】:Flutter - firebase FCM messages not working on Testflight release builds at allFlutter - Firebase FCM 消息根本不适用于 Testflight 发布版本
【发布时间】:2021-12-04 02:48:32
【问题描述】:

前言:

我的应用是基于 Flutter 的 - 但需要原生代码实现才能使 FCM 消息正常工作,请参阅下文了解更多详情

GitHub问题#154供参考。


我在获取 FCM 通知在 iOS 上工作时遇到了巨大的麻烦,特别是在我发布到 Testflight 的应用程序上。我已经被这个问题困扰了一个星期,完全不知道如何继续。

问题

当使用 Xcode/Android Studio 在我的设备上使用调试/发布版本在本地运行时,会在后台、前台等中收到通知。将完全相同的应用程序上传到 Testflight 时,不是单个通知将通过 FCM 发送。

这是至关重要的,因为 FCM 提供 VoIP 通知,这些通知在 Testflight 上没有收到,这非常令人痛苦

问题和解决方案?

我发现了 2 个问题(here & here),似乎都表明这是一个 APNS 证书问题(APNS -> Firebase)。我已重新创建证书并将其添加到 Firebase 控制台(对所有证书生成操作使用相同的 .csr 文件)

设置/配置:

  • APNS 生成密钥并添加到 Firebase

  • 能力:

尝试过:

<key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string>

与:

<key>FirebaseAppDelegateProxyEnabled</key>
<string>0</string>

并与:

<key>FirebaseAppDelegateProxyEnabled</key>
<boolean>false</boolean>
  • 背景模式:
    <key>UIBackgroundModes</key>
    <array>
        <string>audio</string>
        <string>bluetooth-central</string>
        <string>external-accessory</string>
        <string>fetch</string>
        <string>location</string>
        <string>processing</string>
        <string>remote-notification</string>
        <string>voip</string>
        <string>remote-notification</string>
    </array>

教程/来源:

Swift 代码:(定位 >=10.0)


import UIKit
import CallKit
import Flutter
import Firebase
import UserNotifications
import GoogleMaps
import PushKit
import flutter_voip_push_notification
import flutter_call_kit

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        // run firebase app
        FirebaseApp.configure()
        
        // setup Google Maps
        GMSServices.provideAPIKey("google-maps-api-key")
        
        // register notification delegate
        UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
        
        GeneratedPluginRegistrant.register(with: self)
        
        // register VOIP
        self.voipRegistration()
        
        // register notifications
        application.registerForRemoteNotifications();
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    // Handle updated push credentials
    public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        // Process the received pushCredentials
        FlutterVoipPushNotificationPlugin.didUpdate(pushCredentials, forType: type.rawValue);
    }
    
    // Handle incoming pushes
    public func pushRegistry(_ registry: PKPushRegistry,
                             didReceiveIncomingPushWith payload: PKPushPayload,
                             for type: PKPushType,
                             completion: @escaping () -> Swift.Void){
        
        FlutterVoipPushNotificationPlugin.didReceiveIncomingPush(with: payload, forType: type.rawValue)
        
        let signalType = payload.dictionaryPayload["signal_type"] as! String
        if(signalType == "endCall" || signalType == "rejectCall"){
            return
        }
        
        let uuid = payload.dictionaryPayload["session_id"] as! String
        let uID = payload.dictionaryPayload["caller_id"] as! Int
        let callerName = payload.dictionaryPayload["caller_name"] as! String
        let isVideo = payload.dictionaryPayload["call_type"] as! Int == 1;
        FlutterCallKitPlugin.reportNewIncomingCall(
            uuid,
            handle: String(uID),
            handleType: "generic",
            hasVideo: isVideo,
            localizedCallerName: callerName,
            fromPushKit: true
        )
        completion()
    }
    
    // Register for VoIP notifications
    func voipRegistration(){
        // Create a push registry object
        let voipRegistry: PKPushRegistry = PKPushRegistry(queue: DispatchQueue.main)
        // Set the registry's delegate to self
        voipRegistry.delegate = self
        // Set the push type to VoIP
        voipRegistry.desiredPushTypes = [PKPushType.voIP]
    }
}

public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    if #available(iOS 14.0, *) {
        completionHandler([ .banner, .alert, .sound, .badge])
    } else {
        completionHandler([.alert, .sound, .badge])
    }
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    print(deviceToken)
    Messaging.messaging().apnsToken = deviceToken;
}

颤动 main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await initializeDateFormatting();
  setupLocator();
  var fcmService = locator<FCMService>();

  FirebaseMessaging.onBackgroundMessage(FCMService.handleFirebaseBackgroundMessage);
  FirebaseMessaging.onMessage.listen((event) {
    print("Foreground message");
    Fluttertoast.showToast(msg: "Received onMessage event");
    FCMService.processCallNotification(event.data);
  });
  FirebaseMessaging.onMessageOpenedApp.listen((event) {
    print("On message opened app");
    Fluttertoast.showToast(msg: "Received onMessageOpenedAppEvent");
    FCMService.handleInitialMessage(event);
  });
  FirebaseMessaging.instance.getInitialMessage().then((value) {
    Fluttertoast.showToast(msg: "Received onLaunch event");
    if (value != null) {
      FCMService.handleInitialMessage(value);
    }
  });

  initConnectyCube();
  runApp(AppProviders());
}

FCMService.dart

  // handle any firebase message
  static Future<void> handleFirebaseBackgroundMessage(RemoteMessage message) async {
    print("Received background message");
    Fluttertoast.showToast(msg: "Received Firebase background message");
    await Firebase.initializeApp();
    setupLocator();
    var fcmService = locator<FCMService>();
    fcmService.init();

    _handleMessage(message, launchMessage: true);
  }

测试:

测试是在 2 部物理 iPhone(6s 和 8)上完成的。在使用(调试和发布)模式直接从 Mac(Android Studio 和 XCode)构建时,两者都可以与 Firebase FCM 一起使用。从 TestFlight 下载相同的文件时两者都不起作用。

如果有任何可以提供关于错误配置、设置错误或缺少/不正确的 Swift 代码,或者仅仅是错误或遗漏的见解,我们将不胜感激。

【问题讨论】:

  • 你确定你正在做一个实际的发布版本吗?发布构建和存档(必然)不是一回事,具体取决于您构建构建的方式。调试不使用与发布相同的证书。调试使用 APNS_SANDBOX 而不是 APNS。甚至可能是您的服务器或 Firebase 上的错误配置。
  • 说实话我不知道flutter。我只知道推送通知。我不知道颤振对构建方案或配置有什么影响
  • 但是您需要为 PN 做的大部分事情都在 FB 和服务器上。 iOS 只是整个 PN 难题的一小部分。它在 iOS 上实际上比在你的 BE 或 FB 上要简单。 FB 必须配置正确的证书(或最近的 api 密钥),并且 Be 必须将 pn 请求发送到正确的 FB 应用程序才能正常工作。
  • 让安卓远离画面。 PN 在 android 上的工作方式与 iOS 不同。在 iOS 中,您需要特定的证书来进行调试和发布以及您可能拥有的任何其他构建配置。让 PN 在本地工作并不意味着 firebase 设置正确。您需要在 apple developer 和 firebase 上完成设置,并确保所有内容都与您发送到 TF 的构建相匹配。
  • 我说的是推送通知证书。它们不是由 Xcode 管理的。还有最近的 api 密钥。您可能正在使用它们。 Fb 必须配置为将 PN 发送到正确的证书/api 密钥和 APNS。还要确保您的应用程序要求正确访问 PN。如果您的应用程序没有要求用户允许推送通知,则问题出在 iOS 上。通常这是在启动时完成,但也可以在任何时间完成,例如登录。

标签: ios swift firebase firebase-cloud-messaging apple-push-notifications


【解决方案1】:

前言: 问题是我的。

TL;DR 仅将 CubeEnvironment 的 1 个引用更改为 PRODUCTION


有多个位置可以更改CubeEnvironment

使用建议,最好将其添加到“CallManagerService”的init() 方法中:

    bool isProduction = bool.fromEnvironment('dart.vm.product');
    parameters.environment = isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;

调试(进程): 调试过程(对 Swift 和 XCode 有点陌生)本来可以更好。我考虑了各种配置文件、aps-environment 设置等。

由于该问题仅发生在 Testflight 上,它使调试变得更具挑战性和耗时,因为上传调试版本有自己的一组问题

最后我添加了一堆日志,其中最重要的是CB-SDK 调试条目(收到通知时):

[
  {
    "subscription": {
      "id": sub id,
      "_id": "insert sub id",
      "user_id": cube_user_id,
      "bundle_identifier": "insert bundle id",
      "client_identification_sequence": "insert client id",
      "notification_channel_id": 6,
      "udid": "insert-uuid",
      "platform_id": 1,
      "environment": "development",
      "notification_channel": {
        "name": "apns_voip"
      },
      "device": {
        "udid": "insert-uuid",
        "platform": {
          "name": "ios"
        }
      }
    }
  }
]

具体来说,如下条目。

environment": "development

这是由于 APS 使用了 2 个不同的推送通知环境,每个环境都有自己的证书(证书分配给推送通知可能来自的唯一 URL)。 aps-environment 设置为 'production(请在开始上传之前查看上传存档屏幕),但我收到了 development 环境通知 - 这需要修复。

查看我的代码,我终于找到了问题(并修复了上面提到的)。

【讨论】:

    【解决方案2】:

    这个问题有时会让人抓狂,即使他们在正确的场景中应用一切,所以请尝试检查以下内容:

    1- 在您的苹果开发者帐户中,确保您只有一个Apple Push Services Certificate 分配给应用标识符(Bundle ID),请避免重复。

    2- 如果您使用 APNs 密钥接收通知,您必须确保在您的应用上传到 TestFlight 或 AppStore 时将其设置为生产模式

    func application(_ application: UIApplication,
    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
        
        print("APNs Device Token: \(token)")
        Messaging.messaging().apnsToken = deviceToken
        Messaging.messaging().setAPNSToken(deviceToken, type: .prod)
        
    }
    

    注意:TestFlight 被视为发布(生产)模式而不是沙盒模式

    【讨论】:

    • 关于 #2 的澄清点 - 如何设置它。你是指*.entitlements文件,具体设置aps-environmentproduction
    • aps-environment 设置为production,我在apnsToken = deviceToken 之后添加了setAPNSToken() 函数,这没有帮助。关于#1,仅存在 1 个 APS 证书,指的是正确的包名称(在开发人员网站上检查)。
    猜你喜欢
    • 2017-09-09
    • 2017-04-29
    • 2021-06-21
    • 1970-01-01
    • 2023-03-23
    • 2020-10-21
    • 2021-06-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多