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
相关产品推荐
相关产品推荐

