推送通知(三)-iOS>10的UNNotificationServiceExtension
UNNotificationServiceExtension简介
- Notification Service Extension是Xcode8中加入众多extension的其中一种,Extension实际上是App提供了一个额外插件功能,以供iOS操作系统调用,与App是宿主关系。
UNNotificationServiceExtension
是iOS10推出后的一个新特性,先看这张图:- 从这张图上,我们可以看到,原先直接从APNs推送到用户手机的消息,中间添加了ServiceExtension处理的一个步骤(当然你也可以不处理),通过这个ServiceExtension,我们可以把即将给用户展示的通知内容,做各种自定义的处理,最终,给用户呈现一个更为丰富的通知,当然,如果网络不好,或者附件过大,可能在给定的时间内,我们没能将通知重新编辑,那么,则会显示发过来的原版通知内容。
ServiceExtension
只能用于远程推送- ServiceExtension 作用
- 使得推送的数据在iOS系统展示之前,经过App开发者的Extension,可以在不启动App的情况下,完成一些快捷操作逻辑,比如上面的例子,如果你是个社交App,可以在不启动App的情况下,直接点赞回复,而不用打开App,提高效率
- 虽然iOS10的推送数据包已经达到4k,但是对于一些图片视频gif还是无力的,有了Extension,可以在此下载完毕然后直接展示,丰富的图片和视频可以在此显示
- 可以在此Extension中如果要完成1中所述的用户行为操作,则必须加强安全性,服务端可以对推送的数据配合RSA算法用服务端的私钥加密,在Extension中使用服务端私钥解密,其实APNs从SSL数字安全证书到Json Web Token令牌,已经非常安全,但是大量的App使用第三方诸如JPush的推送服务,来跟APNs交互,业务数据跑在别人的管道上,当然有所顾忌,所以,这个地方加密的更多现实意义是防止业务数据被第三方服务商窥探。
- 缺点:
- Notification Service Extension非常容易崩溃crash和内存溢出out of memory
- 更加坑的是debug运行的时候和真机运行的时候,Notification Service Extension性能表现是不一样的,真机运行的时候Notification Service Extension非常容易不起作用,我做了几次实验,图片稍大,Notification Service Extension就崩溃了不起作用了,而相同的debug调试环境下则没问题
- 比如说你下载资源的时候最好分段缓存下载,真机环境下NSURLSessionDataTask下载数据不好使,必须使用NSURLSessionDownloadTask才可以,这点很无奈。
如何新建一个UNNotificationServiceExtension
- 我们不能通过创建UNNotificationServiceExtension的类来使用服务扩展,我们应当创建一个Target,这个Target自带一个模板
- 选择模板
- 然后写名字,下一步,即可,此时我们的目录结构里面,已经多出了一个文件夹了。
- 注意看下图图,这里的bundleID是你的工程名字的bundleID加上.名称
- 不要修改,系统创建的时候就创建好了
- 注意事项:
- 开启当前target的Capability->Push Notification。
-
当前targetinfo.plist添加
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
- 没有第一条远程通知不会走这个ServiceExtension,没有第二条,从网络下载不到数据
如何使用
ServiceExtension
文件夹会多出NotificationService
类,该类继承自:UNNotificationServiceExtension
NotificationService
重写了UNNotificationServiceExtension
的两个方法-
方法1:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *contentToDeliver))contentHandler;
- 你需要通过重写这个方法,来重写你的通知内容,也可以在这里下载附件内容
- 如果在服务的时间过期(30s)之前没有调用该
contentHandler
,则将发送未修改的通知。
-
方法2:
- (void)serviceExtensionTimeWillExpire;
- 调用时刻: 扩展即将结束的时候调用
- bestAttemptContent是你修改后的通知内容,如果没有修改完毕,那么就回使用原始的推送.
- 即: 如果处理时间太长,等不及处理了,就会把收到的apns直接展示出来
- 也就是说等到一定的时间,上面的方法还没有调用contentHandler,那就执行这个方法
- 代码如下:
- 步骤分析
- 将通知内容的重组,逻辑就是有附件的url,我就下载,如果没有url我就直接展示通知。
- 用系统自带类,下载图,存图,找到filePath,创建通知的附件内容。(创建附件的url,必须是一个文件路径,也就是说,必须下载下,才能获取文件路径,开头是file://)
- 判断文件的后缀类型,然后前端好处理。这里我的代码是写死了,因为我就测试一张图。最好的方式是服务器返回的 推送内容中,带有附件的类型。
#import "NotificationService.h" @interface NotificationService () @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; @end @implementation NotificationService /** 你需要通过重写这个方法,来重写你的通知内容,也可以在这里下载附件内容 如果在服务的时间过期之前没有调用该contentHandler,则将发送未修改的通知。 @param request 拿到apns推送的请求 @param contentHandler 交付修改后的推送内容 */ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; // copy发来的通知,开始做一些处理 self.bestAttemptContent = [request.content mutableCopy]; // 在这里修改通知的内容 self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title]; // 重写一些东西 self.bestAttemptContent.title = @"我是修改后的标题"; self.bestAttemptContent.subtitle = @"我是修改后的子标题"; self.bestAttemptContent.body = @"这里是修改后的推送内容"; NSLog(@"----------------------"); // 附件 NSDictionary *dict = self.bestAttemptContent.userInfo; NSDictionary *notiDict = dict[@"aps"]; //拿到图片的url NSString *imgUrl = [NSString stringWithFormat:@"%@",notiDict[@"imageAbsoluteString"]]; // 这里添加一些点击事件,可以在收到通知的时候,添加,也可以在拦截通知的这个扩展中添加 self.bestAttemptContent.categoryIdentifier = @"seeCategory1"; //没有资源就结束 if (!imgUrl.length) { self.contentHandler(self.bestAttemptContent); } //下载图片 [self loadAttachmentForUrlString:imgUrl withType:@"image" completionHandle:^(UNNotificationAttachment *attach) { if (attach) { self.bestAttemptContent.attachments = [NSArray arrayWithObject:attach]; } self.contentHandler(self.bestAttemptContent); }]; } /** 调用时刻: 扩展即将结束的时候调用 bestAttemptContent是你修改后的通知内容,如果没有修改完毕,那么就回使用原始的推送. 即: 如果处理时间太长,等不及处理了,就会把收到的apns直接展示出来 也就是说等到一定的时间,上面的方法还没有调用contentHandler,那就执行这个方法 */ - (void)serviceExtensionTimeWillExpire { self.contentHandler(self.bestAttemptContent); } //这是下载附件通知的方法: - (void)loadAttachmentForUrlString:(NSString *)urlStr withType:(NSString *)type completionHandle:(void(^)(UNNotificationAttachment *attach))completionHandler{ __block UNNotificationAttachment *attachment = nil; NSURL *attachmentURL = [NSURL URLWithString:urlStr]; NSString *fileExt = [self fileExtensionForMediaType:type]; //根据url下载数据到本地 NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; [[session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) { if (error != nil) { NSLog(@"%@", error.localizedDescription); } else { //将下载的文件存储到本地 NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExt]]; [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error]; NSError *attachmentError = nil; //创建附件 attachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:localURL options:nil error:&attachmentError]; if (attachmentError) { NSLog(@"%@", attachmentError.localizedDescription); } } //返回附件 completionHandler(attachment); }] resume]; } //判断文件类型的方法(很多情况下是服务器发送数据类型) - (NSString *)fileExtensionForMediaType:(NSString *)type { NSString *ext = type; if ([type isEqualToString:@"image"]) { ext = @"jpg"; } if ([type isEqualToString:@"video"]) { ext = @"mp4"; } if ([type isEqualToString:@"audio"]) { ext = @"mp3"; } return [@"." stringByAppendingString:ext]; } @end
- 步骤分析
- 如何调试
UNNotificationServiceExtension
- 选择ServiceExtension这个Target运行-> Run->选择你的项目就可以在
NotificationService
中打断点了(可是我这样做却没用)
- 选择ServiceExtension这个Target运行-> Run->选择你的项目就可以在
- 在SmartPush中写的推送内容如下
- 这里我们要注意一定要有
"mutable-content": "1"
,以及一定要有Alert的字段,否则可能会拦截通知失败。 - 除此之外,我们还可以添加自定义字段,比如,图片地址,图片类型等
{ "aps": { "alert": "This is some fancy message.", "badge": 1, "sound": "default", "mutable-content": "1", "imageAbsoluteString": "http://upload.univs.cn/2012/0104/1325645511371.jpg" } }
- 这里我们要注意一定要有