天天看點

了解iOS消息推送一文就夠:史上最全iOS Push技術詳解1、引言2、相關文章3、iOS的Push種類4、本地push實作5、線上、離線(遠端)push流程6、iOS 10關于push的一些新特性7、iOS Push的測試要點羅列8、有關iOS Push的常見疑問彙總9、相關工具推薦附錄:更多消息推送技術文章

本文作者:陳裕發, 騰訊系統測試工程師,由騰訊WeTest整理發表。

1、引言

開發iOS系統中的Push推送,通常有以下3種情況: 1)線上Push:

比如QQ、微信等IM界面處于前台時,聊天消息和指令都會通過IM自建的網絡長連接配接通道推送過來,這種Push在本文中暫且稱為“線上Push”;

2)本地Push:

這種就是最常見的iOS系統通知(作用相當于傳統PC端的提示視窗,在iOS10以後全部整合到UserNotifications.framework架構了),不涉及任何網絡資料,僅僅是讓APP擁有一個統一系統通知方式而已,比如:鬧鐘的定時提醒等;

3)離線/遠端Push:

這就是iOS程式員最熟悉的APNs這一套東西了,它使得APP處于背景或者被kill的情況下仍能收到網絡通知,最常見的應場景就是IM聊天工具了。

本文将對iOS Push的線上push、本地push及離線(遠端)push進行了詳細梳理,介紹相關邏輯、測試時要注意的要點以及相關工具的使用。小小的Push背後蘊藏着大大的邏輯,我們一起來學習吧!

消息推送/im開發學習交流:
- 即時通訊開發交流3群: 185926912

[推薦]

- 移動端IM開發入門文章:《

新手入門一篇就夠:從零開發移動端IM

(本文同步釋出于:

http://www.52im.net/thread-1762-1-1.html

2、相關文章

移動端實時消息推送技術淺析 iOS的推送服務APNs詳解:設計思路、技術原理及缺陷等 信鴿團隊原創:一起走過 iOS10 上消息推送(APNS)的坑 掃盲貼:淺談iOS和Android背景實時消息推送的原理和差別

3、iOS的Push種類

3.1 線上push

線上push:

當使用者線上(APP在前台)時,收到的狀态欄的消息提醒,稱為線上push。這個功能與蘋果系統無關,是我們自己的APP開發的一種功能,該push與設定中是否打開“通知”無關。

這裡以iOS Qzone為例,當APP在前台時,自己發的說說被點贊了,收到的線上push如下:

3.2 離線/遠端push

離線push:

當APP在離線(kill掉程序、切到背景、鎖屏)時,收到的消息提醒,稱為離線push。離線push是需要經過蘋果的APNs伺服器才可以推送到某台裝置的某個APP上的,這是和本地push的本質差別。push與設定中是否打開“通知”有關。

這裡最簡單的以大家常用的手機QQ為例,當APP在背景、鎖屏或者被kiil了程序時,收到了消息: 一種特殊的遠端push:靜默push

嚴格來說,靜默push屬于遠端push的一種特殊情況,靜默push用的場景不較少,這裡隻做簡要介紹。

首先我們看看離線(遠端)push與靜默push的差別:

【普通離線(遠端)push】:收到推送後(有文字有聲音),點開通知,進入APP後,才執行-- (void)application:(UIApplication didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void result))handler *)application *)userInfo (^)(UIBackgroundFetchResult

【靜默push】:收到推送(沒有文字沒有聲音),不用點開通知,不用打開APP,就能執行(void)application:(UIApplication )application)userInfo didReceiveRemoteNotification:(NSDictionary fetchCompletionHandler:(void (^)(UIBackgroundFetchResultresult))handler,使用者完全感覺不到。

是以靜默push又被我們稱做 Background Remote Notification(背景遠端推送)。靜默推送是在iOS7之後推出的一種推送方式。它與其他推送的差別在于允許應用收到通知後在背景(background)狀态下運作一段代碼,可用于從伺服器擷取内容更新。

3.3 本地push

本地push:

本地推送和遠端推送的功能是一樣的,都是要提醒使用者去做某些事情。但是和遠端推送不同的就是本地推送是不需要裝置聯網的,而遠端推送是必需要裝置聯網的,因為隻有聯網狀态下,才能和蘋果的APNs伺服器建立長連接配接,進而推送消息。本地推送是由App自己設定的,并且發送給安裝此App的這台裝置,屬于一對一的對應關系。比較典型的應用是鬧鐘類似的場景。該push與設定中是否打開“通知”有關。

最容易看到本地push的場景,可以直接在手機設定一個計時器,計時器時間到了就會彈出本地push:

由于本地push原理和作用相對于線上push和離線push都更為簡單明了,下文主要介紹線上push和離線push。

4、本地push實作

4.1 iOS10以前本地push彈出方式

試驗過iOS10以前的本地push方法在iOS10+的系統也能使用,不過可能有些參數不生效。

1)立即展示( iOS10以前)

本地push稍微簡單,有兩種方式可以調用,一種是presentLocalNotificationNow方法,立即展示本地push:

2)延遲展示( iOS10以前)

另一種是用scheduleLocalNotification方法按計劃來彈本地推送:

如果使用這種方法,需要對推送的時間進行設定,舉個例子,設為5秒後:

4.2 設定本地push内容( iOS10以前)

其中alertBody是消息内容鎖屏與不鎖屏時效果如下:

applicationIconBadgeNumber是消息數量,我們可以看到這裡設定為66:

4.3 處理本地push ( iOS10以前)

1)App沒有啟動情況下處理本地push

這種情況下,當點選通知時,會啟動App,而在App中,開發人員可以通過實作AppDelegate中的方法:- (BOOL)application:UIApplication)application didFinishLaunchingWithOptions:NSDictionary *)launchOptions,然後從lauchOptions中擷取App啟動的原因,若是因為本地通知,則可以App啟動時對App做對應的操作,比方說跳轉到某個畫面等等。

2)App運作在背景及前台

上面的2種情況的處理基本一緻, 不同點隻有當運作再背景的時候,會有彈窗提示使用者另外一個App有通知,對于本地通知單的處理都是通過AppDelegate的方法:- (void)application

UIApplication )application didReceiveLocalNotification:UILocalNotification *)notification來處理的。

4.4 iOS10以後本地push彈出方式

iOS10以後,本地通知可以由使用 UNUserNotificationCenter來管理。

建立方法:

接下來需要需建立一個包含待通知内容的 UNMutableNotificationContent 對象:

在iOS上可以通過以下幾種觸發器來觸發本地push:

1)UNCalendarNotificationTrigger 傳送本地通知的日期和時間;

2)UNTimeIntervalNotificationTrigger 傳遞本地通知之前必須過期的時間;

3)UNLocationNotificationTrigger 使用者必須達到的地理位置才能提供本地通知;

4)UNPushNotificationTrigger 表示通知是從Apple推送通知服務發送的對象。

假如以時間間隔(TimeInterval)來觸發,則設定觸發器代碼為:

推送本地push的代碼為:

5、線上、離線(遠端)push流程

5.1 線上push流程

線上push相對簡單,因為是内部實作,具體流程如上面所示。

1)判斷app是否線上:

此處可以根據APP自身的背景政策如上一次與背景互動的時間等方法來判斷APP是否線上或者離線。認為線上,會發送線上push,否則,發送離線push。

2)線上push有以下幾個特點:

不需要經過蘋果APNs;

需要自己實作長連結;

代碼在app内部實作。

5.2 離線(遠端)push流程

主要流程為:

1)伺服器端将消息先發送到蘋果的APNs;

2)由蘋果的APNs将消息推送到客戶的裝置端;

3)由iOS系統将接收到的消息傳遞給相應的App。

簡而言之離線push是蘋果系統的行為,與app狀态無關,能夠直接推送到指定手機的指定app。

在進一步了解離線push前,我們有必要先了解幾個名詞。

【離線push名詞解釋】: (1)名詞解釋之APNs

APNs:Apple Push Notification service(蘋果推送通知服務)。

APNs主要用于以下場景:當使用者主動殺掉 APP,或者 APP 進入背景超過約定時長時,APP會被kill,這樣保障了前台 APP 的流暢性,也延長了手機的使用時長,獲得了較好的使用者體驗,但是這也意味着,伺服器無法主動和使用者互動(如推送實時消息等),是以蘋果推出了 APNs,允許裝置和伺服器分别與蘋果的推送通知伺服器保持長連接配接狀态。

關于APNs的更新有以下幾點:

iOS 8以後,APNs推送的位元組是2k,iOS8以前是256位元組;

iOS 9以後APNs支援HTTP/2協定棧,優化長連接配接,具有标準的HTTP傳回和管道複用技術;

iOS 10以後,推送的位元組是4k,APNs可根據推送消息的唯一标示符查詢某條消息是否被使用者閱讀,可更新某一推送消息,而不用發重讀的多條消息。

關于APNs更全面的介紹可以看官方文檔:

點此進入

(2)名詞解釋之payload

什麼是payload?對于每一條發送給APNs的推送消息,都包含一個payload,通常是組成了一個JSON的Dictionary,這其中必不可少的是aps屬性,它對應的value也是一個Dictionary,包含一些但不限于以下内容:标題、副标題、内容、附件、category等,如

(3)名詞解釋之device token

什麼是device token?我們看一下官方的簡介:

device token: APNs uses device tokens to identify each unique app and device combination. It also uses them to authenticate the routing of remote notifications sent to a device.(device token是APNs用于區分識别每個iOS裝置和裝置上不同app的一個辨別符,還可以用于APNs通過它将推送消息路由到指定裝置上)
即:

device token裡包含了device id和bundle id的資訊,但是device id和bundle id不會确定唯一的device token。

但是,這裡有個坑,查資料得知,iOS8及之前的iOS系統,對于同一部手機,如果解除安裝後重裝APP的話,device token是不會變的,在token變了以後,老的token,就被認為是無效了,蘋果不會對這部分無效的token推送。但是,對iOS9及以後的iOS系統,對于同一部手機,解除安裝後重裝APP的device token是會發生變化的,而且老的token不會無效,還可以正常推送,這應該是蘋果的一個bug,但是蘋果也沒有修複這個問題,是以這個需要開發者自己來解決,否則容易出現一個app收到多個push的問題。

官方的說法是:

To protect user privacy, do not use device tokens to identify user devices. Device tokens change when the user updates the operating system and when a device’s data and settings are erased. As a result, apps should always request the current device token at launch time.(即此舉為了保護使用者隐私,device token會在更新系統、擦除設定重置後變化,在一定時間後會過期)
【離線push詳細流程】

知道了以上概念後我們重新來看一下離線(遠端)push的詳細流程:

1) 首先是應用程式注冊消息推送;

2) iOS跟APNS Server要deviceToken。應用程式接受deviceToken;

3) 應用程式将deviceToken發送給PUSH服務端程式;

4) 服務端程式向APNS服務發送消息;

5) APNS服務将消息發送給iPhone應用程式。

值得注意的是,當由于使用者反複解除安裝重裝程式(雖然機率很小)等原因導緻多個device Token指向同一台裝置的同一個app,又把多個device Token發給APNs時,使用者就會收到多條push。蘋果APNs是不會對多個device Token是否指向同一台裝置的同一個app做校驗的,是以需要背景來做去重等處理保證使用者不會收到多條push。

5.3 對離線(遠端)push的響應

1)iOS 7以上對離線(遠端)push時的響應

iOS 7以上關于接受離線push有兩個函數:

那麼這兩個函數有什麼差別呢?其實這兩個方法都是用來處理離線push的。

差别就是,如果app在前台是收到離線(遠端)push,那麼就會調用:

相對的,如果在背景或者殺程序情況下,點選收到的離線push,那麼就會調用,如果沒有實作:

則會調用:

若實作了前者,就隻調用前者。

2)iOS 10以上對離線(遠端)push的響應

iOS10對push的處理主要增加了兩個方法:

其中前者是對APP在前台時收到push時的處理,後者是點選push進入APP執行的函數。

用得比較多的是後者,我們可以舉個例子,點選push進入APP後如何擷取push的消息、角标、标題等内容:

6、iOS 10關于push的一些新特性

iOS10新增的UserNotifications架構,主要有了這樣幾方面的更新:

1)用UserNotifications架構替換了原先與通知相關的接口,通知文字可分為title、subtitle和body三部分,通知可攜帶附件;

2)系統在展示通知之前,可以喚起app附帶的service extension,并且允許它改動通知的内容;

3)使用者在對通知右滑檢視、下拉或者3d touch的時候,通知會展開,展開後頁面的布局可以由app附帶的content extension來決定。

6.1 push的多樣性

iOS10以前的push隻有文字,甚至沒有标題。iOS10以後的push更加多樣化,可以有主标題,副标題,甚至還有附件。

這裡以我司的騰訊新聞為例(有标題,内容,和附件):

3D touch點入詳情以後:

這裡我們驚奇的發現,除了可以攜帶圖檔這樣的附件、push還能展開詳情以外,進入詳情以後,下面還多了“打開”、“收藏”、“不感興趣”這些選項,這裡就涉及到以下iOS10的新特性。

6.2 push攜帶附件

因為payload有大小限制,是以如果remote notification想要攜帶附件,那麼payload上隻能帶上如附件下載下傳位址之類的資訊,等通知到達用戶端後由service extension下載下傳附件到本地,然後在初始化UNNotificationAttachment對象時傳入附件在本地的URL。

初始化UNNotificationAttachment對象時,可以傳入option參數。這裡的option參數可以強制指定附件的類型,可以選擇是否展示縮略圖,以及縮略圖截取自附件的哪一幀、哪一部分。

目前iOS10通知隻将幾種格式的圖檔、音頻和視訊作為附件,附件的大小也有一定限制,具體可以看官方文檔中的限制說明。

關于附件的更加詳細的說明,可以參考官方文檔:

6.3 攜帶action的通知

上面提到的“打開”、“收藏”、“不感興趣”這些選項其實就是push攜帶的action,其實從iOS8開始,通知已經可以攜帶action了。而在iOS10中,通知的action被放在了更明顯的位置,與action相關的接口也有了很大變化。

決定一個通知應該有哪些action呢?在payload中,這是由category字段決定的。如果我們希望一個通知能攜帶若幹個action,我們就需要将若幹個action和一個category綁定起來。通知到達前端後,系統會根據category的名字來決定要給這個通知展示哪些action:

怎麼得知使用者選了哪個action并做出相應操作呢?這需要給UNUserNotificationCenter指定一個delegate:

然後在delegate的類中實作:

方法:

通過response.notification.request.content.categoryIdentifier和response.actionIdentifier就可以得知使用者選擇的action了。

6.4 改變push内容

這裡主要講應用的比較多的離線(遠端)push的改變push方法。

1)改變本地push内容:

本地push,隻要request的id一樣,那麼就可以更新推送。

更新的例子:

此外,還有删除所有推送等,都在UNUserNotificationCenter.h中實作。

2)改變離線(遠端)push内容:

目前遠端push隻支援更新push内容,更新需要通過新的字段apps-collapse-id來作為唯一标示。方法是在HTTP/2 請求頭中使用相同的apns-collapse-id,這樣收到同樣的apns-collapse-id的push時,push内容便會更新。

使用場景:

比較容易了解的一個場景就是球賽比分,比如現在是1:0,如果變成1:1的話,隻需要重新整理原來的新聞,這樣使用者就不會因為同一場比賽收到多條push。

6.5 兩個extension

有兩個與push相關的extension,可能我們會好奇這兩個extension有什麼不同,為什麼需要兩個?它們分别實作什麼功能呢?

【1)notification service extension】

給app添加notification service extension後,系統會在收到通知後喚醒它,并允許它修改通知的内容,之後再展示這個通知。

service extension隻對remote notification起作用,local notification是無法喚起它的。

如果想要讓系統喚起service extension的話,payload必須符合這樣幾個條件:

1)必須增加mutable-content字段并為1,這表示允許用戶端修改這個通知:

payload(舉例)如下:

2)這個通知必須展示一個alert,如果隻是一個修改badge的通知的話,是不會喚起service extension的;

3)靜默推送是不能喚起service extension的,是以payload中不能有”content-available” : 1字段。

是以,通過這個notification service extension,你可以在接收到推送之後、展示推送之前處理一些事情,比如說更新一下推送内容,或者在背景做一些其他事情。

【2)notification content extension】

另一項notification content extension用于完全自定義推送展開後的視圖。上面騰訊新聞的展開後的視圖就是通過這個notification content extension實作的。

依然以騰訊新聞為例子:

這裡Notification Content Extension大展拳腳的地方,在這裡可以自定義繪制不同的内容,将希望展現給使用者的額外資訊可以加載這裡。

下半部分的notification action的實作就是在上面提到的“攜帶action的通知”。

7、iOS Push的測試要點羅列

另外注意一點:

測試Push的時候,區分好Appstore證書和開發證書。兩者不能互相發Push。

8、有關iOS Push的常見疑問彙總

Q:離線push,支援角标(badge)在本地角标數值上+1這樣的操作嗎? A:

不支援。如果是自己實作push服務的話,需要自己的背景将角标值badge發送個APNs伺服器,有些APP使用第三方push SDK除外。

Q:如果重複收到離線push,可能是什麼情況?

1)iOS9之後解除安裝重裝後生成新的deviceToken,背景對多個deviceToken都發送了push

2)背景對登出了的賬号也發送了push。

總而言之一般是背景的邏輯出現了問題,而不是APNs伺服器出現問題。

Q:直接解除安裝APP,還能收到離線push嗎?

不會收到。直接解除安裝APP,雖然背景不知道APP被解除安裝了,仍然會對之前的賬号發送push,但是由于手機上沒有對應APP,是以并不會收到push。

Q:為什麼有時候全新安裝APP就立馬有紅點角标?

這是因為解除安裝該APP時有紅點角标。每個 APP 的角标都是存在 iOS 手機系統裡的,開發無法修改,是以此時解除安裝前有角标,重新安裝也會有角标。但是,APP 解除安裝之後超過一天的時間再重裝,那麼角标就會被系統清空,屆時也不會有新安裝的 APP 就有角标的情況存在。

Q:自己Server通過APNs發的每一條Push,用戶端都會收到麼?

答案是否定的,Push是不可靠的,push通知是fire-and-forget,比如手機關機,那麼自然就收不到,雖然Apple會嘗試幾次。

Q:Push消息的大小是多少?

iOS8發的時間點起,無論那個iOS系統,push消息的body大小調整為2k,注意這裡是iOS8的時間點,也就是2014年秋,就目前來說push的限制應該是2k不再是256了。

9、相關工具推薦

Knuff離線push工具下載下傳連結: https://github.com/KnuffApp/Knuff/releases

使用方法也比較簡單:

比如我的payload輸入如下:

得到的應該是有“Knuff測試”文字,和角标數變為999,我們可以看下結果,與預料是一緻的:

有了這個工具也更加友善了我們的iOS push的調試。

附錄:更多消息推送技術文章

Android端消息推送總結:實作原理、心跳保活、遇到的問題等 掃盲貼:認識MQTT通信協定 一個基于MQTT通信協定的完整Android推送Demo IBM技術經理訪談:MQTT協定的制定曆程、發展現狀等 求教android消息推送:GCM、XMPP、MQTT三種方案的優劣 絕對幹貨:基于Netty實作海量接入的推送服務技術要點 移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信) 為何微信、QQ這樣的IM工具不使用GCM服務推送消息? 極光推送系統大規模高并發架構的技術實踐分享 從HTTP到MQTT:一個基于位置服務的APP資料通信實踐概述 魅族2500萬長連接配接的實時消息推送架構的技術實踐分享 專訪魅族架構師:海量長連接配接的實時消息推送系統的心得體會 深入的聊聊Android消息推送這件小事 基于WebSocket實作Hybrid移動應用的消息推送實踐(含代碼示例) 一個基于長連接配接的安全可擴充的訂閱/推送服務實作思路 實踐分享:如何建構一套高可用的移動端消息推送系統? Go語言建構千萬級線上的高并發消息推送系統實踐(來自360公司) 騰訊信鴿技術分享:百億級實時消息推送的實戰經驗 百萬線上的美拍直播彈幕系統的實時推送技術實踐之路 京東京麥商家開放平台的消息推送架構演進之路 了解iOS消息推送一文就夠:史上最全iOS Push技術詳解 >>  更多同類文章 ……

繼續閱讀