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

Flutter应用终止时触发CallKit来电通知问题求助

解决应用终止时FCM触发CallKit失败的问题

问题根源

从错误日志看,核心问题是应用终止后,Flutter的后台隔离线程无法自动加载FirebaseOptions资源,导致Firebase初始化失败;此外,应用终止状态下,Flutter层调用原生插件(如flutter_callkit_incoming)存在权限和能力限制,无法正常触发CallKit弹窗。


解决方案

需要分平台从原生层处理FCM/VoIP推送,绕过Flutter后台线程的限制,同时修复Firebase初始化问题。

1. 修复Flutter端Firebase初始化(补充方案)

在后台处理器中手动传入FirebaseOptions,避免依赖资源文件加载:

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // 手动传入Firebase配置参数,替换为你的项目实际值
  await Firebase.initializeApp(
    options: const FirebaseOptions(
      apiKey: "YOUR_API_KEY",
      appId: "YOUR_APP_ID",
      messagingSenderId: "YOUR_SENDER_ID",
      projectId: "YOUR_PROJECT_ID",
    ),
  );

  if (message.data['type'] == 'call') {
    // 注意:应用终止时此代码可能仍无法触发CallKit,建议优先用原生方案
    String docName = message.data['doc_name'] ?? '';
    String docImage = message.data['doc_image'] ?? '';
    String uuid = message.data['uuid'] ?? '';
    showCallkitIncoming(docName, docImage, uuid);
  }
}

2. Android端:用原生FirebaseMessagingService处理

应用终止时,原生Service可以被FCM唤醒,直接在原生层触发CallKit:

  • 步骤1:创建自定义MessagingService
package com.your.app.package

import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.hiennv.flutter_callkit_incoming.FlutterCallkitIncomingPlugin

class CallFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        
        if (remoteMessage.data["type"] == "call") {
            val uuid = remoteMessage.data["uuid"] ?: ""
            val docName = remoteMessage.data["doc_name"] ?: ""
            val docImage = remoteMessage.data["doc_image"] ?: ""
            
            // 构造CallKit参数,对应Flutter侧的CallKitParams
            val params = hashMapOf(
                "id" to uuid,
                "nameCaller" to docName,
                "appName" to "AppName",
                "avatar" to docImage,
                "type" to 0,
                "textAccept" to "Accept",
                "textDecline" to "Decline",
                "duration" to 30000,
                "android" to hashMapOf(
                    "backgroundColor" to "#2E2727",
                    "isCustomNotification" to true,
                    "isShowLogo" to false,
                    "isShowFullLockedScreen" to true,
                    "ringtonePath" to "system_ringtone_default"
                )
            )
            
            // 直接调用原生插件方法触发CallKit
            FlutterCallkitIncomingPlugin.getInstance().showCallkitIncoming(applicationContext, params)
        }
    }
}
  • 步骤2:在AndroidManifest.xml注册Service
<service
    android:name=".CallFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
  • 步骤3:添加权限
<uses-permission android:name="android.permission.WAKE_LOCK" />

3. iOS端:使用VoIP推送(必须)

苹果限制普通FCM推送在应用终止时无法唤醒应用,必须使用VoIP推送来触发CallKit:

  • 步骤1:开启Xcode能力
    在Xcode项目中,依次开启Push Notifications和Voice over IP能力。
  • 步骤2:实现VoIP推送处理
import PushKit
import FlutterCallkitIncoming

class VoIPPushHandler: NSObject, PKPushRegistryDelegate {
    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        // 将VoIP凭证上传到你的服务器,用于发送VoIP推送
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        guard type == .voIP else { return }
        
        let data = payload.dictionaryPayload["data"] as? [String: Any] ?? [:]
        let uuid = data["uuid"] as? String ?? ""
        let docName = data["doc_name"] as? String ?? ""
        let docImage = data["doc_image"] as? String ?? ""
        
        let params: [String: Any] = [
            "id": uuid,
            "nameCaller": docName,
            "appName": "AppName",
            "avatar": docImage,
            "type": 0,
            "textAccept": "Accept",
            "textDecline": "Decline",
            "ios": [
                "iconName": "Getfit",
                "supportsVideo": false,
                "ringtonePath": "system_ringtone_default"
            ]
        ]
        
        FlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(params)
        completion()
    }
}
  • 步骤3:在AppDelegate初始化VoIP推送
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // 初始化VoIP推送注册表
        let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
        voipRegistry.delegate = VoIPPushHandler()
        voipRegistry.desiredPushTypes = [.voIP]
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

关键注意事项

  • Android端:确保你的FCM推送是数据消息(而非通知消息),只有数据消息会触发onMessageReceived方法,通知消息在应用终止时会直接显示系统通知,不会进入自定义Service。
  • iOS端:必须使用VoIP推送,普通FCM通知无法在应用终止时唤醒应用;同时需要在服务器端发送VoIP格式的推送(而非普通FCM推送)。

内容的提问来源于stack exchange,提问作者Mujahedul Islam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 21:54:54