级别: ★★☆☆☆
标签:「iOS推送」「iOSPush」「远程推送」
做者: dac_1033
审校: QiShare团队php
iOS中的Push(通知)分为两种:
1. iOS 本地推送
2. iOS 远程推送html
iOS中的通知包括本地推送通知和远程推送通知,两种通知在iOS系统中经过横幅或者弹出提醒两种形式来告诉用户,点击系统弹出的通知会打开应用程序。ios
今天主要介绍iOS端关于远程推送通知的相关功能及操做。git
远程推送通知是经过苹果的APNs(
Apple Push Notification server
)发送到App,而APNs必须先知道用户设备的地址,而后才能向该设备发送通知。此地址采用设备令牌的形式,该设备令牌对于设备和应用程序都是惟一的(即device token)。在启动时,App
与APNs
通讯并接收device token
,而后将其转发到App Server
,App Server
将包含该令牌及要发送的通知消息发送至APNs
。(苹果官网APNs概述)github
远程通知的传递涉及几个关键组件:算法
苹果官方提供的远程通知的传递示意图以下: json
准备工做:
安全
(1)在苹果开发者帐号中建立的App ID不能使用通配ID
,而且在所建立的APP ID的配置项中选择Push Notifications
服务,App使用没有选择该服务的App ID
所生成的推送证书和配置文件时,没法完成注册远程通知;
(2)当前工程配置中的Bundle Identifier
必须和生成配置文件使用的APP ID
彻底一致;
(3)当前工程配置中的“Capabilities”需设置为ON
;
(4)远程推送必须真机调试,模拟器没法获取获得device token
。bash
详细步骤:服务器
-(void)registerRemoteNotification {
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // Xcode 8编译会调用
if (@available(iOS 10.0, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay) completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!error) {
NSLog(@"request notification authorization succeeded!");
}
}];
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
#else // Xcode 7编译会调用
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
#endif
} else {
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
}
复制代码
device token
device token
成功回调:- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
复制代码
device token
失败:- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
复制代码
App将device token
发送给App Server
只有苹果公司知道device token
的生成算法,保证惟一,device token
在App重装等状况时会变化,所以为确保device token
变化后App仍然可以正常接收服务器端发送的通知,建议每次应用程序启动都从新得到device token
,并传给App Server
。
App Server根据最新的device token
将要推送的消息发送给APNs
将指定device token和消息内容发送给APNs
时,消息内容的格式必须彻底按照苹果官方的消息格式组织消息内容,点击查看远程通知消息的字段、建立远程通知消息。
消息格式的例子以下:
{"aps":{"alert":{"title":"通知的title","subtitle":"通知的subtitle","body":"通知的body","title-loc-key":"TITLE_LOC_KEY","title-loc-args":["t_01","t_02"],"loc-key":"LOC_KEY","loc-args":["l_01","l_02"]},"sound":"sound01.wav","badge":1,"mutable-content":1,"category": "realtime"},"msgid":"123"}
APNs
根据device token
查找相应设备,并推送消息 通常状况APNs能够根据deviceToken
将消息成功推送消息到相应设备中,但也存在用户卸载程序等缘由致使推送消息失败的状况,这时App
服务端会收到APNs
返回的错误信息)。
AppDelegate.m
中的回调方法
// iOS<10时,且app被彻底杀死
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
// 注:iOS10以上,若是不使用UNUserNotificationCenter,将走此回调方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
// iOS7及以上系统
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
// iOS>=10: App在前台获取到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler;
// iOS>=10: 点击通知进入App时触发(杀死/切到后台唤起)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;
复制代码
注册远程推送及解析推送数据的代码以下:
#import "AppDelegate.h"
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#import <UserNotifications/UserNotifications.h>
#endif
@interface AppDelegate () <UNUserNotificationCenterDelegate>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 注册APNs
[self registerRemoteNotifications];
return YES;
}
- (void)registerRemoteNotifications {
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // Xcode 8编译会调用
if (@available(iOS 10.0, *)) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay) completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!error) {
NSLog(@"request authorization succeeded!");
}
}];
} else {
// Fallback on earlier versions
}
[[UIApplication sharedApplication] registerForRemoteNotifications];
#else // Xcode 7编译会调用
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
#endif
} else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
UIRemoteNotificationType apn_type = (UIRemoteNotificationType)(UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeBadge);
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:apn_type];
}
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// 获取并处理deviceToken
NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
DLog(@"---DeviceToken--->> %@\n", token);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
DLog(@"---register RemoteNotifications failed---\n%@", error);
}
// 注:iOS10以上,若是不使用UNUserNotificationCenter,将走此回调方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// iOS6及如下系统
if (userInfo) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {// app位于前台通知
NSLog(@"app位于前台通知(didReceiveRemoteNotification:):%@", userInfo);
} else {// 切到后台唤起
NSLog(@"app位于后台通知(didReceiveRemoteNotification:):%@", userInfo);
}
}
}
// 注:
// 1. 该回调方法,App杀死后并不执行;
// 2. 该回调方法,会与application:didReceiveRemoteNotification:互斥执行;
// 3. 该回调方法,会与userNotificationCenter:willPresentNotification:withCompletionHandler:一并执行;
// 4. 该回调方法,会与userNotificationCenter:didReceiveNotificationResponse::withCompletionHandler:一并执行。
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler NS_AVAILABLE_IOS(7_0) {
// iOS7及以上系统
if (userInfo) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {// app位于前台通知
NSLog(@"app位于前台通知(didReceiveRemoteNotification:fetchCompletionHandler:):%@", userInfo);
} else {// 切到后台唤起
NSLog(@"app位于后台通知(didReceiveRemoteNotification:fetchCompletionHandler:):%@", userInfo);
}
}
completionHandler(UIBackgroundFetchResultNewData);
}
#pragma mark - iOS>=10 中收到推送消息
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
// iOS>=10: App在前台获取到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
API_AVAILABLE(ios(10.0)) {
NSDictionary * userInfo = notification.request.content.userInfo;
if (userInfo) {
NSLog(@"app位于前台通知(willPresentNotification:):%@", userInfo);
}
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);;
}
// iO>=10: 点击通知进入App时触发(杀死/切到后台唤起)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
API_AVAILABLE(ios(10.0)) {
NSDictionary * userInfo = response.notification.request.content.userInfo;
if (userInfo) {
NSLog(@"点击通知进入App时触发(didReceiveNotificationResponse:):%@", userInfo);
}
completionHandler();
}
#endif
@end
复制代码
App Server
对应的处理过程交给了第三方工具,第三方推送测试工具备不少,如SmartPush、Pusher等,在这里咱们选用Pusher做为测试工具,Pusher的GitHub地址。
Pusher的使用步骤说明:
(1)选择p12
格式的推送证书;
(2)设置是否为测试环境(默认勾选为测试环境,因为推送证书分为测试推送证书和生产测试证书,而且苹果的APNs
也分为测试和生产两套环境,所以Pusher
须要手动置顶是否为测试环境);
(3)输入device token
;
(4)输入符合苹果要求的推送内容字符串;
(5)当确认手机端设置无误,而且以上4点设置正确时,执行推送。
Pusher推送的消息,以第4点中的示例为例进行测试,手机收到远程推送通知的效果截图以下:
json
串:
备注:
(1)要使用APNs
向非运行的应用程序提供远程通知,须要至少启动目标应用程序一次;
(2)设备没有网络的状况下,是没法注册远程通知的;
(3)通常状况下,device token
是不会发生变化的,即虽然调用注册远程通知的方法,可是返回的device token
仍然是以前获得的值;若是设备令牌在应用程序执行时发生更改,则应用程序对象再次调用相应的委托方法以通知更改;
(4)推送过程当中的消息json
串可在适当位置添加自定义字段,整个消息最大长度为4 KB
(4096
字节),超过最大容许长度,则拒绝通知;
(5)在iOS及以上系统中远程通知还包括“通知扩展”功能,在下一篇文章中介绍。
本文Demo连接:GitHub地址
关注咱们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)