前言
我們在實現推送功能的時候,更需要了解下推送的原理機制,這樣我們在發現問題時候才好定位到問題的解決辦法。
推送流程和原理

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