1. 程式人生 > >本地推送.極光推送.APNs推送

本地推送.極光推送.APNs推送

1.本地推送的簡單應用
2.APNs介紹
3.使用極光推送向你的APP推送一條資訊

一.本地推送

本地通知推送也叫鬧鐘通知, 這是通知中比較簡單的一部分.
如果將app比喻成一個人, 某個controller比喻成身體的某一部分(手臂), 那麼由手臂註冊通知並且傳送通知, 最後被大腦(AppDelegate)收到這個通知並作出處理.

file-list

下面我們模擬一個情景, 在ViewController中建立一個通知(鬧鐘)並且啟用它, 然後在Appdelegate中收到這個通知, 並且彈窗AlertView顯示.

ViewController.h

1
2
// 本地註冊通知的方法
+ (void)registerLocalNotification:
(NSInteger)alertTime;

ViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
+ (void)registerLocalNotification:(NSInteger)alertTime
{
    // 例項化一個本地通知物件.
    UILocalNotification * notification = [[UILocalNotification alloc] init];

    // 設定什麼時間點(具體的時間)觸發本地通知. 距離現在多少秒之後.
notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:alertTime]; // 設定時區,default手機時區 notification.timeZone = [NSTimeZone defaultTimeZone]; // 設定重複間隔, 按秒計算. notification.repeatInterval = kCFCalendarUnitSecond; // 彈出的通知內容, 下拉選單的通知. notification.alertBody = @"快遲到了!"; // 氣泡個數
notification.applicationIconBadgeNumber = 1; // 通知被觸發時播放聲音嗎? notification.soundName = UILocalNotificationDefaultSoundName; // 通知的引數 NSDictionary * userDict = [NSDictionary dictionaryWithObject:@"起床洗漱了" forKey:@"hurry"]; notification.userInfo = userDict; // 最後一步, 將這個通知啟用. [[UIApplication sharedApplication] scheduleLocalNotification:notification]; }

事實上, 這麼寫已經將鬧鐘激活了, 他確實會在alerttime秒之後向傳送一個通知, 但是, 我們沒有在Appdelegate中配置接受這個通知並作出響應. 那麼這個通知實際上被我們丟棄了.

那麼如何在Appdelegate中處理這個通知呢?, 有一個代理方法application:didReceiveLocalNotification是處理這個通知的.

AppDelegate.m

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma mark本地通知代理
// 做過iOS 開發的人對這個函式都會很熟悉,這是在程式結束啟動,並即將執行時呼叫的,通常一些初始化的工作可以在這個函式中處理。
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification*)notification{
    //在此時設定解析notification,並展示提示檢視
    NSString * message = [notification.userInfo valueForKey:@"hurry"];
    UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"aaaa" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles: nil];
    [alert show];

    // 既然已經查看了這個通知, 那麼氣泡就要減少一個
    NSInteger badge = [[UIApplication  sharedApplication] applicationIconBadgeNumber];
    badge --;
    [UIApplication sharedApplication].applicationIconBadgeNumber = badge;
}

以上程式碼並不完整.
我們並沒有為app申請本體通知的許可權:

AppDelegate.m的didFinishLaunchingWithOptions方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    // ios8後,需要新增這個註冊,才能得到授權
    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIUserNotificationType type =  UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    }else{ // ios8之前的申請許可權方法.
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound];
    }
    return YES;
}

這樣基本就完成了, 但是有一種情況, 比如鬧鐘響了, 我們點選了起床, 此時鬧鈴不應該再響了, 我們應該從本地通知中取消這個通知(根據key).

AppDelegate.m的application:didReceiveLocalNotification方法中, 我們在氣泡減一操作之後, 呼叫這個頁面的取消通知方法.

1
2
// 取消通知
[ViewController cancelLocalNotificationWithKey:@"hurry"];

ViewController.h

1
2
// 根據傳入的key, 移除本地通知
+ (void)cancelLocalNotificationWithKey:(NSString *)key;

VidwController.m

1
2
3
4
5
6
7
8
9
10
11
+ (void)cancelLocalNotificationWithKey:(NSString *)key
{
    // 從當前的app中找出所有的本地通知.
    NSArray * notificationArrray = [UIApplication sharedApplication].scheduledLocalNotifications;
    // 如果按照key尋找userInfo, 不為空則說明找到了.
    for (UILocalNotification * not in notificationArrray) {
        if ([not.userInfo valueForKey:key]) {
            [[UIApplication sharedApplication] cancelAllLocalNotifications];
        }
    }
}

以上就是我們比較完整的需求.

二.蘋果自家推送–APNs服務

下面來講一下遠端推送, 上面的推送不聯網也能執行, 但是遠端推送則不行.

等一下, 這個遠端推送是什麼意思? 為什麼會有遠端推送?

蘋果的推送功能是ios3.0之後推出的, 也就是說, 一開始的蘋果作業系統是沒有這個功能. 那麼為什麼後來要加上這個推送呢? 沒有推送行不行呢?

這個還真不行.

原因很簡單, 蘋果ios作業系統不支援真正意義上的多工.
假設一種應用場景, 如果不支援多工的話, 你正在聊qq, 忽然想去微信搖一搖, 切換到微信後, qq程式進入後臺, 但是再也收不到訊息了.

早期(2007年)的iphone就是這個樣子. 除了幾個系統工具, 如打電話, 發簡訊, 看地圖, 發郵件外, 你不能保證使用者自己的app同時執行. 等等, 說錯了! 媽蛋那個時候iphone還沒有app store呢, 你想裝app都不行.

於是乎, 蘋果推出了自己家的推送服務, 由蘋果伺服器手機推送一條通知, 手機根據推送過來的訊息, 判斷屬於發給誰(發給哪個app), 然後向該app傳送通知, 呼叫相應的代理方法, 最終的結果是, 你桌面上的app圖示冒起了氣泡, 桌面通知欄彈出一個條訊息.

file-list

這就是遠端推送, 看似不經意的一個動作, 包含了一整套的設計方案–APNs, 凝結了無數人的心血.

APNs(英文全稱:Apple Push Notification Service),中文翻譯為:蘋果推送通知服務。顧名思義, 該服務由蘋果提供並且維護.

在這裡要特別的注意, 如果伺服器使用推送的方式與APP發生互動, 那麼你必須走APNs, 換而言之, 三方服務不能直接向你的手機推送訊息, 必須通過APNs.

那麼我們該如何理解APNs服務呢?

我們通過三張圖來簡單說明一下APNs的推送過程, 此圖來自APNs百度百科.

  • 手機與伺服器建立連線:
    file-list

    1.手機向APNs伺服器發出初連線請求. 手機與伺服器開始握手.
    2.伺服器向裝置返回伺服器證書(Server Cerficate). 此證書的目的是為了表明伺服器的身份.
    3.手機驗證剛剛收到的證書合法性(Validate Server Cerficate), 如果驗證成功, 則表明手機連線到了正確的伺服器.(Validate: 證實,驗證;確認;使生效, 它還有隱藏含義, 使證書生效).
    4.下面輪到手機向伺服器傳送證書(Device Cerficate), 此證書表明了手機的身份–有沒有越獄? 是不是蘋果機器?
    5.伺服器驗證手機是否合法.
    6.身份驗證完畢.

  • 推送過程:

    1.你開發的APP, 從當前的手機系統iOS中讀取手機的推送證書, 之後, iOS作業系統向APNs Server申請Device token.
    2.APNs Server返回Device token到你的手機, 再由iOS通知給你的APP.
    3.你的APP將獲得的Device token, 傳送到你的伺服器, 可以是極光伺服器, 可以是你們公司自己的伺服器, 不管怎樣, 你的伺服器中要維護一張Device token的表. 這個表中, 記錄了所有使用你APP的Device token.
    4.當你需要向某個使用者傳送推送時, 根據3維護的表, 找到該使用者的Device token, 然後將Device token和需要推送的訊息, 一起傳送給 APNs Server
    5.最終, APNs將這條推送訊息推送到使用者APP中, 完成推送過程.

注意以上的兩個過程建立連線推送過程, 建立連線是為了驗證通訊雙方的身份, 我們關注的點應該在推送.

  • 你的伺服器如何向用戶群推送

    此過程是上圖中的4,5步驟.不做贅述.

下面我們通過使用極光推送來具體的演示一下過程, 並且分析它.

三.極光推送

極光推送主要功能

  • 為 JPush Server 上報 Device Token,免除開發者管理 Device Token 的麻煩
  • 應用執行時,應用內 JPush 長連線可以持續地收到推送訊息

極光推送的整合過程以及使用方法在官方文件中有著極為詳細的說明, 這裡引用一下, 不做贅述.

下面討論一些莫名其妙的東西.

* 一.didFinishLaunching

前面提到, 這個方法會在程式啟動完成之後執行, 通常一些程式的初始化動作可以在這裡執行.
所以, 推送的初始化也要在這裡完成, 推送的註冊(申請許可權)根據iOS的版本不同略有不同, iOS8之前只需要一句話, 之後的版本則多幾行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Required, 註冊通知
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
        //可以新增自定義categories
        [APService registerForRemoteNotificationTypes:( UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories:nil];
    } else {
        //categories 必須為nil
        [APService registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert) categories:nil];
    }

    // Required
    [APService setupWithOption:launchOptions];

    return YES;
}

我們看最後一句:[APService setupWithOption:launchOptions];
launchOptions(下段文字來自百度知道:怎樣判斷app是通過推送訊息啟動的)
當應用程式啟動時執行,應用程式啟動入口。只在應用程式啟動時執行一次。application引數用來獲取應用程式的狀態、變數. 字典引數:(NSDictionary *)launchOptions,該引數儲存程式啟動的原因。

  • 若使用者直接啟動,lauchOptions內無資料;
  • 若由其他應用程式通過openURL:啟動,則UIApplicationLaunchOptionsURLKey對應的物件為啟動URL(NSURL),UIApplicationLaunchOptionsSourceApplicationKey對應啟動的源應用程式的bundle ID (NSString);
  • 若由本地通知啟動,則UIApplicationLaunchOptionsLocalNotificationKey對應的是為啟動應用程式的的本地通知物件(UILocalNotification);
  • 若由遠端通知啟動,則UIApplicationLaunchOptionsRemoteNotificationKey對應的是啟動應用程式的的遠端通知資訊userInfo(NSDictionary);
  • 其他key還有UIApplicationLaunchOptionsAnnotationKey, UIApplicationLaunchOptionsLocationKey,
    UIApplicationLaunchOptionsNewsstandDownloadsKey.

如果要在啟動時,做出一些區分,那就需要在下面的程式碼做處理。
所以, [APService setupWithOption:launchOptions];方法, 會根據啟動型別, 對極光推送做一些配置.

* 二.獲得token
1
2
3
- (void)application:(UIApplication *)applicationdidRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken
// &
- (void)application:(UIApplication *)applicationdidFailToRegisterForRemoteNotificationsWithError:(NSError *)error

這兩個方法是將裝置的token傳送到蘋果的伺服器的回撥方法,在成功之後, 我們應該呼叫:[APService registerDeviceToken:deviceToken];方法, 將這個token傳送到極光伺服器(這裡一定要注意, 我們選擇由極光替我們維護這張token表, 如果你的公司有自己的推送伺服器, 應該將此token傳送到你公司的伺服器, 自行維護這張表.)

* 三.接收處理方法.

如果程式在執行期間或者後臺狀態, 則如果推送訊息會在下面的兩個方法中執行, 注意, 兩個方法作用一樣, 你應該從中二選一, 蘋果推薦你選擇後者, 因為他提供了一個回撥的控制代碼. 這個block的特別之處在於, 回撥方法不是嚴格等待處理結束, 開始處理30s後, 此控制代碼會被調起.

1
2
3
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
// &
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler

userInfo結構

1
2
3
4
5
6
7
8
9
{
    "aps" : {
        "alert" : "You got your emails.",
        "badge" : 9,
        "sound" : "bingbong.aiff"
},
    "acme1" : "bar",
    "acme2" : 42
}