版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.06.21 |
前言
我們做APP很多時(shí)候都需要推送功能,以直播為例为迈,如果你關(guān)注的主播開(kāi)播了敌蜂,那么就需要向關(guān)注這個(gè)主播的人發(fā)送開(kāi)播通知箩兽,提醒用戶去看播,這個(gè)只是一個(gè)小的方面章喉,具體應(yīng)用根據(jù)公司的業(yè)務(wù)邏輯而定汗贫。前面已經(jīng)花了很多篇幅介紹了極光推送,其實(shí)極光推送無(wú)非就是將我們客戶端和服務(wù)端做的很多東西封裝了一下秸脱,節(jié)省了我們很多處理邏輯和流程落包,這一篇開(kāi)始,我們就利用系統(tǒng)的原生推送類(lèi)結(jié)合工程實(shí)踐說(shuō)一下系統(tǒng)推送的集成摊唇,希望我的講解能讓大家很清楚的理解它咐蝇。
幾個(gè)需要提前知道的問(wèn)題
1. 推送流程是如何的?
推送是蘋(píng)果系統(tǒng)提供的功能遏片,其實(shí)基本流程就是我們的客戶端要用deviceToken在蘋(píng)果服務(wù)器進(jìn)行注冊(cè)嘹害,并將這個(gè)deviceToken發(fā)送給我們的服務(wù)端綁定我們的賬號(hào),服務(wù)端將要推送的內(nèi)容發(fā)送給蘋(píng)果吮便,蘋(píng)果就會(huì)將定制化的內(nèi)容根據(jù)deviceToken發(fā)送給我們客戶端笔呀,我們就收到推送了。
也就是說(shuō)髓需,我們客戶端除了注冊(cè)PUSH许师,接收蘋(píng)果給我們的deviceToken,發(fā)送給我們服務(wù)端和當(dāng)前賬號(hào)綁定以外僚匆,我們除了等著收到PUSH進(jìn)行處理就決定不了別的事情了微渠,至于推送什么是我們服務(wù)端決定的,什么時(shí)候能收到就看蘋(píng)果服務(wù)器了咧擂。
下面這張圖就是很好的說(shuō)明了推送的原理逞盆,圖片源于網(wǎng)絡(luò)。
2. deviceToken是如何獲取的松申?有什么用云芦?
deviceToken是我們用蘋(píng)果的API注冊(cè)PUSH的時(shí)候,蘋(píng)果的服務(wù)器發(fā)給我們的贸桶,我們?cè)诖矸椒?code>- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken中進(jìn)行接收舅逸。那么這個(gè)是做什么的呢?這個(gè)就是蘋(píng)果發(fā)送個(gè)性化推送找到你機(jī)器的憑證皇筛,有了它琉历,蘋(píng)果系統(tǒng)就知道這個(gè)消息要推送給你。
我們?cè)诖矸椒ㄖ惺盏酵扑秃螅话阋獙⑦@個(gè)deviceToken發(fā)送給我們的服務(wù)器旗笔,讓服務(wù)器將這個(gè)deviceToken和你當(dāng)前App的賬號(hào)進(jìn)行綁定彪置,這樣才能進(jìn)行個(gè)性化的推送和消息訂制。
- (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);
// 這個(gè)是我自己寫(xiě)的單例换团,用來(lái)存儲(chǔ)關(guān)于App的一些基本信息悉稠,這里是用NSUserDefaults存儲(chǔ)當(dāng)前收到的deviceToken
[JJAppPrefs setDeviceToken:deviceid];
//這個(gè)是我寫(xiě)的一個(gè)單例宫蛆,用來(lái)專(zhuān)門(mén)處理Push相關(guān)的艘包,這個(gè)就是綁定token,將這個(gè)收到的deviceToken發(fā)送給你的服務(wù)器耀盗,
// 服務(wù)器進(jìn)行綁定想虎,你只能知道綁定成功或者失敗,不用做其他的叛拷。
[[JJPushManager manager] bindDeviceToken];
}
3. 客戶端要如何開(kāi)啟推送舌厨?
這個(gè)是一個(gè)開(kāi)關(guān),只要點(diǎn)擊一下就可以了忿薇,就能開(kāi)啟推送了裙椭。
4. 關(guān)于證書(shū)要做什么?
我們做推送是要證書(shū)的署浩,讓有賬號(hào)的人生成兩個(gè)證書(shū)揉燃,一個(gè)是測(cè)試環(huán)境證書(shū),一個(gè)是生產(chǎn)環(huán)境證書(shū)筋栋。
具體如何生成證書(shū)可以看網(wǎng)上的資料炊汤,跟著一步步的做就可以了,這里就不多說(shuō)了弊攘。
注冊(cè)PUSH
截止目前抢腐,iOS已經(jīng)有了11的系統(tǒng),注冊(cè)PUSH從10系統(tǒng)以后就要考慮版本的差異了襟交。
如果你是在iOS10以前做PUSH是不用這么麻煩的迈倍,但是隨著iOS10的更新,一個(gè)新類(lèi)UNUserNotificationCenter
出來(lái)了捣域,所以要根據(jù)版本的不同進(jìn)行不同的注冊(cè)邏輯啼染。
iOS10及其以后版本
//10及其以后系統(tǒng)使用下面方法進(jìn)行注冊(cè)
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setDelegate:self];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge |
UNAuthorizationOptionSound)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@"push - 注冊(cè)成功");
}
else{
NSLog(@"push - 注冊(cè)失敗");
}
}];
[[UIApplication sharedApplication] registerForRemoteNotifications];
這里,[[UIApplication sharedApplication] registerForRemoteNotifications];
一定不能少竟宋,要不你是收不到deviceToken的提完。因?yàn)槟悴粚?xiě)這句話表示你沒(méi)有注冊(cè)。
同時(shí)丘侠,你要設(shè)置好代理@interface AppDelegate () <UNUserNotificationCenterDelegate>
徒欣,否則你處理PUSH的方法都不會(huì)進(jìn)行回調(diào)的。
iOS9及其以前版本
iOS9及其以前版本使用類(lèi)UIUserNotificationSettings
進(jìn)行注冊(cè)PUSH的蜗字。
//9及其以前系統(tǒng)使用下面方法進(jìn)行注冊(cè)
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)];
}
這里有幾個(gè)小的問(wèn)題打肝,UIUserNotificationSettings
在10系統(tǒng)出現(xiàn)就廢棄了這個(gè)類(lèi)脂新。
NS_CLASS_DEPRECATED_IOS(8_0, 10_0, "Use UserNotifications Framework's UNNotificationSettings") __TVOS_PROHIBITED
@interface UIUserNotificationSettings : NSObject
這里大家也注意到了這個(gè)方法
- (void)registerForRemoteNotificationTypes:(UIRemoteNotificationType)types NS_DEPRECATED_IOS(3_0, 8_0, "Use -[UIApplication registerForRemoteNotifications] and UserNotifications Framework's -[UNUserNotificationCenter requestAuthorizationWithOptions:completionHandler:]") __TVOS_PROHIBITED;
可以看見(jiàn),這個(gè)方法在iOS 8.0也已經(jīng)廢棄了粗梭,改用上面提示的那兩個(gè)方法了争便。
上面這兩種注冊(cè)方法,需要像iOS10以上的那個(gè)方法設(shè)置代理嗎断医?簡(jiǎn)單來(lái)說(shuō)滞乙,不用你去做,他們的代理叫UIApplicationDelegate
鉴嗤,系統(tǒng)已經(jīng)給你設(shè)置好了斩启。也就是說(shuō)10以前的系統(tǒng)直接就可以寫(xiě)代理方法進(jìn)行相應(yīng)的處理了,不用再進(jìn)行遵循代理這一個(gè)步驟了醉锅。
// 這個(gè)系統(tǒng)已經(jīng)給你寫(xiě)好了
@interface AppDelegate : UIResponder <UIApplicationDelegate>
獲取deviceToken
上面你去注冊(cè)PUSH兔簇,注冊(cè)成功后,蘋(píng)果服務(wù)器就會(huì)返回給你deviceToken硬耍,在下面幾個(gè)代理方法中獲取deviceToken的狀態(tài)已經(jīng)進(jìn)行其他處理垄琐。
- (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);
// 這個(gè)是我自己寫(xiě)的單例,用來(lái)存儲(chǔ)關(guān)于App的一些基本信息经柴,這里是用NSUserDefaults存儲(chǔ)當(dāng)前收到的deviceToken
[JJAppPrefs setDeviceToken:deviceid];
//這個(gè)是我寫(xiě)的一個(gè)單例狸窘,用來(lái)專(zhuān)門(mén)處理Push相關(guān)的,這個(gè)就是綁定token口锭,將這個(gè)收到的deviceToken發(fā)送給你的服務(wù)器朦前,
// 服務(wù)器進(jìn)行綁定,你只能知道綁定成功或者失敗鹃操,不用做其他的韭寸。
[[JJPushManager manager] bindDeviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
NSLog(@"push - didFailToRegisterForRemoteNotificationsWithError = %@", error);
}
這里我們?cè)诖矸椒ㄖ惺盏絛eviceToken,首先進(jìn)行本地存儲(chǔ)荆隘,然后在發(fā)給服務(wù)器進(jìn)行和當(dāng)前賬號(hào)的綁定恩伺。
收到PUSH的處理
和注冊(cè)PUSH一樣,對(duì)于PUSH的處理也是要分系統(tǒng)版本的椰拒。
1. 代理方法的調(diào)用
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);
}
這里有幾個(gè)問(wèn)題需要注意:
JJPushManager
是我自己寫(xiě)的單例晶渠,用來(lái)處理接收到的PUSH以及綁定和解綁deviceToken。這里在單例分出來(lái)進(jìn)行處理燃观,可以減少AppDelegate中代碼的冗雜褒脯,分離出單利進(jìn)行處理更清晰。關(guān)于iOS10這兩個(gè)方法缆毁,收到橫幅的時(shí)候
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0))
方法會(huì)調(diào)用番川,用戶點(diǎn)擊查看橫幅的時(shí)候- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler API_AVAILABLE(ios(10.0))
會(huì)進(jìn)行調(diào)用。對(duì)于iOS10以后的系統(tǒng),無(wú)論App在前臺(tái)颁督、后臺(tái)或者是殺死都會(huì)收到推送的橫幅践啄。
這樣就收到開(kāi)播通知了,下一步就是點(diǎn)擊進(jìn)行處理了沉御。
iOS 9及其以后
這里接收通知的就一個(gè)方法
//iOS9及其以后方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSLog(@"push - didReceiveRemoteNotification = %@", userInfo);
[[JJPushManager manager] jj_application:application didReceiveRemoteNotification:userInfo];
}
這里有幾個(gè)問(wèn)題需要注意:
- iOS9系統(tǒng)當(dāng)App在前臺(tái)的時(shí)候是沒(méi)有橫幅的屿讽,這個(gè)時(shí)候就需要我們進(jìn)行特殊的處理,一個(gè)方案是模擬成本地通知進(jìn)行展示吠裆,另外一個(gè)就是彈出系統(tǒng)的AlertView伐谈,然后進(jìn)行相關(guān)的業(yè)務(wù)邏輯處理,比如跳進(jìn)某個(gè)主播的直播間硫痰。
2. PUSH處理
PUSH我統(tǒng)一在自己寫(xiě)的單例JJPushManager
中進(jìn)行的處理衩婚。
@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
//根據(jù)信息數(shù)據(jù)返回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
{
// 未登錄時(shí)窜护,不處理push信息
if ([JJUserManager loadUserPref] == NO) {
return;
}
CPFeed *feedInfo = [self gainFeedInfo];
if (!feedInfo) {
return;
}
JJViewController *vc = (JJViewController *)JJCurrentNaviController.topViewController;
if ([vc isKindOfClass:[JJPlayingViewController class]]) {
return;
}
//直接跳轉(zhuǎn)到直播間
JJPlayingViewController *playVC = [[JJPlayingViewController alloc] initWithFeed:feedInfo];
[CPCurrentNaviController pushViewController:playVC animated:YES];
}
#pragma mark - Object Public Function
//iOS10以下使用這個(gè)方法接收通知
- (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:@"快來(lái)圍觀效斑,%@開(kāi)播啦~", 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
//進(jìn)入Active消息通知
- (void)handlePushWhenApplicationActive
{
if (!self.currentPushDict) {
return;
}
[self gotoPlayingRoom];
self.currentPushDict = nil;
}
注意:上面只是簡(jiǎn)單的邏輯,更復(fù)雜的處理根據(jù)產(chǎn)品的需求進(jìn)行額外的處理柱徙。
幾個(gè)其他問(wèn)題
1. 關(guān)于測(cè)試
開(kāi)發(fā)和QA在進(jìn)行測(cè)試的時(shí)候缓屠,都應(yīng)該使用推送的開(kāi)發(fā)證書(shū)進(jìn)行測(cè)試,直接用正式證書(shū)測(cè)試是收不到的护侮。
2. 關(guān)于到達(dá)率
利用開(kāi)發(fā)證書(shū)進(jìn)行推送測(cè)試敌完,有的時(shí)候到達(dá)率并不是100%,我用11系統(tǒng)試了一下羊初,基本是100%滨溉,用9系統(tǒng)測(cè)試,很大幾率就收不到长赞,偶爾可以收到晦攒,這個(gè)和蘋(píng)果測(cè)試環(huán)境證書(shū)有關(guān)系,正式環(huán)境就沒(méi)事了得哆,這個(gè)我們客戶端控制不了脯颜,也不用擔(dān)心,這并不是bug贩据。
3. 關(guān)于用戶登錄和退出
這個(gè)需要額外的處理栋操,當(dāng)用戶登錄(不管用手機(jī)號(hào)還是三方)都需要重新注冊(cè)下PUSH,綁定deviceToken饱亮,當(dāng)用戶退出登錄的時(shí)候矾芙,需要發(fā)送到服務(wù)端解綁deviceToken并移除deviceToken的本地存儲(chǔ)。
登錄
[(AppDelegate *)([UIApplication sharedApplication].delegate) registerPushNotifications];
退出登錄
[[JJPushManager manager] unbindDeviceToken];
[JJUserManager clearData];
參考資料
后記
本篇主要講述了系統(tǒng)PUSH的相關(guān)集成及問(wèn)題近上,感興趣的給個(gè)贊或者關(guān)注~~~~