简介:本课程将带你了解iOS的push原理,如何搭建push环境,push的交互设置,以及该如何实现推送消息。并且会给大家分别讲解Local Push的一般用法和高级用法,以及push的相关业务功能介绍。
要点:
iOS远程推送通知课程介绍
iOS之远程push环境搭建
iOS本地推送通知课程介绍
通知大家都不陌生,其实通知分两种,远程通知和本地通知。
远程通知是指服务器发出的通知,通过苹果的推送然后到达用户设备。
本地通知是指不通过网络,直接安装应用后就可以接到通知了,典型的例子是日历、待办、闹钟等应用。
不过就表现形式来说两者基本一样,都会出现在通知中心,都可以出现在锁屏界面,都可以出现在界面上部,都可以添加应用上的红点。
注意:现在在Xcode中使用远程通知功能需要在工程的Targets中的Capabilities标签里打开Push Notification权限,且需要APNS证书,不过本地通知是不需要的,可以直接测试接收通知。
iOS的远程push通道是一个独立的长连接通道,这个通道是由iOS系统独立维护的(这个通道也是唯一的一个push通道)。也就是说,我们每一个手机系统在它开机之后就会有一个后台悄悄长连接连向苹果的服务器(APNs服务器),APNs当收到新的push通知的时候它就会把这个通知推给我们的设备。
大致的工作原理是:
1,在手机系统启动之后或者是打开某个App之后,会生成一个独立的唯一的一个token,并把这个token上传给我们自己的服务器。
2,我们的服务器拿到这个token之后就可以给我们的设备推送通知了,但是这个通知并不能直接推给我们的设备,而是要推给苹果的服务器,因为上面提到了push的通道是由苹果维护的,是一个唯一的通道,所以我们的服务器要把这个token加上要推送的内容一起推给苹果服务器(APNs)。
3,苹果服务器在收到我们的服务器的push之后再根据token定位到我们的设备,然后把这个push推给我们的设备。
4,设备拿到token之后,再根据这个token定位到这个token是属于我们设备上的哪个app,这样就可以一个通知推送到具体的哪一个App当中。
- 手机刚开机,和苹果服务器(APNs)建立加密的socket通道(基于SSL加密的)。
- 打开App之后,苹果的APNs服务器会下发一个deviceToken给我们的设备,deviceToken能标识设备上唯一的App。
- 设备拿到App的deviceToken。
- 设备把拿到的deviceToken上传给我们自己的服务器。
- 我们的服务器有了deviceToken之后可以给APNs服务器发送【deviceToken+push内容】,APNs根据deviceToken给对应的设备发送【deviceToken+push内容】,设备根据deviceToken给对应的App发送push通知。
- 配置项目
- 新建项目工程PushTestDemo;
- 在开发者账号中为新项目创建Apple ID;
- 推送证书(ProvisioningProfile)
- 项目中的关键代码
项目中的关键代码
注册APNs
向APNs申请deviceToken(申请成功和失败的回调方法)
deviceToken上传服务器
/**
iOS8-10注册APNs
*/
- (void)registerAPNs{
if (iOS_SystemVersion < 10.0 && iOS_SystemVersion >= 8.0){
UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
//这个注册之后项目中会有弹框,是否允许开启推送服务
[[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
//向苹果的APNs服务器申请token(在下面回调方法中申请token也可以)
// [[UIApplication sharedApplication] registerForRemoteNotifications];
}
}
/**
注册苹果推送通知服务
iOS8-10
下面这个方法是上面 - (void)registerUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;这个方法的回调
需要调用内容 获取用户对通知的设置信息
用户点击 ”允许“之后会走下面的方法
*/
#ifdef __IPHONE_8_0
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
//到这里表示用户点击了”允许“操作
//register to receive notifications
//向苹果的APNs服务器申请token
[application registerForRemoteNotifications];
}
#endif
/**
deviceToken注册成功的回调
*/
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
NSString *tokenStr = [NSString stringWithFormat:@"%@",deviceToken];
//去掉尖括号和空格
tokenStr = [tokenStr stringByReplacingOccurrencesOfString:@" " withString:@""];
tokenStr = [tokenStr stringByReplacingOccurrencesOfString:@"<" withString:@""];
tokenStr = [tokenStr stringByReplacingOccurrencesOfString:@">" withString:@""];
NSLog(@"%@",tokenStr);
//把tokenStr上传到自己的服务器
.........
}
/**
deviceToken注册失败的回调
*/
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
}
- 获取Push权限
就是上面的注册APNs相关的代码,要严格按照版本调对应的API,因为[7,8),[8-10),[10+,要用不同的API。
- 提示开启Push
如果用户第一次关闭了用户权限,那用户在使用相关业务功能模块的时候,我们可以再次引导用户打开push权限;
例如:如果用户第一次关闭了用户权限,那么当用户进入后台10次后引导用户再次开启push权限。
注意:iOS8之后允许跳转到设置界面,iOS8之前是不允许的。
//提醒用户开启推送权限
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationLaunchOptionsRemoteNotificationKey]];
介绍
对于本地通知,iOS 10以前和以后分两种实现方式。
iOS 10以前使用UILocalNotification,iOS10开始支持一个新的类库UNUserNotificationCenter,都给了他特定的前缀UN了,可见重视程度。
使用场景
例如:有一个签到功能,用户当天没打开App,这时候可以弹出一个提示框,提示用户去签到。
例如:某些游戏中升级建筑装备,一般都需要时间去完成,玩家不可能一直在线等待,当升级结束,游戏在本地把通知推送给玩家,玩家响应并再次进入游戏,不仅节约玩家的时间,也给游戏的激活带来更多的流量。
代码实现
如上面图所示,要发通知是需要用户同意的,也就是在第一次打开App的时候必须尝试注册通知,如果不注册,那么即使用户去设置中找也无法再通知里找到你的App然后打开。
所以我们需要在AppDelegate.m的application: didFinishLaunchingWithOptions:方法中注册通知:
这样就会在第一次启动App时向用户索取权限。接下来就可以决定发什么通知以及收到通知后怎么处理了。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //1,注册APNs获得用户权限 [self registerAPNs]; return YES; } -(void)registerAPNs{ // ios8后,需要添加这个注册,才能得到授权,否则收不到推送通知 CGFloat version = [[[UIDevice currentDevice] systemVersion] floatValue]; if (version >=10) { NSLog(@"====>registerAPNs ios10及以上版本"); //iOS 10 使用以下方法注册,才能得到授权,注册通知以后,会自动注册 deviceToken,如果获取不到 deviceToken,Xcode8下要注意开启 Capability->Push Notification。 if (@available(iOS 10.0, *)) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) { if (granted) { } }]; } //2,参数5是触发时间 单位:秒 [self registerLocalNotificationForNewVersion:5]; }else if(version >=8){ // // 注册通知,如果已经获得发送通知的授权则创建本地通知,否则请求授权(注意:如果不请求授权在设置中是没有对应的通知设置项的,也就是说如果从来没有发送过请求,即使通过设置也打不开消息允许设置) if ([[UIApplication sharedApplication] currentUserNotificationSettings].types != UIUserNotificationTypeNone) { //2,参数5是触发时间 单位:秒 [self registerLocalNotification:5]; } else { [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]]; } }else{ NSLog(@"====>registerAPNs ios8以下版本"); } } #pragma mark ios (8,10) // 注册通知具体方法 - (void)registerLocalNotification:(NSInteger)alertTime { UILocalNotification *notification = [[UILocalNotification alloc] init]; // 设置触发通知的时间 NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:alertTime]; NSLog(@"fireDate=%@",fireDate); notification.fireDate = fireDate; // 时区 notification.timeZone = [NSTimeZone defaultTimeZone]; // 设置重复的间隔 notification.repeatInterval = kCFCalendarUnitSecond; // 通知内容 notification.alertBody = @"该起床了...iOS8-10"; notification.applicationIconBadgeNumber = 1; // 通知被触发时播放的声音 notification.soundName = UILocalNotificationDefaultSoundName; // 通知参数,用于取消本地推送的key notification.userInfo = [NSDictionary dictionaryWithObject:@"开始学习iOS开发了" forKey:@"study"]; // 通知重复提示的单位,可以是天、周、月 notification.repeatInterval = NSCalendarUnitMinute; // 执行通知注册 [[UIApplication sharedApplication] scheduleLocalNotification:notification]; } #pragma mark ios (10+) // 发送通知具体方法 -(void)registerLocalNotificationForNewVersion:(NSInteger)alerTime{ // 使用 UNUserNotificationCenter 来管理通知 if (@available(iOS 10.0, *)) { UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; //需创建一个包含待通知内容的 UNMutableNotificationContent 对象,注意不是 UNNotificationContent ,此对象为不可变对象。 UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; // Use -[NSString localizedUserNotificationStringForKey:arguments:] to provide a string that will be localized at the time that the notification is presented. //提供一个字符串,该字符串将在发出通知时本地化。 content.title = [NSString localizedUserNotificationStringForKey:@"Hello iOS10+!" arguments:nil]; content.body = [NSString localizedUserNotificationStringForKey:@"Hello_message_body" arguments:nil]; //收到通知时播放的声音,默认消息声音 content.sound = [UNNotificationSound defaultSound]; //设置用户信息 content.userInfo = @{ @"id":@"00012", @"user":@"cheng", @"content":@"iOS10+通知内容", }; // 在 alertTime 后推送本地推送,发本地通知的时间点 UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:alerTime repeats:NO]; UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"FiveSecond" content:content trigger:trigger]; [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { NSLog(@"成功添加推送"); }]; } }
要接收通知并处理必须要遵循 UNUserNotificationCenterDelegate 这个协议,上面代码中就设置了delegate是self,然后就可以处理接收通知:
#pragma mark - iOS8 - 10本地通知回调 // 本地通知回调函数,当应用程序在前台接收到通知,或者后台点击通知调用 - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { NSLog(@"%s",__func__); NSLog(@"didReceiveLocalNotification:%@",notification); // 这里真实需要处理交互的地方 // 获取通知所带的数据 NSString *notMess = [notification.userInfo objectForKey:@"study"]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"本地通知iOS8-10" message:notMess delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; // 更新显示的badge个数 NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber; badge--; badge = badge >= 0 ? badge : 0; [UIApplication sharedApplication].applicationIconBadgeNumber = badge; // 在不需要再推送时,可以取消某一个推送(或者可以取消全部本地通知) [self cancelLocalNotificationWithKey:@"study"]; } #pragma mark - iOS10+本地通知回调 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 /** UNUserNotificationCenterDelegate提供的两个代理方法,第一个方法主要用来决定是否在应用内展示通知。 如果在应用内展示通知 (如果不想在应用内展示,可以不实现这个方法) */ - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0)){ NSLog(@"%s",__func__); NSDictionary * userInfo = notification.request.content.userInfo; UNNotificationRequest *request = notification.request; // 收到推送的请求 UNNotificationContent *content = request.content; // 收到推送的消息内容 NSNumber *badge = content.badge; // 推送消息的角标 NSString *body = content.body; // 推送消息体 UNNotificationSound *sound = content.sound; // 推送消息的声音 NSString *subtitle = content.subtitle; // 推送消息的副标题 NSString *title = content.title; // 推送消息的标题 if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { NSLog(@"iOS10 前台收到远程通知:%@", body); } else { // 判断为本地通知 NSLog(@"iOS10 前台收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo); } //展示 completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置 //不展示 // completionHandler(UNNotificationPresentationOptionNone); } #endif
#pragma mark - iOS10+本地通知回调 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 /** UNUserNotificationCenterDelegate提供的两个代理方法,第二个方法用来处理用户与推送的通知进行的交互。 */ - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(ios(10.0)){ NSLog(@"%s",__func__); // 获取通知所带的数据 NSString *notMess = [response.notification.request.content.userInfo objectForKey:@"content"]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"本地通知 iOS10+!" message:notMess delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; // 更新显示的badge个数 NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber; badge--; badge = badge >= 0 ? badge : 0; [UIApplication sharedApplication].applicationIconBadgeNumber = badge; completionHandler(); } #endif
此外还有几个可能用得到的代理方法:
/** 应用注册通知后 @param application 应用 @param notificationSettings 通知设置 */ - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { if (notificationSettings.types != UIUserNotificationTypeNone) { [self registerLocalNotification:5]; } } /** 应用进入前台时调用 @param application 应用 */ - (void)applicationWillEnterForeground:(UIApplication *)application { [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];//进入前台取消应用消息图标 } /** 收到通知后回调 @param application 应用 @param notification 通知 */ - (void)application:(UIApplication *)application didReceiveLocalNotification:(nonnull UILocalNotification *)notification { NSLog(@"%@", notification.alertBody); }
学习地址
iOS 10的UserNotifications 框架解析及使用
对iOS10新增Api的详细探究
iOS 推送通知及通知扩展