1. 程式人生 > >iOS 推送通知:本地通知(UILocalNotification)和遠端通知(APNs)詳解

iOS 推送通知:本地通知(UILocalNotification)和遠端通知(APNs)詳解

——本地推送通知

推送通知的作用?

在App退到後臺或者完全退出時,可以使用通知來告訴使用者某件事情,比如推送新的聊天訊息、新聞等

通知對應的效果:

  1. 在主螢幕的頂端會出現通知訊息
  2. 當手機鎖屏時出現在鎖屏介面,可以通過滑動開啟該App,
  3. 在通知中心中出現推送的訊息
  4. App圖示會有微標值① badgeValue
  5. 推送通知提示音

注意:
傳送通知時,如果程式正在前臺允許,那麼推送通知UI就不會顯示出來;點選通知系統預設會開啟該App。

這裡寫圖片描述 - 這裡寫圖片描述
這裡寫圖片描述 - 這裡寫圖片描述

通知的種類:

  • 通知設計模式(NSNotification)
  • 本地通知(UILocalNotification)
  • 遠端通知(APNs)

通知設計模式:
——是一種設計模式,是一種設計思想,是抽象的,推送通知(本地和遠端)是肉眼可以看到的,是有介面的。

本地推送通知:
——本地通知不需要連線網路,一般是開發人員在合適的情況下在App內傳送通知,應用場景:當能夠確定在某個時間時需要提醒使用者。

遠端通知:
——遠端通知必須需要連線網路,遠端推送服務又稱為APNs(Apple Push Notification Services),一般是伺服器端傳送通知。

對於使用者,通知一般是指的推送通知,即本地推送通知和遠端推送通知
推送通知的代理類是: AppDelegate

推送通知的使用場景:

  • 一些任務管理App,會在任務時間即將到達時,通知你該做任務了。如:提醒事項App、 電影App:電影即將開始提示使用者按時到達影院。
  • 聊天App:程式退出到後臺或者完全退出時收到訊息
  • 電商App:推薦新品時
  • 新聞App:推送新聞

知識準備

  • (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions :啟動選項引數:當程式是通過點選應用程式圖示時該引數是nil,當應用程式完全退出時,點選推送通知時該引數不為空,key為UIApplicationLaunchOptionsLocalNotificationKey

通常當用戶點選通知時會做一些業務處理,如QQ在前臺狀態下會將提醒數字+1, 當應用程式在後臺狀態或完全退出狀態下會開啟對應的聊天視窗

本地通知的基本使用

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

// 簡單例項:點選通知進入App
- (IBAction)postLocalNotification:(id)sender {
    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    localNotification.alertBody = @"hello, 你好啊!- alertBody + fireDate";
    localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:3]; // 3秒鐘後

    //--------------------可選屬性------------------------------
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.2) {
        localNotification.alertTitle = @"推送通知提示標題:alertTitle"; // iOS8.2
    }

    // 鎖屏時在推送訊息的最下方顯示設定的提示字串
    localNotification.alertAction = @"點選檢視訊息";

    // 當點選推送通知訊息時,首先顯示啟動圖片,然後再開啟App, 預設是直接開啟App的
    localNotification.alertLaunchImage = @"LaunchImage.png";

    // 預設是沒有任何聲音的 UILocalNotificationDefaultSoundName:聲音類似於震動的聲音
    localNotification.soundName = UILocalNotificationDefaultSoundName;

    // 傳遞引數
    localNotification.userInfo = @{@"type": @"1"};

    //重複間隔:類似於定時器,每隔一段時間就傳送通知
//  localNotification.repeatInterval = kCFCalendarUnitSecond;

    localNotification.category = @"choose"; // 附加操作

    // 定時傳送
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
    NSInteger applicationIconBadgeNumber =  [[UIApplication sharedApplication] applicationIconBadgeNumber] + 1;
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:applicationIconBadgeNumber];

    // 立即傳送
//    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}

// 示例1:簡單示例:點選通知進入App
//- (IBAction)postLocalNotification:(id)sender {
//    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
//    localNotification.alertBody = @"hello, 你好啊!- alertBody + fireDate";
//    localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:5]; // 5秒鐘後
//    
//    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
//}
@end
#import "AppDelegate.h"
#import "AppDelegate+PrivateMethod.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    if (launchOptions != nil) {
        UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
        if (localNotification != nil) {
            // 程式完全退出狀態下,點選推送通知後的業務處理
            // 如QQ會開啟想對應的聊天視窗
            NSInteger applicationIconBadgeNumber = application.applicationIconBadgeNumber - 1;
            application.applicationIconBadgeNumber = applicationIconBadgeNumber >= 0 ? applicationIconBadgeNumber : 0;
        }
    }

    [self registerUserNotificationSettingsForIOS80];

    return YES;
}


// 當App在前臺狀態下,如果有通知會呼叫該方法
// 當應用程式在後臺狀態下,點選推送通知,程式從後臺進入前臺後,會呼叫該方法(從鎖屏介面點選推送通知從後臺進入前臺也會執行)
// 當應用程式完全退出時不呼叫該方法
- (void)application:(UIApplication *)application didReceiveLocalNotification:(nonnull UILocalNotification *)notification {
    NSLog(@"%@", notification);

    // 處理點選通知後對應的業務
    UIApplicationState applicationState = [[UIApplication sharedApplication] applicationState];
    if (applicationState == UIApplicationStateActive) {     // 前臺
        // 例如QQ會增加tabBar上的badgeValue未讀數量
    } else if (applicationState == UIApplicationStateInactive) {// 從前臺進入後臺
        // 例如QQ會開啟對應的聊天視窗
        NSInteger applicationIconBadgeNumber = application.applicationIconBadgeNumber - 1;
        application.applicationIconBadgeNumber = applicationIconBadgeNumber >= 0 ? applicationIconBadgeNumber : 0;
    }

    [application cancelLocalNotification:notification];
}

// 監聽附加操作按鈕
- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(nonnull UILocalNotification *)notification completionHandler:(nonnull void (^)())completionHandler {
    NSLog(@"identifier:%@", identifier);
    completionHandler();
}

// 該方法在iOS9.0後呼叫,iOS9.0之前呼叫上面那個方法
- (void)application:(UIApplication *)app handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(nonnull UILocalNotification *)notification withResponseInfo:(nonnull NSDictionary *)responseInfo completionHandler:(nonnull void (^)())completionHandler {
    // ====identifier:no, content:{UIUserNotificationActionResponseTypedTextKey = "not agree";}
    NSLog(@"====identifier:%@, content:%@", identifier, responseInfo);
    completionHandler();
}
@end
#import "AppDelegate.h"

@interface AppDelegate (PrivateMethod)

- (void)registerUserNotificationSettingsForIOS80;

@end

#import "AppDelegate+PrivateMethod.h"

@implementation AppDelegate (PrivateMethod)

- (void)registerUserNotificationSettingsForIOS80 {
    // iOS8.0 適配
    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        // categories: 推送訊息的附加操作,可以為nil,此時值顯示訊息,如果不為空,可以在推送訊息的後面增加幾個按鈕(如同意、不同意)

        UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
        category.identifier = @"choose";

        // 同意
        UIMutableUserNotificationAction *action1 = [[UIMutableUserNotificationAction alloc] init];
        action1.identifier = @"yes";
        action1.title = @"同意";
        action1.activationMode = UIUserNotificationActivationModeForeground;  // 點選按鈕是否進入前臺
        action1.authenticationRequired = true;
        action1.destructive = false;

        // 不同意
        UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
        action2.identifier = @"no";
        action2.title = @"不同意";
        action2.activationMode = UIUserNotificationActivationModeBackground;  // 後臺模式,點選了按鈕就完了
        action2.authenticationRequired = true;
        action2.destructive = true;
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
            action2.behavior = UIUserNotificationActionBehaviorTextInput;
            action2.parameters = @{UIUserNotificationTextInputActionButtonTitleKey: @"拒絕原因"};
        }


        [category setActions:@[action1, action2] forContext:UIUserNotificationActionContextDefault];


        NSSet<UIUserNotificationCategory *> *categories = [NSSet setWithObjects:category, nil];

        UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
        UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:categories];
        [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
    }
}
@end

執行效果:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

—————遠端推送通知—————

什麼是遠端推送通知

  • 從遠端伺服器推送給客戶端的通知,需要連線網路
  • 遠端推送服務,又稱為APNs 蘋果推送通知服務(Apple Push Notification services)

傳統推送通知

傳統推送通知是相對於APNs而言的,傳統推送通知的原理是:當APP開啟時和App的伺服器建立一個長連線(需要網路),當需要通知的時候,App伺服器通過長連線向對應的客戶端傳送資料,當客戶端接收到資料時使用UILocalNotfication本地通知的方式來展示,這樣就實現了傳統推送通知。

傳統推送通知必須要聯網,如果關閉了App或者打開了App但是無法連線伺服器了,這些情況都收不到通知了。

APNs遠端推送通知:只要手機聯網了,不管App是開啟或者關閉都能接收到蘋果伺服器推送的通知

APNs

  • 所有蘋果裝置在聯網狀態下都會與蘋果伺服器建立長連線,連線是雙向的,蘋果裝置可以向蘋果伺服器傳送請求,蘋果伺服器也可以向蘋果裝置傳送請求。

蘋果伺服器常用的通知功能:

  • 時間校準
  • 系統升級
  • 查詢我的iPhone

長連線的好處:更加及時

遠端通知的過程:
例如微信App:首先每個聯網並開啟微信的App都與微信伺服器有一個長連線,當微信A使用者向微信B使用者傳送一個訊息時,微信A使用者將訊息傳送到微信伺服器,然後微信伺服器判斷微信B使用者是否和微信伺服器建立了長連線,如果有直接通過微信B使用者和微信伺服器建立的連線管道直接傳送即可,這樣微信B使用者就能收到訊息;如果微信B使用者此時沒有開啟微信App,那麼微信伺服器就將訊息傳送給蘋果伺服器,蘋果伺服器再講訊息傳送到某臺蘋果裝置上。蘋果是怎麼知道該傳送給那臺裝置呢?使用者A傳送訊息時需要將使用者B的UDID和微信App的Bundle ID 附帶在訊息上一塊傳送給B使用者,這些訊息微信伺服器又傳送給蘋果伺服器,蘋果伺服器通過UDID就知道傳送給那臺裝置了,然後通過Bundle ID就知道是哪個App傳送的了。蘋果根據UDID + Bundle ID 生成一個deviceToken, 這樣每條微信訊息中都加上deviceToken蘋果伺服器就能識別裝置和App了。
例如 微信A使用者傳送訊息:{“to”:”1234567”, “msg”:”hello”} ——-》微信伺服器{“deviceToken”:”RSFWERWR23L342JOI2NLMO2H4”, “to”:”1234567”, “msg”:”hello”} —–》APNs伺服器 ——》微信B使用者

deviceToken 在客戶端傳送之前就請求???

實現步驟:

  1. 建立真機除錯證書並配置推送證書檔案:apns_development.cer和描述檔案
    1. 首先建立真機證書、AppIDs(要選擇Push Notifications), AppIDs建立完後可以看到狀態是Configurable,是黃色的圓點,此時還不能使用推送通知,還要繼續配置一下,選擇Edit–>Push Notifications—> Create Certificate(建立推送通知證書),當證書建立完成後,可以看到AppID中的狀態就變成了綠色的圓點(可用狀態)
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
      這裡寫圖片描述
  2. 請求蘋果伺服器獲取deviceToken
  3. 傳送deviceToken給App的伺服器
  4. 監聽使用者點選遠端推送通知的行為

當配置App IDs時需要選擇Push Notifications,然後Edit,Create Certificate(建立證書),然後闖將證書選擇Apple Push Notification servide SSL(Sandbox)

獲取deviceToken:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSDictionary *dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

    // 程式完全退出,點選通知[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]有值,
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
    textView.text = dictionary.description;
    [self.window.rootViewController.view addSubview:textView];
    if (dictionary != nil) {
        // 處理程式完全退出時,點選推送通知
    }


    if ([[[UIDevice currentDevice] systemVersion] floatValue] > 8.0) {
        UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];

        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        UIRemoteNotificationType types = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound;
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
    }


    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken {
    // <37d1dd32 c19a22a0 066cf575 013f968b a89f80b8 2780dd06 be8af24c 521d0c4d>
    NSLog(@"deviceToken: %@", deviceToken);
}

// 我使用的iOS10.0.3,鎖屏和從後臺進入前臺會呼叫該方法,在前臺呼叫下面那個方法,程式完全退出時這兩個方法都不呼叫,可以通過判斷launchOptions 中的鍵是否有值來判斷,這裡使用的是iOS10.0.3,好像不同系統這幾種狀態呼叫那個方法不一樣???
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
    NSLog(@"從後臺進入前臺、鎖屏介面 這兩種情況會呼叫該方法");
}

// 前臺
// 該方法需要設定Background Modes --> Remote Notifications
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {

    NSLog(@"didReceiveRemoteNotification");
    [_window.rootViewController.view addSubview:[[UISwitch alloc] init]];
    completionHandler(UIBackgroundFetchResultNewData);
}

@end

第三方PushMeBaby: 用於模擬自己的APP伺服器,伺服器傳送格式:
{
“apns”: {
“alert”: “This is some fancy message.”,
“badge”: 1,
“content-available”: “xxx” // 可選,不是必須的
}
}

這裡寫圖片描述
這裡寫圖片描述

上面那個代理方法都是使用者點選通知以後才會呼叫,如果想一收到訊息(使用者還沒點選通知)就會呼叫該方法,需要有3個條件
1. 設定Background Modes –> Remote Notifications
2. 在代理方法中呼叫程式碼塊 completionHandler(UIBackgroundFetchResultNewData);
3. App伺服器傳送資料時要增加一個”content-available”欄位,值隨意寫
滿足以上三個條件,當接收到通知時立即會呼叫代理方法

極光推送JPush

上面測試是使用第三放PushMeBaby來模擬自己App對應的伺服器,PushMeBaby用於將訊息傳送給蘋果哦伺服器,實際開發中可定不能使用這種方式,要麼自己搭建自己的推送伺服器和蘋果伺服器進行交換,要麼使用其他第三方平臺服務,第三方實現的更加方便、更加完善

遠端推送:是指伺服器向客戶端傳送訊息。
即時通訊:是使用者向用戶傳送訊息。

整合步驟