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

Android向iOS发送FCM推送失败,求代码示例及解决方案

问题:Android无法向iOS发送FCM推送通知
  • Android到Android:推送正常
  • iOS到iOS:推送正常
  • iOS到Android:推送正常
  • Android到iOS:推送失败

用户怀疑问题出在Kotlin代码中,以下是相关代码:

Kotlin 相关代码

NotificationService 类

class NotificationService: FirebaseMessagingService() {

...

   fun sendNotification(notification: PushNotification) = CoroutineScope(Dispatchers.IO).launch {
        try {
            val response = RetrofitInstance.api.postNotification(notification)
            if (response.isSuccessful) {
                println("RESPONSE : ${Gson().toJson(response)}")
                println("OK")
            }else {
                println("RESPONSE ERROR: ${response.errorBody().toString()}")
            }
        }catch (e: Exception) {
            println("ERROR: ${e.toString()}")
        }
    }

    fun send(title: String, message: String, token: String) {
        PushNotification(NotificationData(title, message), token).also {
            sendNotification(it)
        }
    }
}

调用方式:NotificationService().send("My title", "My message", "A_GIVEN_TOKEN")

RetrofitInstance 类

class RetrofitInstance {

    companion object {
        private val retrofit by lazy {
            Retrofit.Builder()
                .baseUrl(BASE_URL) //BASE_URL = "https://fcm.googleapis.com"
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }

        val api by lazy {
            retrofit.create(NotificationAPI::class.java)
        }
    }
}

NotificationAPI 接口

interface NotificationAPI {

    @Headers("Authorization: key=$SERVER_KEY", "Content-Type:$CONTENT_TYPE") //CONTENT_TYPE = "application/json"
    @POST("fcm/send")
    suspend fun postNotification(
        @Body notification: PushNotification
    ): Response<ResponseBody>
}

PushNotification 数据类

data class PushNotification (
    val data: NotificationData,
    val to: String)

NotificationData 数据类

data class NotificationData (
    val title: String,
    val message: String
)

iOS AppDelegate 代码

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

...

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
          //FirebaseApp.configure()
        Messaging.messaging().delegate = self

        if #available(iOS 10.0, *) {  // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(options: authOptions,completionHandler: {_, _ in })
                  // For iOS 10 data message (sent via FCM
        } else {
            let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)

        }

        application.registerForRemoteNotifications()


        return true
    }
    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
      
    guard let fcmToken = fcmToken else {return}

      let dataDict:[String: String] = ["token": fcmToken]
      NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
      // TODO: If necessary send token to application server.
      // Note: This callback is fired at each app startup and whenever a new token is generated.
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
      
     //DOESEN'T PASS HERE
        guard let aps = userInfo["aps"] as? [String: Any] else {return}
        guard let alert = aps["alert"] as? [String: String] else {return}
        guard let title = alert["title"] else {return}
        guard let body = alert["body"]  else {return}

        let systemSoundID: SystemSoundID = 1007
        AudioServicesPlaySystemSound(systemSoundID)

        
        print("TITLE : ", title)
        print("BODY : ", body)
        
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("ERROOOOR: ", error)
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
    {
        //PASS HERE
        let systemSoundID: SystemSoundID = 1007
               // to play sound
        AudioServicesPlaySystemSound(systemSoundID)
        print(notification.request.content.userInfo)
        completionHandler([.alert, .badge, .sound])
    }
        
}

解决方案

问题核心:iOS接收FCM推送时,若要显示系统通知栏提醒,需要请求体包含notification字段;仅传data字段时,iOS后台/杀死状态下不会触发通知栏提醒,且前台需要手动解析数据。Android则可直接处理data字段。

1. 修改Kotlin端推送数据结构

新增支持notification字段的模型,同时保留data字段兼容Android:

更新PushNotification及相关数据类

data class PushNotification(
    val data: NotificationData? = null,
    val notification: NotificationContent? = null,
    val to: String
)

// 用于iOS系统通知栏显示的内容结构
data class NotificationContent(
    val title: String,
    val body: String,
    val sound: String = "default" // 指定通知音效,可选
)

// 原NotificationData保留,用于Android端数据解析
data class NotificationData(
    val title: String,
    val message: String
)

修改NotificationService的send方法

同时传入data和notification字段,确保两端都能正常处理:

fun send(title: String, message: String, token: String) {
    PushNotification(
        data = NotificationData(title, message),
        notification = NotificationContent(title, message),
        to = token
    ).also {
        sendNotification(it)
    }
}

2. 修复iOS端回调处理

补充后台数据接收逻辑,统一解析data和notification字段:

更新AppDelegate代码

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {

...

func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // 必须启用Firebase配置,否则FCM无法工作
        FirebaseApp.configure()
        Messaging.messaging().delegate = self

        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(options: authOptions,completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            application.registerUserNotificationSettings(settings)
        }

        application.registerForRemoteNotifications()
        return true
    }
    
    // 前台收到通知时触发
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let systemSoundID: SystemSoundID = 1007
        AudioServicesPlaySystemSound(systemSoundID)
        
        let userInfo = notification.request.content.userInfo
        // 解析data字段
        if let data = userInfo["data"] as? [String: String] {
            let title = data["title"] ?? ""
            let message = data["message"] ?? ""
            print("前台Data消息:\(title) - \(message)")
        }
        // 解析notification字段
        if let aps = userInfo["aps"] as? [String: Any], let alert = aps["alert"] as? [String: String] {
            let title = alert["title"] ?? ""
            let body = alert["body"] ?? ""
            print("前台Notification消息:\(title) - \(body)")
        }
        
        completionHandler([.alert, .badge, .sound])
    }
    
    // 点击通知时触发(后台/杀死状态)
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        // 解析逻辑同前台
        if let data = userInfo["data"] as? [String: String] {
            let title = data["title"] ?? ""
            let message = data["message"] ?? ""
            print("点击通知收到Data消息:\(title) - \(message)")
        }
        if let aps = userInfo["aps"] as? [String: Any], let alert = aps["alert"] as? [String: String] {
            let title = alert["title"] ?? ""
            let body = alert["body"] ?? ""
            print("点击通知收到Notification消息:\(title) - \(body)")
        }
        
        completionHandler()
    }
    
    // 后台接收纯数据消息(无通知栏提醒)
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        if let data = userInfo["data"] as? [String: String] {
            let title = data["title"] ?? ""
            let message = data["message"] ?? ""
            print("后台Data消息:\(title) - \(message)")
        }
        completionHandler(.newData)
    }
    
    // 其他原有方法保留...
}

3. 额外检查项

  • 确认iOS项目已启用FirebaseApp.configure()
  • 验证iOS设备的FCM令牌是最新且有效的
  • 检查Firebase控制台中iOS应用的APNs证书是否正确(区分开发/生产环境)

内容的提问来源于stack exchange,提问作者Mr the cat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 17:55:00