iOS 遠程推送

級別: ★★☆☆☆
標籤:「iOS推送」「iOSPush」「遠程推送」
做者: dac_1033
審校: QiShare團隊php

iOS中的Push(通知)分爲兩種:
1. iOS 本地推送
2. iOS 遠程推送html


iOS中的通知包括本地推送通知和遠程推送通知,兩種通知在iOS系統中經過橫幅或者彈出提醒兩種形式來告訴用戶,點擊系統彈出的通知會打開應用程序。ios

今天主要介紹iOS端關於遠程推送通知的相關功能及操做。git

遠程推送通知

遠程推送通知是經過蘋果的APNsApple Push Notification server)發送到App,而APNs必須先知道用戶設備的地址,而後才能向該設備發送通知。此地址採用設備令牌的形式,該設備令牌對於設備和應用程序都是惟一的(即device token)。在啓動時,AppAPNs通訊並接收device token,而後將其轉發到App ServerApp Server將包含該令牌及要發送的通知消息發送至APNs。(蘋果官網APNs概述github

遠程通知的傳遞涉及幾個關鍵組件:算法

  • App Server
  • Apple推送通知服務(APNs)
  • 用戶的設備(包括iPhone、iPad、iTouch、mac等)
  • 相應的App

蘋果官方提供的遠程通知的傳遞示意圖以下: json

遠程通知的傳遞示意圖
遠程通知中各關鍵組件之間的交互細節:

遠程通知各組件之間的交互細節

準備工做:
安全

(1)在蘋果開發者帳號中建立的App ID不能使用通配ID,而且在所建立的APP ID的配置項中選擇Push Notifications服務,App使用沒有選擇該服務的App ID所生成的推送證書和配置文件時,沒法完成註冊遠程通知;
(2)當前工程配置中的Bundle Identifier必須和生成配置文件使用的APP ID徹底一致;
(3)當前工程配置中的「Capabilities」需設置爲ON
(4)遠程推送必須真機調試,模擬器沒法獲取獲得device tokenbash

詳細步驟:服務器

  1. 在AppDelegate中註冊APNs推送消息
-(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];
    }
}
複製代碼
  1. App獲取device token
  • 在註冊遠程通知以後,獲取device token成功回調:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
複製代碼
  • 獲取device token失敗:
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
複製代碼
  1. App將device token發送給App Server 只有蘋果公司知道device token的生成算法,保證惟一,device token在App重裝等狀況時會變化,所以爲確保device token變化後App仍然可以正常接收服務器端發送的通知,建議每次應用程序啓動都從新得到device token,並傳給App Server

  2. 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"}

  1. APNs根據device token查找相應設備,並推送消息 通常狀況APNs能夠根據deviceToken將消息成功推送消息到相應設備中,但也存在用戶卸載程序等緣由致使推送消息失敗的狀況,這時App服務端會收到APNs返回的錯誤信息)。

  2. 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
複製代碼
  1. 模擬推送工具「Pusher」 本文只側重於介紹iOS端對遠程推送通知的處理,所以咱們把App Server對應的處理過程交給了第三方工具,第三方推送測試工具備不少,如SmartPushPusher等,在這裏咱們選用Pusher做爲測試工具,Pusher的GitHub地址
    Pusher截圖

Pusher的使用步驟說明:
(1)選擇p12格式的推送證書;
(2)設置是否爲測試環境(默認勾選爲測試環境,因爲推送證書分爲測試推送證書和生產測試證書,而且蘋果的APNs也分爲測試和生產兩套環境,所以Pusher須要手動置頂是否爲測試環境);
(3)輸入device token
(4)輸入符合蘋果要求的推送內容字符串;
(5)當確認手機端設置無誤,而且以上4點設置正確時,執行推送。
Pusher推送的消息,以第4點中的示例爲例進行測試,手機收到遠程推送通知的效果截圖以下:

iOS遠程推送通知效果圖
點擊遠程推送通知橫幅打開App,在回調中獲取的 json串:
點擊橫幅,在App回調方法中獲取數據

備註:
(1)要使用APNs向非運行的應用程序提供遠程通知,須要至少啓動目標應用程序一次;
(2)設備沒有網絡的狀況下,是沒法註冊遠程通知的;
(3)通常狀況下,device token是不會發生變化的,即雖然調用註冊遠程通知的方法,可是返回的device token仍然是以前獲得的值;若是設備令牌在應用程序執行時發生更改,則應用程序對象再次調用相應的委託方法以通知更改;
(4)推送過程當中的消息json串可在適當位置添加自定義字段,整個消息最大長度爲4 KB4096字節),超過最大容許長度,則拒絕通知;
(5)在iOS及以上系統中遠程通知還包括「通知擴展」功能,在下一篇文章中介紹。

本文Demo連接:GitHub地址


關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)

推薦文章:
在iOS 12中沒法獲取WiFi的SSID了?別慌!
Web安全漏洞之CSRF

相關文章
相關標籤/搜索