版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.06.21 |
前言
我们做APP很多时候都需要推送功能,以直播为例,如果你关注的主播开播了,那么就需要向关注这个主播的人发送开播通知,提醒用户去看播,这个只是一个小的方面,具体应用根据公司的业务逻辑而定。前面已经花了很多篇幅介绍了极光推送,其实极光推送无非就是将我们客户端和服务端做的很多东西封装了一下,节省了我们很多处理逻辑和流程,这一篇开始,我们就利用系统的原生推送类结合工程实践说一下系统推送的集成,希望我的讲解能让大家很清楚的理解它。
几个需要提前知道的问题
1. 推送流程是如何的?
推送是苹果系统提供的功能,其实基本流程就是我们的客户端要用deviceToken在苹果服务器进行注册,并将这个deviceToken发送给我们的服务端绑定我们的账号,服务端将要推送的内容发送给苹果,苹果就会将定制化的内容根据deviceToken发送给我们客户端,我们就收到推送了。
也就是说,我们客户端除了注册PUSH,接收苹果给我们的deviceToken,发送给我们服务端和当前账号绑定以外,我们除了等着收到PUSH进行处理就决定不了别的事情了,至于推送什么是我们服务端决定的,什么时候能收到就看苹果服务器了。
下面这张图就是很好的说明了推送的原理,图片源于网络。
2. deviceToken是如何获取的?有什么用?
deviceToken是我们用苹果的API注册PUSH的时候,苹果的服务器发给我们的,我们在代理方法- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
中进行接收。那么这个是做什么的呢?这个就是苹果发送个性化推送找到你机器的凭证,有了它,苹果系统就知道这个消息要推送给你。
我们在代理方法中收到推送后,一般要将这个deviceToken发送给我们的服务器,让服务器将这个deviceToken和你当前App的账号进行绑定,这样才能进行个性化的推送和消息订制。
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(@"push - didRegisterForRemoteNotificationsWithDeviceToken");
NSString * deviceid = [NSString stringWithFormat:@"%@", deviceToken];
deviceid = [deviceid stringByReplacingOccurrencesOfString:@"<" withString:@""];
deviceid = [deviceid stringByReplacingOccurrencesOfString:@">" withString:@""];
deviceid = [deviceid stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"push - deviceToken = %@", deviceid);
// 这个是我自己写的单例,用来存储关于App的一些基本信息,这里是用NSUserDefaults存储当前收到的deviceToken
[JJAppPrefs setDeviceToken:deviceid];
//这个是我写的一个单例,用来专门处理Push相关的,这个就是绑定token,将这个收到的deviceToken发送给你的服务器,
// 服务器进行绑定,你只能知道绑定成功或者失败,不用做其他的。
[[JJPushManager manager] bindDeviceToken];
}
3. 客户端要如何开启推送?
这个是一个开关,只要点击一下就可以了,就能开启推送了。
4. 关于证书要做什么?
我们做推送是要证书的,让有账号的人生成两个证书,一个是测试环境证书,一个是生产环境证书。
具体如何生成证书可以看网上的资料,跟着一步步的做就可以了,这里就不多说了。
注册PUSH
截止目前,iOS已经有了11的系统,注册PUSH从10系统以后就要考虑版本的差异了。
如果你是在iOS10以前做PUSH是不用这么麻烦的,但是随着iOS10的更新,一个新类UNUserNotificationCenter
出来了,所以要根据版本的不同进行不同的注册逻辑。
iOS10及其以后版本
//10及其以后系统使用下面方法进行注册
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setDelegate:self];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge |
UNAuthorizationOptionSound)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@"push - 注册成功");
}
else{
NSLog(@"push - 注册失败");
}
}];
[[UIApplication sharedApplication] registerForRemoteNotifications];
这里,[[UIApplication sharedApplication] registerForRemoteNotifications];
一定不能少,要不你是收不到deviceToken的。因为你不写这句话表示你没有注册。
同时,你要设置好代理@interface AppDelegate () <UNUserNotificationCenterDelegate>
,否则你处理PUSH的方法都不会进行回调的。
iOS9及其以前版本
iOS9及其以前版本使用类UIUserNotificationSettings
进行注册PUSH的。
//9及其以前系统使用下面方法进行注册
else if ([UIUserNotificationSettings self]) {
id settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert |
UIUserNotificationTypeSound |
UIUserNotificationTypeBadge)
categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
else {
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert)];
}
这里有几个小的问题,UIUserNotificationSettings
在10系统出现就废弃了这个类。
NS_CLASS_DEPRECATED_IOS(8_0, 10_0, "Use UserNotifications Framework's UNNotificationSettings") __TVOS_PROHIBITED
@interface UIUserNotificationSettings : NSObject
这里大家也注意到了这个方法
- (void)registerForRemoteNotificationTypes:(UIRemoteNotificationType)types NS_DEPRECATED_IOS(3_0, 8_0, "Use -[UIApplication registerForRemoteNotifications] and UserNotifications Framework's -[UNUserNotificationCenter requestAuthorizationWithOptions:completionHandler:]") __TVOS_PROHIBITED;
可以看见,这个方法在iOS 8.0也已经废弃了,改用上面提示的那两个方法了。
上面这两种注册方法,需要像iOS10以上的那个方法设置代理吗?简单来说,不用你去做,他们的代理叫UIApplicationDelegate
,系统已经给你设置好了。也就是说10以前的系统直接就可以写代理方法进行相应的处理了,不用再进行遵循代理这一个步骤了。
// 这个系统已经给你写好了
@interface AppDelegate : UIResponder <UIApplicationDelegate>
获取deviceToken
上面你去注册PUSH,注册成功后,苹果服务器就会返回给你deviceToken,在下面几个代理方法中获取deviceToken的状态已经进行其他处理。
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
NSLog(@"push - didRegisterUserNotificationSettings");
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(@"push - didRegisterForRemoteNotificationsWithDeviceToken");
NSString * deviceid = [NSString stringWithFormat:@"%@", deviceToken];
deviceid = [deviceid stringByReplacingOccurrencesOfString:@"<" withString:@""];
deviceid = [deviceid stringByReplacingOccurrencesOfString:@">" withString:@""];
deviceid = [deviceid stringByReplacingOccurrencesOfString:@" " withString:@""];
NSLog(@"push - deviceToken = %@", deviceid);
// 这个是我自己写的单例,用来存储关于App的一些基本信息,这里是用NSUserDefaults存储当前收到的deviceToken
[JJAppPrefs setDeviceToken:deviceid];
//这个是我写的一个单例,用来专门处理Push相关的,这个就是绑定token,将这个收到的deviceToken发送给你的服务器,
// 服务器进行绑定,你只能知道绑定成功或者失败,不用做其他的。
[[JJPushManager manager] bindDeviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(@"push - didFailToRegisterForRemoteNotificationsWithError = %@", error);
}
这里我们在代理方法中收到deviceToken,首先进行本地存储,然后在发给服务器进行和当前账号的绑定。
收到PUSH的处理
和注册PUSH一样,对于PUSH的处理也是要分系统版本的。
1. 代理方法的调用
iOS 10及其以后
//iOS10新增
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(ios(10.0))
{
NSLog(@"push - didReceiveNotificationResponse = %@", response);
[[JJPushManager manager] jj_userNotificationCenter:center didReceiveNotificationResponse:response];
completionHandler();
}
//iOS10新增
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0))
{
NSLog(@"push - willPresentNotification");
[[JJPushManager manager] jj_userNotificationCenter:center willPresentNotification:notification];
completionHandler(UNNotificationPresentationOptionBadge|
UNNotificationPresentationOptionSound|
UNNotificationPresentationOptionAlert);
}
这里有几个问题需要注意:
JJPushManager
是我自己写的单例,用来处理接收到的PUSH以及绑定和解绑deviceToken。这里在单例分出来进行处理,可以减少AppDelegate中代码的冗杂,分离出单利进行处理更清晰。关于iOS10这两个方法,收到横幅的时候
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0))
方法会调用,用户点击查看横幅的时候- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(ios(10.0))
会进行调用。对于iOS10以后的系统,无论App在前台、后台或者是杀死都会收到推送的横幅。
这样就收到开播通知了,下一步就是点击进行处理了。
iOS 9及其以后
这里接收通知的就一个方法
//iOS9及其以后方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSLog(@"push - didReceiveRemoteNotification = %@", userInfo);
[[JJPushManager manager] jj_application:application didReceiveRemoteNotification:userInfo];
}
这里有几个问题需要注意:
- iOS9系统当App在前台的时候是没有横幅的,这个时候就需要我们进行特殊的处理,一个方案是模拟成本地通知进行展示,另外一个就是弹出系统的AlertView,然后进行相关的业务逻辑处理,比如跳进某个主播的直播间。
2. PUSH处理
PUSH我统一在自己写的单例JJPushManager
中进行的处理。
@interface JJPushManager()
@property (nonatomic, strong) NSDictionary *currentPushDict;
@property (nonatomic, strong) JJFeed *feed;
@end
@implementation CPPushManager
#pragma mark - Class Public Function
+ (instancetype)manager
{
static JJPushManager *pushManager;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
pushManager = [[JJPushManager alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:pushManager selector:@selector(handlePushWhenApplicationActive) name:UIApplicationDidBecomeActiveNotification object:nil];
});
return pushManager;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Object Private Function
//根据信息数据返回Feed模型
- (CPFeed *)gainFeedInfo
{
self.feed = [[JJFeed alloc] initWithDict:self.currentPushDict];
return self.feed;
}
- (void)initCurrentPush:(NSDictionary *)currentPushDict
{
if (!currentPushDict) {
return;
}
self.currentPushDict = currentPushDict;
}
//直接跳到直播间
- (void)gotoPlayingRoom
{
// 未登录时,不处理push信息
if ([JJUserManager loadUserPref] == NO) {
return;
}
CPFeed *feedInfo = [self gainFeedInfo];
if (!feedInfo) {
return;
}
JJViewController *vc = (JJViewController *)JJCurrentNaviController.topViewController;
if ([vc isKindOfClass:[JJPlayingViewController class]]) {
return;
}
//直接跳转到直播间
JJPlayingViewController *playVC = [[JJPlayingViewController alloc] initWithFeed:feedInfo];
[CPCurrentNaviController pushViewController:playVC animated:YES];
}
#pragma mark - Object Public Function
//iOS10以下使用这个方法接收通知
- (void)jj_application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self initCurrentPush:userInfo];
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
JJViewController *vc = (JJViewController *)JJCurrentNaviController.topViewController;
if ([vc isKindOfClass:[JJPlayingViewController class]]) {
return;
}
[self gainFeedInfo];
NSString *titleStr = [NSString stringWithFormat:@"快来围观,%@开播啦~", self.feed.anchorPrefs.userNickName];
IMP_WSELF();
[vc showAlertViewWithTitle:titleStr message:nil cancelAction:nil ensureAction:^{
[wself gotoPlayingRoom];
}];
}
}
//iOS10新增
- (void)jj_userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response API_AVAILABLE(ios(10.0))
{
NSDictionary * userInfo = response.notification.request.content.userInfo;
[self initCurrentPush:userInfo];
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
[self gotoPlayingRoom];
}
}
//iOS10新增
- (void)jj_userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification API_AVAILABLE(ios(10.0))
{
NSDictionary * userInfo = notification.request.content.userInfo;
[self initCurrentPush:userInfo];
}
#pragma mark - Action && Notification
//进入Active消息通知
- (void)handlePushWhenApplicationActive
{
if (!self.currentPushDict) {
return;
}
[self gotoPlayingRoom];
self.currentPushDict = nil;
}
注意:上面只是简单的逻辑,更复杂的处理根据产品的需求进行额外的处理。
几个其他问题
1. 关于测试
开发和QA在进行测试的时候,都应该使用推送的开发证书进行测试,直接用正式证书测试是收不到的。
2. 关于到达率
利用开发证书进行推送测试,有的时候到达率并不是100%,我用11系统试了一下,基本是100%,用9系统测试,很大几率就收不到,偶尔可以收到,这个和苹果测试环境证书有关系,正式环境就没事了,这个我们客户端控制不了,也不用担心,这并不是bug。
3. 关于用户登录和退出
这个需要额外的处理,当用户登录(不管用手机号还是三方)都需要重新注册下PUSH,绑定deviceToken,当用户退出登录的时候,需要发送到服务端解绑deviceToken并移除deviceToken的本地存储。
登录
[(AppDelegate *)([UIApplication sharedApplication].delegate) registerPushNotifications];
退出登录
[[JJPushManager manager] unbindDeviceToken];
[JJUserManager clearData];
参考资料
后记
本篇主要讲述了系统PUSH的相关集成及问题,感兴趣的给个赞或者关注~~~~