苹果的推送通知从iOS3开始出现, 每一年都会更新一些新的用法, 譬如:
- iOS 7 出现的Silent remote notifications(远程静默推送)
- iOS 8 出现的Category(分类, 也可称之为快捷回复)
- iOS 9 出现的Text Input action(文本框快捷回复).
随着iOS10的到来 , 苹果对远程通知和本地通知进行了大范围的更新.
iOS10推出了全新的UserNotifications框架(iOS10之前从属于UIKit框架).
新的推送通知框架, 整合了本地推送和远程推送的点击处理方法, 使得以前专门处理推送点击的方法只能处理静默推送了.
远程推送的实现原理:
1 打开App时: 发送UDID和BundleID给APNs加密后返回deviceToken
2 获取Token后,App调用接口,将用户身份信息和deviceToken发给服务器,服务器记录
3 当推送消息时, 服务器按照用户身份信息找到存储的deviceToken,将消息和deviceToken发送给APNs
4 苹果的APNs通过deviceToken, 找到指定设备的指定程序, 并将消息推送给用户
实现远程推送功能的前提:
1 真机
2 调试阶段的证书
- iOS_development.cer 用于真机调试的证书
- aps_development.cer 用于真机推送调试能的证书
- xxx.mobileprovision 描述文件,记录了能够调试的手机、电脑和程序
3 发布阶段的证书
- iOS_distribution.cer 用于发布app的证书
- aps.cer 用于发布时,让app有推送功能的证书
- xxx.mobileprovision 描述文件,记录了能够发布app的电脑
iOS10 全新远程通知Demo:
一. 注册远程推送并获取DeviceToken
1.创建iOS的项目,并输入项目名字
2.在AppDelegate中导入头文件:
#import <UserNotifications/UserNotifications.h>
3.在application:didFinishLaunchingWithOptions方法中, 注册远程通知
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//请求通知权限, 本地和远程共用
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@"请求成功");
} else {
NSLog(@"请求失败");
}
}];
//注册远程通知
[[UIApplication sharedApplication] registerForRemoteNotifications];
//设置通知的代理
center.delegate = self;
return YES;
}
4.在接收远程推送的DeviceToken方法中, 获取Token
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
//将来需要将此Token上传给后台服务器
NSLog(@"token:%@", deviceToken);
}
二. iOS10远程推送通知的处理方法
当点击了推送后, 如果你希望进行处理. 那么在iOS10中, 还需要设置UNUserNotificationCenter的delegate, 并遵守UNUserNotificationCenterDelegate协议.
以及实现下面实现3个方法, 用于处理点击通知时的不同情况的处理
* userNotificationCenter:willPresentNotification:withCompletionHandler:
前台运行
* userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
后台运行时点击横幅、消息栏消息
* application:didReceiveRemoteNotification:fetchCompletionHandler:
静默推送、App杀死后横幅和消息栏消息点击唤起App
//设置通知的代理
center.delegate = self;
1.前台运行 会调用的方法
前台运行: 指的是程序正在运行中, 用户能看见程序的界面.
iOS10会出现通知横幅, 而在以前的框架中, 前台运行时, 不会出现通知的横幅.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
NSDictionary *userInfo = notification.request.content.userInfo;
//前台运行推送 显示红色Label
[self showLabelWithUserInfo:userInfo color:[UIColor redColor]];
//可以设置当收到通知后, 有哪些效果呈现(声音/提醒/数字角标)
completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
2.后台运行点击消息会调用的方法
后台运行: 指的是程序已经打开, 用户看不见程序的界面, 如锁屏和按Home键.
程序退出: 指的是程序没有运行, 或者通过双击Home键,关闭了程序.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler
{
NSDictionary *userInfo = response.notification.request.content.userInfo;
//后台显示绿色Label
[self showLabelWithUserInfo:userInfo color:[UIColor greenColor]];
completionHandler();
}
3.静默推送通知 会调用的方法
静默推送: iOS7以后出现, 不会出现提醒及声音.
要求:
- 推送的payload中不能包含alert及sound字段
- 需要添加content-available字段, 并设置值为1
- 例如: {"aps":{"content-available":"1"},"PageKey”":"2"}
//如果是以前的旧框架, 此方法 前台/后台/退出/静默推送都可以处理
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
//静默推送 显示蓝色Label
[self showLabelWithUserInfo:userInfo color:[UIColor blueColor]];
completionHandler(UIBackgroundFetchResultNewData);
}
4.处理通知的公用方法
开发中, 点击通知的逻辑应当看自己程序的需求.
这里为了方便演示, 简单的将通知的值, 通过UILabel显示在主界面上
- (void)showLabelWithUserInfo:(NSDictionary *)userInfo color:(UIColor *)color
{
UILabel *label = [UILabel new];
label.backgroundColor = color;
label.frame = CGRectMake(0, 250, [UIScreen mainScreen].bounds.size.width, 300);
label.text = userInfo.description;
label.numberOfLines = 0;
[[UIApplication sharedApplication].keyWindow addSubview:label];
}
三. 测试远程推送
PushMeBaby是一个简单的模拟服务器的Mac小程序, 可以将内容提交给苹果的APNs服务器.
为了测试远程通知, 我们需要安装此程序.
请前往www.github.com, 搜索并下载PushMeBaby
使用时:
- 编译该项目, 如果报错, 则注释报错的代码, 不影响实际使用.
- 进入苹果开发者网站, 获取真机调试用的远程推送证书, 导入到项目中
- 将之前获取到的DeviceToken, 及测试的文字, 填入该项目中的AppDelegate中的init方法中.
- 运行此项目, 会出现一个Mac小程序, 点击Push即可发送远程通知.
- (id)init {
self = [super init];
if(self != nil) {
self.deviceToken = @"de20184c ef0461d5 12c76422 f5b78240 5f657e18 ebf91c9f 01d5560c e2913102";
self.payload = @"{\"aps\":{\"alert\":{\"title\":\"传智播客\",\"subtitle\":\"黑马程序员\",\"body\":\"iOS10远程&本地推送教程\"},\"badge\":1,\"sound\":\"default\"},\"PageKey\":\"1\"}";
self.certificate = [[NSBundle mainBundle] pathForResource:@"aps_development" ofType:@"cer"];
}
return self;
}
四.补充
因为推送的回调Api方法过多, 初次接触可能会有些头大,虽然从iOS 10起,苹果对于推送的授权(本地+远程)方式有所调整, 而且采用了使用代理的方式. 而我们也可以采取的实现方式是:
1. 通过requestAuthorizationWithOptions:授权
2. 不设置UNUserNotificationCenter 的 delegate
3. 这样还会走之前iOS 9的回调方法
五. 回调API
-
application: didReceiveRemoteNotification: fetchCompletionHandler:
前台收到推送消息时,不会有弹框提醒 -
userNotificationCenter: willPresentNotification: withCompletionHandler:
可以通过设置回调参数,根据回调参数内是否传入UNNotificationPresentationOptionAlert
,如果有,则在前台收到推送时,也会有弹框提醒,在系统通知栏内可见推送消息,如果不传入,则不会有弹框提醒,系统通知栏不可见
completionHandler( UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound );
对于收到推送后, 通知消息的获取与进入App的方式有关:
- App未运行时:
1.1 点击通知消息进入, 会执行didFinishLaunchWithOptions:
方法,通过options获取通知消息
1.2 点击应用图标进入,会执行didFinishLaunchWithOptions
方法,但options中不包含notification信息
- App未运行时:
2.App后台运行:
2.1 点击通知消息进入,会执行didReceiveRemoteNotification:
方法(不同iOS版本的后台方法),从而获取推送消息
2.2 点击应用图标进入,会执行didReceiveRemoteNotification
方法不会被调用,无法获取到推送消息对于1.2和2.2场景, 可以通过后台协助, 每次点击应用图标进入后,向后台请求是否有新的未读推送,通过请求的方式获取推送信息.
对于1.1&1.2场景, 如果远程通知到达时应用程序未运行,则
application: didReceiveRemoteNotification: fetchCompletionHandler:
该方法将启动应用程序并在启动选项字典中提供适当的信息。
if the app is not running when a remote notification arrives,
the method launches the app and provides the appropriate information in the launch options dictionary.
The app does not call this method to handle that remote notification.
Instead, your implementation of the
application:willFinishLaunchingWithOptions:
or
application:didFinishLaunchingWithOptions:
method needs to get the remote notification payload data and respond appropriately.
关于角标:
App当前角标 | 收到推送 | 变化 |
---|---|---|
角标为0 | 推送badge > 0 | 角标 = badge |
角标为0 | 推送badge = 0 | 不显示角标 |
角标不为0 | 推送badge > 0 | 角标 = badge (最后一条推送中的badge) |
角标不为0 | 推送badge = 0 | 角标 = 0 , 同时在通知栏清空之前的推送记录 |