本文旨在對 iOS 推送進行一個完整的剖析,如果你之前對推送一無所知,那麼在你認真地閱讀了全文後必将變成一個推送老手,你将會對其中的各種細節和原理有充分的了解。以下是 pikacode 使用 iOS 推送的一些經驗,歡迎互相交流,指出錯漏之處。
推送服務可以說是所有 App 的标配,不論是哪種類型的 App,推送都從很大程度上決定了 App 的 打開率、使用率、存活率 。是以,熟知并掌握推送原理及方法,對每一個開發者來說都是必備技能,對每一個依賴 App 的公司來說都至關重要。
從 iOS 10 新增的
UserNotifications Framework
可以發現,Apple 整合了原有散亂的 API,并且增加了許多強大的功能。以 Apple 官方的角度來看,也必然是相當重視推送服務對 App 的影響、以及對 Apple iOS 生态圈長遠發展的影響。
準備篇
Tip 1:推送通知(Push Notification)必須購買 Apple 開發者賬号,并使用特定的推送證書
- 使用免費帳号不能推送。
- 那如果我們使用的是第三方推送服務(以下簡稱第三方)呢?比如「極光推送」。也必須購買開發者帳号。因為所有的第三方都會将推送請求發至 APNs(Apple Push Notification service 蘋果推送通知服務),所有推送均是由 APNs 下發。
- 如何注冊及正确的配置證書,參考這裡 iOS 證書設定指南。
原理篇
Tip 2:推送通知本身是 iOS 系統的行為,是以在 App 沒有運作(沒有在前台也沒有在背景)的時候:
- 仍然能夠推送及接收(通知中心通知、頂部橫幅、重新整理 App 右上角的小圓點即
[以下簡稱角标] 等都會由系統來控制和展示)。badge
- 收到推送時,是無法在 App 的代碼中擷取到通知内容的。因為沙盒機制,此時 App 的任何代碼都不可能被執行。
Tip 3:手機向 APNs 注冊推送服務
- 在代碼中注冊推送服務:
#ifdef __IPHONE_8_0 if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) { UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge| UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; } else { UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound; [[UIApplication sharedApplication] registerForRemoteNotificationTypes:myTypes]; } #else UIRemoteNotificationType myTypes = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound; [[UIApplication sharedApplication] registerForRemoteNotificationTypes:myTypes]; #endif
- 在第一次觸發這段代碼的時候,會有一個系統彈窗,詢問你是否允許該 App 要給你推送資訊。當你選擇允許時,系統會打包 App+手機唯一辨別+證書 資訊發送至 APNs 伺服器注冊推送服務,APNs 系統會對該手機安裝的該 App 是否有推送權限進行驗證,是以必須要加入了 Apple Deveice 的手機,使用對應 App 的推送證書才能夠成功的注冊。
- 如果注冊成功,則可以在
的如下方法中擷取到AppDelegate.m
,它是對 該手機+該App 組合的一個唯一辨別,當使用遠端推送時,隻需将推送消息發給指定的deviceToken
即可使推送資訊傳達給指定手機的指定 App 上。是以如果你使用第三方,就需要在這個方法裡将deviceToken
傳給第三方。(在 iOS 9 為了更好的保護使用者隐私,會出現多次重複删除/安裝 App 導緻deviceToken
不斷變化的情況。有時會出現一條推送手機會收到 2 次的問題,屬于 iOS 9 系統問題)。deviceToken
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [JPUSHService registerDeviceToken:deviceToken];//将 deviceToken 傳給極光推送 }
- 如果以上步驟均成功,此時你能夠取到第三方提供的裝置注冊 id。能否取到該 id 值,可以作為判斷裝置是否能夠成功推送的标準(見 Tip 6 - Registration ID)。因為當你取到該值時必然:
- 推送證書配置正确(你擁有了推送權限)。
- 裝置成功在 APNs 注冊并傳回了
(APNs 能識别你的裝置了)。deviceToken
- 傳回 的
傳給第三方,成功在第三方生成了唯一辨別注冊 id(第三方能将你的裝置資訊傳給 APNs 了)。deviceToken
- 綜上,注冊及接收推送必須使用真機,必須連網。
Tip 4:推送通知從 服務端 --> App 代碼 的過程
- 使用你們公司或第三方的服務端向 APNs 發送推送請求(請參考蘋果 APNs 相關資料,或者第三方推送提供了更簡單的 REST API)。
- APNs 接收并驗證推送請求。
- APNs 找到裝置下發推送。
- 手機收到推送通知,系統根據 App 狀态進行處理:
- 前台收到:
- 系統會将通知内容傳到
didReceiveRemoteNotification
- 系統會将通知内容傳到
- 背景收到:
- 如果開啟了
,系統将推送傳到Remote Notification
(見 Tip 5 - 背景推送),否則此時代碼中收不到推送。didReceiveRemoteNotification:fetchCompletionHandler:
- 展示橫幅、通知中心、聲音、角标。
- 如果開啟了
- 退出收到:
- 如果點選推送橫幅/通知中心而啟動 App,系統将通知傳到
。didFinishLaunchingWithOptions
- 展示橫幅、通知中心、聲音、角标。
- 如果點選推送橫幅/通知中心而啟動 App,系統将通知傳到
- 前台收到:
推送通知内容篇
Tip 5:推送通知分為 本地/遠端 2 種類型:
- 本地通知,可指定推送時間,在該時間準時彈出推送通知。
- 遠端推送通知,分為 普通推送/背景推送/靜默推送 3 種類型。存在延遲問題(由于 Tip 1 第 2 點,APNs 的不穩定及高峰時段的巨量請求所緻)。
- 普通推送
- 就是我們在手機上平時見到的推送通知。
- 包含聲音、橫幅、角标、自定義字段。
- App :
- 處于前台,不會展示橫幅,可通過
(iOS 7 before)didReceiveRemoteNotification
(iOS 7 after)擷取通知内容(前台展示橫幅的方法看這裡)。didReceiveRemoteNotification:fetchCompletionHandler:
- 處于背景,會展示橫幅,無法擷取通知内容。
- 處于退出,會展示橫幅,無法擷取通知内容。
- 點選圖示啟動,無法擷取通知内容。
- 點選通知橫幅啟動,在
擷取通知内容。didFinishLaunchingWithOptions
- 處于前台,不會展示橫幅,可通過
- 通知内容類似如下:
{ "_j_msgid" = 200806057; // 第三方附帶的 id,用于統計點選 aps = { alert = "顯示内容"; badge = 1; // App 角标,可推送 n、+n、-n 來實作角标的固定、增加、減少 sound = default; // 推送聲音,預設系統三全音,如需使用自己的聲音,需要将聲音檔案拖拽&拷貝至 Xcode 工程目錄任意位置,并在推送時指定其檔案名 }; key1 = value1; // 自定義字段,可設定多組,用于處理内部邏輯 key2 = value2; }
- 背景推送
- 各種顯示效果跟普通推送完全一樣。
- 必須攜帶
"content-available" = 1;
- 必須攜帶
、alert
、badge
中sound
。至少 1 個字段
- 僅 iOS 7 以後支援。
- 必須在 Xcode 工程中 TARGETS - Capabilities - Background Modes - Remote notifications 開啟該功能,具體可參照 iOS 7 Background Remote Notification。
- App:
- 處于前台,可通過
(iOS 7 before)didReceiveRemoteNotification
(iOS 7 after) 擷取通知内容。didReceiveRemoteNotification:fetchCompletionHandler:
- 處于背景,可通過
擷取通知内容 // 擷取情況中與普通推送的唯一不同點,此時 iOS 系統允許開發者在 App 處于背景的情況下,執行一些代碼,大概提供幾分鐘的時間,可以用來偷偷的重新整理 UI、切換頁面、下載下傳更新包等等操作。didReceiveRemoteNotification:fetchCompletionHandler:
- 處于退出,無法擷取通知内容。
- 點選圖示啟動,無法擷取通知内容。
- 點選推送橫幅啟動,在
擷取通知内容。didFinishLaunchingWithOptions
- 處于前台,可通過
- 通知内容類似如下:
{ "_j_msgid" = 2090737306; aps = { alert = "顯示内容"; badge = 1; "content-available" = 1; // 必帶字段 sound = default; }; key1 = value1; }
- 靜默推送
- 沒有任何展示效果。
- 必須攜帶
,是以靜默必然是背景的。"content-available" = 1;
- 必須不攜帶
、alert
、badge
。sound
- 可攜帶自定義字段。
- App :
- 處于前台,可通過
(iOS 7 before)didReceiveRemoteNotification
(iOS 7 after) 擷取通知内容。didReceiveRemoteNotification:fetchCompletionHandler:
- 處于背景,可通過
擷取通知内容 //擷取情況中與普通推送的唯一不同點,此時 iOS 系統允許開發者在 App 處于背景的情況下,執行一些代碼,大概提供幾分鐘的時間,可以用來偷偷的重新整理 UI、切換頁面、下載下傳更新包等等操作。didReceiveRemoteNotification:fetchCompletionHandler:
- 處于退出,無法擷取通知内容。
- 處于前台,可通過
- 通知内容類似如下:
{ "_j_msgid" = 3938587719; aps = { alert = ""; "content-available" = 1; // 必帶字段 }; key1 = value1; }
- 普通推送
推送目标篇
别名、标簽、Registration ID 均是第三方提供的用于更友善地指定推送目标的功能。
Tip 6:推送根據目标的不同可分為:
- 廣播
- 無差别發送給所有使用者。
- 别名 alias 推送
- 第三方提供的功能
- 一個手機的一款 App 隻能設定一個 alias(可修改)。
- 建議對每一個使用者都取不同的别名,以此來确定唯一的使用者(也可多個使用者取 1 個别名)。
- 推送時可指定多個 alias 來下發同一内容。
- 僅指定 alias 的使用者能夠收到推送。
- 标簽 tag 推送
- 第三方提供的功能。
- 可設定多個、可增加、清空。
- 用于指定多樣的屬性,如 『1000』+『daily』+『discount』 可用于表示月消費超過 1k、喜歡購買日用品、偏好折扣商品的使用者。
- 如果要删除,需要在上次設定時,将設定的 tags 儲存至
,本次剔除不需要的 tag 後,再重新設定。NSUserDefaults
- 推送時可指定多個 tag 來下發同一内容。
- 手機如果設定了推送指定的多個 tag 中任一個tag,都能夠收到推送消息。如指定 『1000』+『globe』+『original』 (千元級消費者、全球購、原價),那麼設定了 『100』+『globe』+『discount』(百元級消費者、全球購、折扣價)的使用者可以收到該推送消息。
- Registration ID 推送
- 第三方提供的功能。
- 在 Tip 3 的第 3 步時将
提供給第三方之後,其伺服器會自動生成的指向該手機的唯一 id。deviceToken
- 可在推送時指定多個 id 來下發消息。
- 可用于對核心使用者、旗艦使用者的精準推送。
應用内消息篇
Tip 7:應用内消息(以下簡稱消息 )和推送通知的差別,消息:
- 不需要 Apple 推送證書。
- 由第三方的伺服器下發,而不是 APNs。
- 相比通知,更快速,幾乎沒有延遲,可用于 IM 消息的即時送達。
- 能夠長時間保留離線消息,可擷取所有曆史消息内容。
- 通過長連接配接技術下發消息,是以:
- 手機必須啟動并與第三方伺服器建立連接配接。
- 如果手機啟動立刻切至背景,很可能連接配接沒有建立。
- 手機必須處于前台才能收到消息。
- 手機從背景切回前台,會自動重建立立連接配接,并收到離線消息。
- 沒有任何展示(橫幅、通知中心、角标、聲音),是以可以:
- 自定義字段實作 UI 效果。
- 完全在靜默情況下處理 App 内部邏輯。
- 使用一些 App Store 稽核不會通過的功能,在稽核時關閉功能,上架後通過接收消息,開啟相關功能。
組合大招篇
Tip 8:tags 的組合技巧
- 見 Tip 5 - 标簽 tag 推送。
- 可以在服務端來統計分析使用者行為,然後将指定的 tags 發送至手機,手機接收後再為使用者打上對應的 tags。
Tip 9:通知+消息的組合技巧
- 首先來看通知和消息特性的對比:
通知 | 消息 | |
---|---|---|
送達時間 | 可能存在幾秒延遲 | 幾乎無延遲 |
擷取時機 | 處于前台或背景能擷取内容 | 僅處于前台能擷取内容 |
離線内容 | 保留『一段時間』,過期會抛棄,無法查詢曆史内容 | 始終保留,可查詢全部曆史内容 |
系統展示 | 會展示(靜默推送或App處于前台不展示) | 不展示 |
- 由于各自的特性都存在差異,是以二者結合使用是使得 App 推送性能最大化的必然選擇:
-
情景一:
QQ/微信 聊天。會同時下發一組通知+消息 ,如果使用者沒有啟動 QQ,雖有延遲但必然能夠先收到通知,在收到通知的提醒之後,使用者打開 App,此時收到了離線消息,即時更新 UI,與好友即時地發送/接收消息。(在收到通知後,斷網,然後啟動 App,你會發現此時手機裡并不會顯示剛剛通知的内容,因為它是依靠拉取消息來重新整理頁面的,而不是不夠穩定的通知)。
- 情景二:(期待您的補充...)
-
文/pikacode(簡書作者)
原文連結:http://www.jianshu.com/p/e9c313df746f
著作權歸作者所有,轉載請聯系作者獲得授權,并标注“簡書作者”。