iOS 遠(yuǎn)程通知

級(jí)別: ★★☆☆☆
標(biāo)簽:「iOS通知」「iOSPush」「遠(yuǎn)程通知」
作者: dac_1033
審校: QiShare團(tuán)隊(duì)

iOS中的通知(Notification)分為兩種:
1. iOS 本地通知
2. iOS 遠(yuǎn)程通知
3. iOS 通知擴(kuò)展


iOS中的通知包括本地通知和遠(yuǎn)程通知,兩種通知在iOS系統(tǒng)中通過(guò)橫幅或者彈出提醒兩種形式來(lái)告訴用戶,點(diǎn)擊系統(tǒng)彈出的通知會(huì)打開(kāi)應(yīng)用程序。

今天主要介紹iOS端關(guān)于遠(yuǎn)程通知的相關(guān)功能及操作。

遠(yuǎn)程通知

遠(yuǎn)程通知是通過(guò)蘋(píng)果的APNsApple Push Notification server)發(fā)送到App,而APNs必須先知道用戶設(shè)備的地址,然后才能向該設(shè)備發(fā)送通知。此地址采用設(shè)備令牌的形式,該設(shè)備令牌對(duì)于設(shè)備和應(yīng)用程序都是唯一的(即device token)。在啟動(dòng)時(shí),AppAPNs通信并接收device token,然后將其轉(zhuǎn)發(fā)到App ServerApp Server將包含該令牌及要發(fā)送的通知消息發(fā)送至APNs。(蘋(píng)果官網(wǎng)APNs概述

遠(yuǎn)程通知的傳遞涉及幾個(gè)關(guān)鍵組件:

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

蘋(píng)果官方提供的遠(yuǎn)程通知的傳遞示意圖如下:

遠(yuǎn)程通知的傳遞示意圖

遠(yuǎn)程通知中各關(guān)鍵組件之間的交互細(xì)節(jié):

遠(yuǎn)程通知各組件之間的交互細(xì)節(jié)

準(zhǔn)備工作:

(1)在蘋(píng)果開(kāi)發(fā)者賬號(hào)中創(chuàng)建的App ID不能使用通配ID,并且在所創(chuàng)建的APP ID的配置項(xiàng)中選擇Push Notifications服務(wù),App使用沒(méi)有選擇該服務(wù)的App ID所生成的推送證書(shū)和配置文件時(shí),無(wú)法完成注冊(cè)遠(yuǎn)程通知;
(2)當(dāng)前工程配置中的Bundle Identifier必須和生成配置文件使用的APP ID完全一致;
(3)當(dāng)前工程配置中的“Capabilities”需設(shè)置為ON;
(4)遠(yuǎn)程推送必須真機(jī)調(diào)試,模擬器無(wú)法獲取得到device token。

詳細(xì)步驟:

  1. 在AppDelegate中注冊(cè)APNs消息
-(void)registerRemoteNotification {
    
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 // Xcode 8編譯會(huì)調(diào)用
        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編譯會(huì)調(diào)用
        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
  • 在注冊(cè)遠(yuǎn)程通知之后,獲取device token成功回調(diào):
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
  • 獲取device token失?。?/li>
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
  1. App將device token發(fā)送給App Server
    只有蘋(píng)果公司知道device token的生成算法,保證唯一,device token在App重裝等情況時(shí)會(huì)變化,因此為確保device token變化后App仍然能夠正常接收服務(wù)器端發(fā)送的通知,建議每次應(yīng)用程序啟動(dòng)都重新獲得device token,并傳給App Server。

  2. App Server根據(jù)最新的device token將要推送的消息發(fā)送給APNs
    將指定device token和消息內(nèi)容發(fā)送給APNs時(shí),消息內(nèi)容的格式必須完全按照蘋(píng)果官方的消息格式組織消息內(nèi)容,點(diǎn)擊查看遠(yuǎn)程通知消息的字段創(chuàng)建遠(yuǎn)程通知消息。

消息格式的例子如下:
{"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根據(jù)device token查找相應(yīng)設(shè)備,并推送消息
    一般情況APNs可以根據(jù)deviceToken將消息成功推送到相應(yīng)設(shè)備中,但也存在用戶卸載程序等原因?qū)е峦扑拖⑹〉那闆r,這時(shí)App服務(wù)端會(huì)收到APNs返回的錯(cuò)誤信息)。

  2. AppDelegate.m中的回調(diào)方法

// iOS<10時(shí),且app被完全殺死
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

// 注:iOS10以上,如果不使用UNUserNotificationCenter,將走此回調(diào)方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;

// iOS7及以上系統(tǒng)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;

//  iOS>=10: App在前臺(tái)獲取到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler;

//  iOS>=10: 點(diǎn)擊通知進(jìn)入App時(shí)觸發(fā)(殺死/切到后臺(tái)喚起)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;

注冊(cè)遠(yuǎn)程通知及解析通知數(shù)據(jù)的代碼如下:

#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 {
   
    // 注冊(cè)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編譯會(huì)調(diào)用
        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編譯會(huì)調(diào)用
        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,將走此回調(diào)方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    // iOS6及以下系統(tǒng)
    if (userInfo) {
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {// app位于前臺(tái)通知
            NSLog(@"app位于前臺(tái)通知(didReceiveRemoteNotification:):%@", userInfo);
        } else {// 切到后臺(tái)喚起
            NSLog(@"app位于后臺(tái)通知(didReceiveRemoteNotification:):%@", userInfo);
        }
    }
}

// 注:
// 1. 該回調(diào)方法,App殺死后并不執(zhí)行;
// 2. 該回調(diào)方法,會(huì)與application:didReceiveRemoteNotification:互斥執(zhí)行;
// 3. 該回調(diào)方法,會(huì)與userNotificationCenter:willPresentNotification:withCompletionHandler:一并執(zhí)行;
// 4. 該回調(diào)方法,會(huì)與userNotificationCenter:didReceiveNotificationResponse::withCompletionHandler:一并執(zhí)行。
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler NS_AVAILABLE_IOS(7_0) {
    // iOS7及以上系統(tǒng)
    if (userInfo) {
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {// app位于前臺(tái)通知
            NSLog(@"app位于前臺(tái)通知(didReceiveRemoteNotification:fetchCompletionHandler:):%@", userInfo);
        } else {// 切到后臺(tái)喚起
            NSLog(@"app位于后臺(tái)通知(didReceiveRemoteNotification:fetchCompletionHandler:):%@", userInfo);
        }
    }
    completionHandler(UIBackgroundFetchResultNewData);
}

#pragma mark - iOS>=10 中收到推送消息

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
//  iOS>=10: App在前臺(tái)獲取到通知
- (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位于前臺(tái)通知(willPresentNotification:):%@", userInfo);
    }
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);;
}

//  iO>=10: 點(diǎn)擊通知進(jìn)入App時(shí)觸發(fā)(殺死/切到后臺(tái)喚起)
- (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(@"點(diǎn)擊通知進(jìn)入App時(shí)觸發(fā)(didReceiveNotificationResponse:):%@", userInfo);
    }
    completionHandler();
}
#endif


@end
  1. 模擬推送工具“Pusher”
    本文只側(cè)重于介紹iOS端對(duì)遠(yuǎn)程推送通知的處理,因此我們把App Server對(duì)應(yīng)的處理過(guò)程交給了第三方工具,第三方推送測(cè)試工具有很多,如SmartPush、Pusher等,在這里我們選用Pusher作為測(cè)試工具,Pusher的GitHub地址。
    Pusher截圖

Pusher的使用步驟說(shuō)明:
(1)選擇p12格式的推送證書(shū);
(2)設(shè)置是否為測(cè)試環(huán)境(默認(rèn)勾選為測(cè)試環(huán)境,由于推送證書(shū)分為測(cè)試推送證書(shū)和生產(chǎn)測(cè)試證書(shū),并且蘋(píng)果的APNs也分為測(cè)試和生產(chǎn)兩套環(huán)境,因此Pusher需要手動(dòng)置頂是否為測(cè)試環(huán)境);
(3)輸入device token
(4)輸入符合蘋(píng)果要求的推送內(nèi)容字符串;
(5)當(dāng)確認(rèn)手機(jī)端設(shè)置無(wú)誤,并且以上4點(diǎn)設(shè)置正確時(shí),執(zhí)行推送。
Pusher推送的消息,以第4點(diǎn)中的示例為例進(jìn)行測(cè)試,手機(jī)收到遠(yuǎn)程推送通知的效果截圖如下:

iOS遠(yuǎn)程推送通知效果圖

點(diǎn)擊遠(yuǎn)程推送通知橫幅打開(kāi)App,在回調(diào)中獲取的json串:
點(diǎn)擊橫幅,在App回調(diào)方法中獲取數(shù)據(jù)

備注:
(1)要使用APNs向非運(yùn)行的應(yīng)用程序提供遠(yuǎn)程通知,需要至少啟動(dòng)目標(biāo)應(yīng)用程序一次;
(2)設(shè)備沒(méi)有網(wǎng)絡(luò)的情況下,是無(wú)法注冊(cè)遠(yuǎn)程通知的;
(3)一般情況下,device token是不會(huì)發(fā)生變化的,即雖然調(diào)用注冊(cè)遠(yuǎn)程通知的方法,但是返回的device token仍然是之前得到的值;如果設(shè)備令牌在應(yīng)用程序執(zhí)行時(shí)發(fā)生更改,則應(yīng)用程序?qū)ο笤俅握{(diào)用相應(yīng)的委托方法以通知更改;
(4)推送過(guò)程中的消息json串可在適當(dāng)位置添加自定義字段,整個(gè)消息最大長(zhǎng)度為4 KB4096字節(jié)),超過(guò)最大允許長(zhǎng)度,則拒絕通知;
(5)在iOS及以上系統(tǒng)中遠(yuǎn)程通知還包括“通知擴(kuò)展”功能,在下一篇文章中介紹。

本文Demo鏈接:GitHub地址

專(zhuān)欄下一篇:iOS 通知擴(kuò)展


了解更多iOS及相關(guān)新技術(shù),請(qǐng)關(guān)注我們的公眾號(hào):

關(guān)注我們的途徑有:
QiShare(簡(jiǎn)書(shū))
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號(hào))

推薦文章:
在iOS 12中無(wú)法獲取WiFi的SSID了?別慌!
Web安全漏洞之CSRF
奇舞周刊276期

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容