級別: ★★☆☆☆
標籤:「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(微信公衆號)