Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送
十二、使用 Notification Service Extension 拦截并修改通知
iOS 10 中添加了两个与通知相关的 extension:Service Extension 和 Content Extension。本文先介绍下前者。
1,基本介绍
- Service Extension 目前只对远程推送的通知有效。
- Service Extension 可以让我们有机会在收到远程推送通知后,展示之前对通知内容进行修改。
通过本机截取推送并替换内容的方式,我们可以实现端到端 (end-to-end) 的推送加密:
我们在服务器推送 payload 中加入加密过的文本,在客户端接到通知后使用预先定义或者获取过的密钥进行解密,然后立即显示。
这样一来,即使推送信道被第三方截取,其中所传递的内容也还是安全的。使用这种方式来发送密码或者敏感信息,对于一些金融业务应用和聊天应用来说,应该是必备的特性。
2,使用说明
(1)首先我们点击”File” -> “New” -> “Target…”,使用 NotificationService 的模板来创建一个 NotificationService。
(2)NotificationService 的模板已经自动为我们生成了一些基本代码,这里对其稍作修改(自动给通知内容后面加上一个小尾巴)。NotificationService 里特别要注意如下两个方法:
1,didReceive
该方法中有一个等待发送的通知请求。我们通过修改这个请求中的 content 内容,然后在限制的时间内将修改后的内容通过调用 contentHandler 返还给系统,就可以显示这个修改过的通知了。
2,serviceExtensionTimeWillExpire
在一定时间内如果没有调用 contentHandler 的话,系统会调用这个方法,来告诉我们时间到了:
- 我们可以什么都不做,这样的话系统便当作什么都没发生,简单地显示原来的通知。
- 或许我们已经设置好了绝大部分内容,只是有很少一部分没有完成。这时我们也可以像例子中这样调用 contentHandler 来显示一个变更到一半的通知。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import UserNotifications class NotificationService : UNNotificationServiceExtension { var contentHandler: (( UNNotificationContent ) -> Void )? var bestAttemptContent: UNMutableNotificationContent ? //我们可以在后台处理接收到的推送,让后传递修改后的的内容给contentHandler进行展示 override func didReceive(_ request: UNNotificationRequest , withContentHandler contentHandler: @escaping ( UNNotificationContent ) -> Void ) { self .contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as ? UNMutableNotificationContent ) if let bestAttemptContent = bestAttemptContent { //给通知内容添加个小尾巴 bestAttemptContent.body = "\(bestAttemptContent.body) 【来自hangge.com】" contentHandler(bestAttemptContent) } } //如果我们获取消息后一段时间内没有调用 contentHandler 的话,系统会调用这个方法 override func serviceExtensionTimeWillExpire() { //如果消息没处理好,我们也将这个没处理完毕的消息进行展示 if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } } |
(3)如果需要调试这个通知扩展类,注意 Target 要选择 NotificationService,然后编译运行时选择我们的程序。
(4)同时 Service Extension 的发布版本要低于设备的版本(比如我手机是 10.3.1,那么这里可以直接设置为 10)。
(5)最后我们在远程通知的 payload 中增加一个 mutable-content 值为 1 的项来启用内容修改(这个一定要有,否则可能会拦截通知失败)。
1 2 3 4 5 6 7 8 9 10 11 | { "aps" : { "alert" : { "title" : "最新资讯" , "body" : "2017全国文明城市公布" }, "sound" : "default" , "badge" : 1, "mutable-content" : 1 }, } |
(6)可以看到客户端这边收到通知后,会自动在内容尾部增加一个段小尾巴(【来自hangge.com】)
十三、为本地通知添加多媒体内容
多媒体推送是 iOS10 新增加的一个功能。我们可以在通知中嵌入图片或者视频,这极大地丰富了推送内容的可读性和趣味性。
1,使用说明
- 为本地通知添加多媒体内容十分简单,只需要通过本地磁盘上的文件 URL 创建一个 UNNotificationAttachment 对象,然后将这个对象放到数组中赋值给 content 的 attachments 属性就可以了。
- attachments 虽然是一个数组,但是系统只会展示第一个 attachment 对象的内容。不过我们依然可以发送多个 attachments,然后在要展示的时候再重新安排它们的顺序,以显示最符合情景的图片或者视频。另外,我们也可能会在自定义通知展示 UI 时用到多个 attachment,这个我们下文会进行演示。
- 系统在创建 attachement 时会根据提供的 url 后缀确定文件类型,如果没有后缀,或者后缀不正确的话,我们可以在创建时通过 UNNotificationAttachmentOptionsTypeHintKey 来指定资源类型。
2,多媒体文件的格式、尺寸限制
(1)支持的最大尺寸:
- 图片:10MB
- 音频:5MB
- 视频:50MB
(2)支持的文件格式:
- 图片:kUTTypeJPEG、kUTTypeGIF、kUTTypePNG
- 音频:kUTTypeAudioInterchangeFileFormat、kUTTypeWaveformAudio、kUTTypeMP3、kUTTypeMPEG4Audio
- 视频:kUTTypeMPEG、kUTTypeMPEG2Video、kUTTypeMPEG4、kUTTypeAVIMovie
3,使用样例
(1)下面代码我们给通知附带上一张图片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import UIKit import UserNotifications class ViewController : UIViewController { override func viewDidLoad() { super .viewDidLoad() //设置推送内容 let content = UNMutableNotificationContent () content.title = "hangge.com" content.body = "囤积iPhoneX的黄牛赔到怀疑人生?" //给通知添加图片附件 if let imageURL = Bundle .main.url(forResource: "image" , withExtension: "png" ), let attachment = try? UNNotificationAttachment (identifier: "imageAttachment" , url: imageURL, options: nil ) { content.attachments = [attachment] } //设置通知触发器 let trigger = UNTimeIntervalNotificationTrigger (timeInterval: 5, repeats: false ) //设置请求标识符 let requestIdentifier = "com.hangge.testNotification" //设置一个通知请求 let request = UNNotificationRequest (identifier: requestIdentifier, content: content, trigger: trigger) //将通知请求添加到发送中心 UNUserNotificationCenter .current().add(request) { error in if error == nil { print ( "Time Interval Notification scheduled: \(requestIdentifier)" ) } } } override func didReceiveMemoryWarning() { super .didReceiveMemoryWarning() } } |
(2)效果图
- 上面代码运行后,在通知显示时,横幅或者弹窗将附带有设置的图片。
- 使用 3D Touch pop 通知或者下拉通知显示详细内容时,图片也会被放大展示。
- 除了图片以外,通知还支持音频以及视频。我们可以将 MP3 或者 MP4 这样的文件提供给系统,从而在通知中进行展示和播放。
4,访问已创建的 attachment 的内容
我们可以访问一个已经创建的 attachment 的内容,但是要注意权限问题。可以使用 startAccessingSecurityScopedResource 来暂时获取已创建的 attachment 的访问权限。
1 2 3 4 5 6 7 | let content = notification.request.content if let attachment = content.attachments.first { if attachment.url.startAccessingSecurityScopedResource() { eventImage.image = UIImage (contentsOfFile: attachment.url.path!) attachment.url.stopAccessingSecurityScopedResource() } } |
十四、为远程推送添加多媒体内容
1,实现原理
对于远程推送,我们也可以显示图片等多媒体内容。不过需要通过上面介绍的 Notification Service Extension 来修改推送通知内容的技术。具体流程如下:
- 我们在推送的 payload 中指定需要加载的图片资源地址,这个地址可以是应用 bundle 内已经存在的资源,也可以是网络的资源。
- 客户端收到通知后,根据资源地址创建相应的 UNNotificationAttachment。由于只能使用本地资源创建 UNNotificationAttachment,所以如果多媒体还不在本地的话,我们需要先将其下载到本地。
- 在完成 UNNotificationAttachment 创建后,我们就可以像本地通知一样,将它设置给 attachments 属性,然后调用 contentHandler 了。
2,使用样例
(1)假设我们的远程通知 payload 报文如下,其中:
- mutable-content:表示我们会在接收到通知时需要对内容进行更改。
- image:表示需要显示的图片的地址。
1 2 3 4 5 6 7 8 9 10 11 12 | { "aps" : { "alert" : { "title" : "最新资讯" , "body" : "2017全国文明城市公布" }, "sound" : "default" , "badge" : 1, "mutable-content" : 1 }, } |
(2)项目这边创建一个 NotificationService,作用是接收到上面这样的通知时会自动提取图片地址、下载,并生成 attachment,然后进行通知展示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | import UserNotifications class NotificationService : UNNotificationServiceExtension { var contentHandler: (( UNNotificationContent ) -> Void )? var bestAttemptContent: UNMutableNotificationContent ? //我们可以在后台处理接收到的推送,让后传递修改后的的内容给contentHandler进行展示 override func didReceive(_ request: UNNotificationRequest , withContentHandler contentHandler: @escaping ( UNNotificationContent ) -> Void ) { self .contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as ? UNMutableNotificationContent ) if let bestAttemptContent = bestAttemptContent { //将远程推送通知中的图片下载到本地,并显示 if let imageURLString = bestAttemptContent.userInfo[ "image" ] as ? String , let URL = URL (string: imageURLString) { downloadAndSave(url: URL ) { localURL in if let localURL = localURL { do { let attachment = try UNNotificationAttachment (identifier: "download" , url: localURL, options: nil ) bestAttemptContent.attachments = [attachment] } catch { print (error) } } contentHandler(bestAttemptContent) } } } } //如果我们获取消息后一段时间内没有调用 contentHandler 的话,系统会调用这个方法 override func serviceExtensionTimeWillExpire() { //如果消息没处理好,我们也将这个没处理完毕的消息进行展示 if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } //将图片下载到本地临时文件夹中 private func downloadAndSave(url: URL , handler: @escaping (_ localURL: URL ?) -> Void ) { let task = URLSession .shared.dataTask(with: url, completionHandler: { data, res, error in var localURL: URL ? = nil if let data = data { //取得当前时间的时间戳 let timeInterval = Date ().timeIntervalSince1970 let timeStamp = Int (timeInterval) //文件后缀 let ext = (url.absoluteString as NSString ).pathExtension let temporaryURL = FileManager . default .temporaryDirectory let url = temporaryURL.appendingPathComponent( "\(timeStamp)" ) .appendingPathExtension(ext) if let _ = try? data.write(to: url) { localURL = url } } handler(localURL) }) task.resume() } } |
(3)具体效果如下。可以看到即使是远程通知,附带的也是网络图片,但也是可以正常显示的。