
前言
我們?cè)趯?shí)現(xiàn)推送功能的時(shí)候,更需要了解下推送的原理機(jī)制,這樣我們?cè)诎l(fā)現(xiàn)問題時(shí)候才好定位到問題的解決辦法。
推送流程和原理

Provider就是我們自己程序的后臺(tái)服務(wù)器(或者是第三方的推送服務(wù)器),APNS是Apple Push Notification Service的縮寫,也就是蘋果的推送服務(wù)器。
上圖可以分為三個(gè)階段:
- 第一階段:應(yīng)用程序的服務(wù)器端把要發(fā)送的消息、目的iPhone的標(biāo)識(shí)打包,發(fā)給APNS。
- 第二階段:APNS在自身的已注冊(cè)Push服務(wù)的iPhone列表中,查找有相應(yīng)標(biāo)識(shí)的iPhone,并把消息發(fā)送到iPhone。
- 第三階段:iPhone把發(fā)來的消息傳遞給相應(yīng)的應(yīng)用程序,并且按照設(shè)定彈出Push通知。
APNS推送通知的詳細(xì)工作流程下面這張圖是說明APNS推送通知的詳細(xì)工作流程:

根據(jù)圖片我們可以概括一下:
- 應(yīng)用程序注冊(cè)APNS消息推送。
- iOS從APNS Server獲取devicetoken,應(yīng)用程序接收device token。
- 應(yīng)用程序?qū)evice token發(fā)送給程序的PUSH服務(wù)端程序。
- 服務(wù)端程序向APNS服務(wù)發(fā)送消息。
- APNS服務(wù)將消息發(fā)送給iPhone應(yīng)用程序。
有幾點(diǎn)值得注意
首先要有一臺(tái)蘋果的設(shè)備,模擬器是不支持推送的,
APNS
如果需要給應(yīng)用集成推送功能,就一定要用到蘋果的推送服務(wù)。Apple推送通知服務(wù)(Apple Push Notification service =APNs),例如友盟,極光之類的推送服務(wù)都是向APNs推送消息,APNs再將消息推送給設(shè)備的。推送消息傳輸路徑:
Provider-APNs-Client App 我們的設(shè)備聯(lián)網(wǎng)時(shí)(無論是蜂窩聯(lián)網(wǎng)還是Wi-Fi聯(lián)網(wǎng))都會(huì)與蘋果的APNs服務(wù)器建立一個(gè)長連接(persistent IP connection),當(dāng)Provider推送一條通知的時(shí)候,這條通知并不是直接推送給了我們的設(shè)備,而是先推送到蘋果的APNs服務(wù)器上面,而蘋果的APNs服務(wù)器再通過與設(shè)備建立的長連接進(jìn)而把通知推送到我們的設(shè)備上(參考圖1-1,圖1-2)。而當(dāng)設(shè)備處于非聯(lián)網(wǎng)狀態(tài)的時(shí)候,APNs服務(wù)器會(huì)保留Provider所推送的最后一條通知,當(dāng)設(shè)備轉(zhuǎn)換為連網(wǎng)狀態(tài)時(shí),APNs則把其保留的最后一條通知推送給我們的設(shè)備;如果設(shè)備長時(shí)間處于非聯(lián)網(wǎng)狀態(tài)下,那么APNs服務(wù)器為其保存的最后一條通知也會(huì)丟失。Remote Notification必須要求設(shè)備連網(wǎng)狀態(tài)下才能收到,并且太頻繁的接收遠(yuǎn)程推送通知對(duì)設(shè)備的電池壽命是有一定的影響的。deviceToken的生成
當(dāng)一個(gè)App注冊(cè)接收遠(yuǎn)程通知時(shí),系統(tǒng)會(huì)發(fā)送請(qǐng)求到APNs服務(wù)器,APNs服務(wù)器收到此請(qǐng)求會(huì)根據(jù)請(qǐng)求所帶的key值生成一個(gè)獨(dú)一無二的value值也就是所謂的deviceToken,而后APNs服務(wù)器會(huì)把此deviceToken包裝成一個(gè)NSData對(duì)象發(fā)送到對(duì)應(yīng)請(qǐng)求的App上。然后App把此deviceToken發(fā)送給我們自己的服務(wù)器,就是所謂的Provider。Provider收到deviceToken以后進(jìn)行儲(chǔ)存等相關(guān)處理,以后Provider給我們的設(shè)備推送通知的時(shí)候,必須包含此deviceToken。
-
deviceToken到底是什么?有什么用?為什么是獨(dú)一無二的?
是什么:
deviceToken其實(shí)就是根據(jù)注冊(cè)遠(yuǎn)程通知的時(shí)候向APNs服務(wù)器發(fā)送的Token key,Token key中包含了設(shè)備的UDID和App的Bundle Identifier,然后蘋果APNs服務(wù)器根據(jù)此Token key編碼生成一個(gè)deviceToken。deviceToken可以簡單理解為就是包含了設(shè)備信息和應(yīng)用信息的一串編碼。有什么用:
上面提到Provider推送消息的時(shí)候必須帶有此deviceToken,然后此消息就根據(jù)deviceToken(UDID + App's Bundle Identifier)找到對(duì)應(yīng)的設(shè)備以及該設(shè)備上對(duì)應(yīng)的應(yīng)用,從而把此推送消息推送給此應(yīng)用。唯一性:
蘋果APNs的編碼技術(shù)和deviceToken的獨(dú)特作用保證了他的唯一性。唯一性并不是說一臺(tái)設(shè)備上的一個(gè)應(yīng)用程序永遠(yuǎn)只有一個(gè)deviceToken,當(dāng)用戶升級(jí)系統(tǒng)的時(shí)候deviceToken是會(huì)變化的。
后臺(tái)推送也是很必須的,不是所謂的多做活動(dòng),因?yàn)橛行┩扑褪菞l件觸發(fā)的,無法做到人為推送(比如大量用戶中,接單后通知發(fā)單的人)。
推送通知本身是 iOS 系統(tǒng)的行為,所以在 App 沒有運(yùn)行(沒有在前臺(tái)也沒有在后臺(tái))的時(shí)候:仍然能夠推送及接收(通知中心通知、頂部橫幅、刷新 App 右上角的小圓點(diǎn)即 badge [以下簡稱角標(biāo)] 等都會(huì)由系統(tǒng)來控制和展示)。但是收到推送時(shí),是無法在 App 的代碼中獲取到通知內(nèi)容的。因?yàn)樯澈袡C(jī)制,此時(shí) App 的任何代碼都不可能被執(zhí)行。
開發(fā)中實(shí)現(xiàn)推送的步驟
在代碼中注冊(cè)推送服務(wù);
在第一次觸發(fā)這段代碼的時(shí)候,會(huì)有一個(gè)系統(tǒng)彈窗,詢問你是否允許該 App 要給你推送信息。當(dāng)你選擇允許時(shí),系統(tǒng)會(huì)打包 App+手機(jī)唯一標(biāo)識(shí)+證書 信息發(fā)送至 APNs 服務(wù)器注冊(cè)推送服務(wù),APNs 系統(tǒng)會(huì)對(duì)該手機(jī)安裝的該 App 是否有推送權(quán)限進(jìn)行驗(yàn)證,所以必須要加入了 Apple Deveice 的手機(jī),使用對(duì)應(yīng) App 的推送證書才能夠成功的注冊(cè)。
-
如果注冊(cè)成功,則可以在 AppDelegate.m 的如下方法中獲取到 deviceToken,它是對(duì) 該手機(jī)+該App 組合的一個(gè)唯一標(biāo)識(shí),當(dāng)使用遠(yuǎn)程推送時(shí),只需將推送消息發(fā)給指定的 deviceToken 即可使推送信息傳達(dá)給指定手機(jī)的指定 App 上。因此如果你使用第三方,就需要在這個(gè)方法里將 deviceToken 傳給第三方。(在 iOS 9 為了更好的保護(hù)用戶隱私,會(huì)出現(xiàn)多次重復(fù)刪除/安裝 App 導(dǎo)致 deviceToken 不斷變化的情況。有時(shí)會(huì)出現(xiàn)一條推送手機(jī)會(huì)收到 2 次的問題,屬于 iOS 9 系統(tǒng)問題)。
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [JPUSHService registerDeviceToken:deviceToken];//將 deviceToken 傳給極光推送 } 綜上,注冊(cè)及接收推送 必須 使用真機(jī),必須連網(wǎng)。
推送從 服務(wù)端 --> App 代碼 的過程
- 使用你們公司或第三方的服務(wù)端向 APNs 發(fā)送推送請(qǐng)求(包含 推送內(nèi)容+App描述+手機(jī)描述 )
- APNs 接收并驗(yàn)證推送請(qǐng)求
- APNs 利用網(wǎng)絡(luò)搜索并定位指定設(shè)備,下發(fā)推送
- 手機(jī)收到推送,系統(tǒng)根據(jù) App 狀態(tài)進(jìn)行處理
前臺(tái)收到:
后臺(tái)收到:
退出收到:
推送分幾種分類
-
普通式推送
就是我們?cè)谑謾C(jī)上平時(shí)見到的推送
包含聲音、彈窗、角標(biāo)、自定義字段App 處于前臺(tái):不會(huì)彈窗,可通過 didReceiveRemoteNotification 獲取推送內(nèi)容([前臺(tái)彈窗的方法看這里](https://github.com/pikacode/EBForeNotification)) 處于后臺(tái):會(huì)彈窗 ,無法獲取推送內(nèi)容 處于退出: 會(huì)彈窗,無法獲取推送內(nèi)容,點(diǎn)擊圖標(biāo)啟動(dòng),無法獲取推送內(nèi)容 點(diǎn)擊推送彈窗啟動(dòng),在 didFinishLaunchingWithOptions獲取推送內(nèi)容 推送內(nèi)容類似如下: { "_j_msgid" = 200806057;//第三方附帶的 id,用于在后臺(tái)查詢送達(dá)情況 aps = { alert = "顯示內(nèi)容"; badge = 1;//App 角標(biāo),可推送 n、+n、-n 來實(shí)現(xiàn)角標(biāo)的固定、增加、減少 sound = default;//推送聲音,默認(rèn)系統(tǒng)三全音,如需使用自己的聲音,需要將聲音文件拖拽&拷貝至 Xcode 工程目錄任意位置,并在推送時(shí)指定其文件名 }; key1 = value1;//自定義字段,可設(shè)置多組,用于處理內(nèi)部邏輯 key2 = value2; } -
后臺(tái)式推送
各種顯示效果跟普通推送完全一樣。
必須攜帶alert、badge、sound中至少 1 個(gè)字段。
僅 iOS 7 以后支持。
必須在 Xcode 工程中 TARGETS - Capabilities - Background Modes - Remote notifications 開啟該功能.App: 處于前臺(tái):可通過didReceiveRemoteNotification(iOS 7 before)didReceiveRemoteNotification:fetchCompletionHandler:(iOS 7 after) 獲取通知內(nèi)容。 處于后臺(tái):可通過didReceiveRemoteNotification:fetchCompletionHandler:獲取通知內(nèi)容 // 獲取情況中與普通推送的唯一不同點(diǎn),此時(shí) iOS 系統(tǒng)允許開發(fā)者在 App 處于后臺(tái)的情況下,執(zhí)行一些代碼,大概提供幾分鐘的時(shí)間,可以用來偷偷的刷新 UI、切換頁面、下載更新包等等操作。 處于退出:無法獲取通知內(nèi)容。 點(diǎn)擊圖標(biāo)啟動(dòng),無法獲取通知內(nèi)容。 點(diǎn)擊推送橫幅啟動(dòng),在didFinishLaunchingWithOptions獲取通知內(nèi)容。 通知內(nèi)容類似如下: { "_j_msgid" = 2090737306; aps = { alert = "顯示內(nèi)容"; badge = 1; }; key1 = value1; } -
靜默式推送
沒有任何展示效果。
必須攜帶 "content-available" = 1;,因此靜默必然是后臺(tái)的。
必須不攜帶 alert、badge、sound。
可攜帶自定義字段。App : 處于前臺(tái):可通過didReceiveRemoteNotification(iOS 7 before)didReceiveRemoteNotification:fetchCompletionHandler:(iOS 7 after) 獲取通知內(nèi)容。 處于后臺(tái):可通過 didReceiveRemoteNotification:fetchCompletionHandler: 獲取通知內(nèi)容 //獲取情況中與普通推送的唯一不同點(diǎn),此時(shí) iOS 系統(tǒng)允許開發(fā)者在 App 處于后臺(tái)的情況下,執(zhí)行一些代碼,大概提供幾分鐘的時(shí)間,可以用來偷偷的刷新 UI、切換頁面、下載更新包等等操作。 處于退出,無法獲取通知內(nèi)容。 通知內(nèi)容類似如下: { "_j_msgid" = 3938587719; aps = { alert = ""; "content-available" = 1; // 必帶字段 }; key1 = value1; }
小結(jié)
推送的大致原理我們說了一下,其他相關(guān)知識(shí)可以查看我的其他相關(guān)知識(shí)。