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

