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

Firebase下Android与iOS通知兼容:全状态自定义处理方案咨询

解决iOS与Android通知跨状态自定义处理的兼容问题

核心矛盾

iOS端需要通知payload包含notification字段(可空),才能保证应用在活跃、后台、终止状态下都能触发自定义处理逻辑;而Android端恰好相反,不能携带notification字段,只能保留data字段——否则应用在后台或终止状态时,系统会直接接管通知展示,无法执行下载图片这类自定义操作。

解决方案思路

通过后端区分设备类型下发不同payload + 客户端各自针对性处理,实现双端全状态下的通知自定义展示。

1. 后端payload适配

  • iOS设备:下发包含notification字段(填充基础通知内容)+ data字段(存储自定义业务数据)的payload,同时必须开启APNS的mutable-content参数,允许iOS修改通知内容。
  • Android设备:仅下发data字段,把通知展示内容(标题、正文)和自定义数据都放在data中。
示例Payload

iOS:

{
  "notification": {
    "title": "新消息提醒",
    "body": "您有一条未读消息"
  },
  "data": {
    "custom_id": "12345",
    "image_url": "https://example.com/avatar.jpg"
  },
  "apns": {
    "payload": {
      "aps": {
        "mutable-content": 1
      }
    }
  }
}

Android:

{
  "data": {
    "title": "新消息提醒",
    "body": "您有一条未读消息",
    "custom_id": "12345",
    "image_url": "https://example.com/avatar.jpg"
  }
}

2. 客户端自定义处理

Android端(基于onMessageReceived)

当payload仅包含data字段时,无论应用处于活跃、后台还是终止状态,都会触发onMessageReceived方法。我们可以在这里完成所有自定义操作,再手动构建并展示通知:

override fun onMessageReceived(message: RemoteMessage) {
    super.onMessageReceived(message)
    // 从data中提取所有数据
    val title = message.data["title"] ?: ""
    val body = message.data["body"] ?: ""
    val imageUrl = message.data["image_url"]
    val customId = message.data["custom_id"]

    // 执行自定义操作,比如下载图片
    imageUrl?.let { url ->
        downloadRemoteImage(url) { bitmap ->
            buildAndShowNotification(title, body, bitmap, customId)
        }
    } ?: run {
        buildAndShowNotification(title, body, null, customId)
    }
}

private fun buildAndShowNotification(title: String, body: String, bitmap: Bitmap?, customId: String?) {
    // 创建通知渠道(Android 8.0+必填)
    val channelId = "custom_notification_channel"
    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(channelId, "自定义通知", NotificationManager.IMPORTANCE_DEFAULT)
        notificationManager.createNotificationChannel(channel)
    }

    // 构建通知
    val builder = NotificationCompat.Builder(this, channelId)
        .setSmallIcon(R.drawable.ic_notify)
        .setContentTitle(title)
        .setContentText(body)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    
    // 添加大图样式(如果有下载好的图片)
    bitmap?.let {
        builder.setLargeIcon(it)
        builder.setStyle(NotificationCompat.BigPictureStyle().bigPicture(it))
    }

    // 发送通知
    notificationManager.notify(System.currentTimeMillis().toInt(), builder.build())
}

// 简化的图片下载方法
private fun downloadRemoteImage(url: String, callback: (Bitmap?) -> Unit) {
    Thread {
        val bitmap = try {
            val inputStream = URL(url).openStream()
            BitmapFactory.decodeStream(inputStream)
        } catch (e: Exception) {
            null
        }
        runOnUiThread { callback(bitmap) }
    }.start()
}
iOS端(基于didReceive(_:withContentHandler:))

因为payload携带了notification字段且开启了mutable-content,应用在所有状态下都会触发该方法。我们可以在这里下载图片、修改通知内容,最后将处理后的内容返回给系统:

func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    let userInfo = request.content.userInfo
    let imageUrlString = userInfo["image_url"] as? String
    guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
        contentHandler(request.content)
        return
    }

    // 没有图片直接返回原内容
    guard let imageUrl = URL(string: imageUrlString ?? "") else {
        contentHandler(mutableContent)
        return
    }

    // 异步下载图片并添加为通知附件
    URLSession.shared.dataTask(with: imageUrl) { data, _, error in
        guard let data = data, let image = UIImage(data: data), error == nil else {
            contentHandler(mutableContent)
            return
        }

        // 将图片保存到临时目录
        let tempDir = FileManager.default.temporaryDirectory
        let imageFileName = UUID().uuidString + ".jpg"
        let imageFileUrl = tempDir.appendingPathComponent(imageFileName)
        
        do {
            try image.jpegData(compressionQuality: 0.8)?.write(to: imageFileUrl)
            let attachment = try UNNotificationAttachment(identifier: "notification_image", url: imageFileUrl, options: nil)
            mutableContent.attachments = [attachment]
            contentHandler(mutableContent)
        } catch {
            contentHandler(mutableContent)
        }
    }.resume()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:31:46