Notification Service Extension:在iOS上显示前处理内容

作者: IT Sectr 发布日期: 2026-03-21 阅读时间: 8 分钟

Notification Service Extension 是一个iOS扩展,它在推送通知显示给用户之前拦截它,并允许修改内容。该扩展可以解密加密数据、下载媒体附件、更改文本或添加自定义字段。根据 Apple Developer, 2025Notification Service Extension 有最多30秒的时间在后台执行任务。

要点

  • Notification Service Extension 是用于在显示前处理推送通知的iOS扩展。
  • 该扩展继承自 UNNotificationServiceExtension 并实现 didReceive 方法。
  • 最多有30秒的后台执行时间用于下载和处理数据。
  • 主要场景:解密payload、下载图片和视频、内容验证。
  • 该扩展可以延迟通知的显示或通过 contentHandler 完全取消它。

什么是 Notification Service Extension

Notification Service Extension 是iOS中的一个应用扩展,在推送通知显示之前启动。它允许服务器发送最小的payload,并由扩展用内容补充:下载图像、解密数据、替换文本。用户看到的是已经准备好的、处理过的通知。

它是如何工作的

当iOS收到推送通知时,系统会检查该应用程序是否存在扩展。如果扩展已注册,iOS会在后台进程中启动它,并通过 didReceive(_:withContentHandler:) 方法传递通知内容。扩展处理内容并使用修改后的内容调用 contentHandler。如果扩展未能在30秒内完成,系统将显示原始通知。

何时使用 Service Extension

在通知内容需要在设备上处理的场景中,Service Extension 是必需的。安全通信:服务器发送加密的payload,扩展在本地解密。富媒体通知:服务器发送图像URL,扩展下载并附加它。动态本地化:扩展用设备语言替换文本。

场景无扩展有扩展
图像不支持下载并显示
加密服务器保存密钥在设备上解密
文本在服务器上固定设置动态替换
验证不检查取消恶意通知

限制和执行时间

Notification Service Extension 在受限环境中运行。最大执行时间为 30秒。扩展无法访问应用程序的主存储(App Group 除外)。扩展的大小限制为50 MB。超时时,系统使用原始内容调用 contentHandler,所有更改都将丢失。

创建扩展和配置 Info.plist

Xcode 中,通过创建 Notification Service Extension 类型的新目标来添加 Notification Service Extension。Xcode 会生成一个继承自 UNNotificationServiceExtension 的 NotificationService 类模板,包含两个方法:didReceive 和 serviceExtensionTimeWillExpire。

向项目添加目标

在 Xcode 中选择 File → New → Target → Notification Service Extension。指定名称(例如 PushNotificationService)和 Swift 语言。确保目标已添加到主应用程序并且签名正确。创建后将生成 NotificationService.swift 类及其基本实现。

扩展的 Info.plist 结构

扩展的 Info.plist 包含 NSExtension 键,其子键为 NSExtensionPointIdentifier (com.apple.usernotifications.service) 和 NSExtensionPrincipalClass(控制器的名称)。可选地,可以指定 NSExtensionAttributes 以及扩展的激活规则。这些设置由 Xcode 自动生成。

基本扩展示例

基本实现重写 didReceive,修改通知内容并调用 contentHandler。如果处理时间过长,将调用 serviceExtensionTimeWillExpire,此时需要以当前状态完成工作。

swift
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        request: UNNotificationRequest,
        withContentHandler handler: @escaping (UNNotificationContent) -> Void
    ) {
        contentHandler = handler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent {
            bestAttemptContent.title = "[已处理] \(bestAttemptContent.title)"
            contentHandler?(bestAttemptContent)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

解密密文payload

Service Extension 的关键应用之一是在设备上 解密 通知内容。服务器发送加密的payload,扩展使用存储在 Keychain 或 App Group 中的密钥进行解密。这保证了内容在传输过程中无法被拦截。

安全交付架构

服务器使用对称密钥 (AES-256) 加密通知正文。加密密钥在客户端和服务器之间预先协商。扩展在 payload 的数据字段中接收加密字符串,将其解密并放置到通知字段中。用户密钥 可以在身份验证后从 Keychain 获取。

swift
override func didReceive(
    request: UNNotificationRequest,
    withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
    guard let content = request.content.mutableCopy()
        as? UNMutableNotificationContent else {
        handler(request.content)
        return
    }

    guard let encryptedData = content.userInfo["encrypted_data"]
        as? String else {
        handler(content)
        return
    }

    let decrypted = CryptoService.decrypt(encryptedData)
    content.body = decrypted.body
    content.title = decrypted.title
    handler(content)
}

密钥的安全存储

解密密钥不应存储在扩展代码或 NSUserDefaults 中。使用 iOS Keychain 并通过 App Group 访问,以便应用程序和扩展都可以读取密钥。要在客户端生成密钥,请使用带有 AES-256-GCM 算法的 Security.framework。

下载和附加媒体

Notification Service Extension 最常见的用途是下载图像、GIF 和视频以便在通知中显示。服务器发送媒体文件的 URL,扩展下载它,将其保存到临时目录并创建 UNNotificationAttachment。

媒体下载和附加过程

扩展从通知的 payload 字段接收图像 URL。使用 URLSession,扩展将文件下载到临时目录。下载完成后,使用本地 URL 创建 UNNotificationAttachment。附件被传递给修改后的内容。iOS 自动 在标准界面或 Notification Content Extension 中显示图像。

swift
private func downloadAndAttachMedia(
    content: UNMutableNotificationContent,
    mediaUrl: String,
    handler: @escaping (UNNotificationContent) -> Void
) {
    guard let url = URL(string: mediaUrl) else {
        handler(content)
        return
    }

    let task = URLSession.shared.downloadTask(with: url) { localUrl, _, error in
        guard let localUrl = localUrl, error == nil else {
            handler(content)
            return
        }

        let attachment = try? UNNotificationAttachment(
            identifier: "media",
            url: localUrl,
            options: nil
        )

        if let attachment = attachment {
            content.attachments = [attachment]
        }
        handler(content)
    }
    task.resume()
}

支持的媒体格式

iOS 支持以下格式在通知中显示:JPEG、PNG、GIF(静态)、MPEG-4 视频(最大50 MB)。对于音频文件,支持 MP3、AAC 和 ALAC。重要:所有媒体文件必须在30秒限制内下载。对于大文件,建议使用服务器端裁剪或渐进式下载。

时间管理和备用方案

管理30秒限制 是开发 Notification Service Extension 的关键任务。如果扩展未能完成处理,系统将调用 serviceExtensionTimeWillExpire 并显示原始内容。必须为每种处理类型预设备用方案。

优先级策略

按优先级划分任务。关键修改(解密、基本本地化)首先执行。可选改进(图像下载、文本丰富化)其次执行。对于网络请求,使用 带超时的 URLSession,以避免将整个限制花费在单个操作上。

错误时的备用方案

如果图像下载失败或 payload 解密返回错误,扩展应使用原始内容调用 contentHandler。切勿在不调用 contentHandler 的情况下结束扩展——这会导致通知丢失。安全备用 应始终在扩展代码中预设。

swift
override func didReceive(
    request: UNNotificationRequest,
    withContentHandler handler: @escaping (UNNotificationContent) -> Void
) {
    let content = (request.content.mutableCopy()
        as? UNMutableNotificationContent) ?? request.content

    // 关键任务:解密
    var decryptedContent = tryDecryptPayload(content)

    // 可选任务:媒体
    guard let mediaUrl = decryptedContent.userInfo["media_url"]
        as? String else {
        handler(decryptedContent)
        return
    }

    downloadAndAttachMedia(
        content: decryptedContent,
        mediaUrl: mediaUrl,
        handler: handler
    )
}

测试扩展

要测试 Notification Service Extension,请使用 Xcode:选择扩展目标,在模拟器上运行,并通过终端或 Firebase Console 发送推送通知。通过 os_log 记录 每个处理阶段——这有助于诊断定时和加载错误问题。

常见问题

如果扩展未能在30秒内完成会怎样?

系统将调用 serviceExtensionTimeWillExpire,然后显示原始通知而不做任何更改。所有下载的文件和修改都将被丢弃。

能否从扩展取消通知的显示?

是的,如果使用空内容(具有空字段的 UNNotificationContent)调用 contentHandler,通知将不会显示。这用于过滤 垃圾通知 或不正确的数据。

如何传递用于下载媒体的授权令牌?

令牌可以在通知 payload 的 userInfo 中传递,或通过 App Group 从 Keychain 获取。不建议将令牌存储在扩展的 UserDefaults 中。

扩展是否需要特殊的证书配置?

扩展必须使用与主应用程序相同的开发者证书签名。对于生产环境,需要 生产证书 并启用 Push Notifications 功能。

如何在真实设备上调试扩展?

将设备连接到 Xcode,在启动方案中选择扩展目标,并通过 Firebase Console 发送推送通知。扩展中的 断点 与主应用程序中的工作方式相同。

总结

  • Notification Service Extension 是用于在显示前在后台处理推送通知的iOS扩展。
  • 最大执行时间为 30秒,之后系统显示原始内容。
  • 主要用途:payload解密、媒体下载、内容验证和本地化。
  • 扩展继承自 UNNotificationServiceExtension,包含 didReceive 和 serviceExtensionTimeWillExpire 方法。
  • 使用 URLSession 下载媒体,使用 iOS Keychain 存储密钥。
  • 始终预设备用方案——出现错误时使用原始内容调用 contentHandler。
  • 将 Service Extension 与 Content Extension 结合使用,以完全控制通知。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读