安卓推送這件小事

即刻技術團隊發表於2017-03-29

今天來講講推送這件小事,事雖小,要做好卻不容易。

推送難,難於上青天。

我們在討論 Android 手機上的推送時,大多數情況是在說整合第三方推送,因為即使是像微信這樣的大廠,也需要廠商加到啟動白名單裡才能保持線上。

iOS 手機使用 APNs(Apple Push Notification service)進行推送,而 Android 手機,也是有 GCM(Google Cloud Messaging)作為 Google 官方的推送支援的,但是在國內需要翻牆才能使用,並且需要手機安裝了 Google Service ,條件比較苛刻。

這樣一來,國產手機的推送就成了個問題,也帶了機會。

微信由於有國際版,將 GCM 作為輔助公共通道,但僅用於啟用微信自己的 Push 通道,並沒有通過 GCM 來傳遞資料,這點也是為了複用心跳的優化策略和資料處理邏輯。

GCM 最新版本叫 FCM(Firebase Cloud Messaging)

推送的實現方式

總結一下幾種推送實現方式,其中有些我們只要瞭解即可,因為屬於歷史解決方案,現在已經被廢棄掉了。

1. 輪詢

客戶端定期詢問伺服器有沒有新的訊息,這種方式最大的缺點就是效能實時性的矛盾,輪詢時間過長和過短都不好。

2. 簡訊

這種方式還沒出生就不被允許了,首先運營商不會配合,其次攔截手機簡訊本身就是一個高危許可權,大多數使用者不會買單。

3. 長連

目前最通用的方案,客戶端與服務端建立 TCP 長連,定時傳送心跳包保活,有新訊息時服務端通過該長連通道進行推送。

這裡再簡單說明一下長連和心跳包。

長連線就是建立連線之後,雙方互相傳送資料,,發完了也不主動斷開連線。

但是在某些情況下長連會斷開,問題就在斷開這件事上,而且這件事必須是由客戶端知道,因為客戶端是可以重連伺服器的,伺服器卻沒法再聯絡上客戶端。這樣才確定了心跳包必須由客戶端發給伺服器。所以心跳包的作用就是告訴服務端客戶端還活著,如果服務端掛了,客戶端能知道,所以保活說的是保持兩邊都活著。

上面說的某些情況中, NAT 超時算是一個典型的例子。

因為 IPv4 的 IP 量有限,運營商分配給手機終端的 IP 是運營商內網的 IP,手機要連線 Internet ,就需要通過運營商的閘道器做一個網路地址轉換(Network Address Translation,NAT)。簡單的說運營商的閘道器需要維護一個外網 IP、埠到內網 IP、埠的對應關係,以確保內網的手機可以跟 Internet 的伺服器通訊。大部分移動無線網路運營商都在鏈路一段時間沒有資料通訊時,會淘汰 NAT 表中的對應項,造成鏈路中斷。所以長連線心跳間隔必須要小於 NAT 超時時間(aging-time),如果超過老化時間不做心跳, TCP 長連線鏈路就會中斷,伺服器就無法發訊息給客戶端,只能等到客戶端下次心跳失敗後,重建連線才能取到訊息。

有了長連,即使在休眠模式下,有推送訊息過來,也能喚醒 Android 系統,這是由系統機制決定的,我們這裡只要知道結論就好。

想要了解更多的可以去看文末的參考資料

推送的指標

在接入推送服務時有幾個核心指標需要考量:

1. 線上率

線上率 = 線上使用者數 / 總使用者數

推送服務後臺保持線上的方法

  • Push 程式常駐後臺,需要使用者手動讓應用常駐
  • 共享連線通道的方式,比如極光或者個推,通過共享連線,當應用有推送到達時,喚起該應用

顯然,後者在體驗上更加接近 GCM 。

2. 到達率

到達率 = 實際到達數 / 目標使用者數

線上數 -> 目標使用者數 -> 成功下發數,如果後端的計算或呼叫出現問題這兩個資料就會不準確

線上數 -> 實際到達數 -> 展示數,資料收到後,是否展示要看使用者有沒有開啟該應用的允許通知的開關,可以通過如下方法判斷

notificationManagerCompat.areNotificationsEnabled();複製程式碼

3. 耗電量

耗電量受到很多方面的影響,如果收到推送比較多,開啟應用比較頻繁,耗電量自然也會上去不少,但這個使用者是可以接受的。以下幾個耗電量的因素使用者是比較反感的:

  • 應用間互相喚醒產生的耗電,因為這個耗電是別的應用的,使用者本來沒有意圖要去開啟
  • 錯誤重試造成的耗電,重試策略的優化包括重試時間的累加和重置

推送選型

上面提到,我們這裡聊的推送,是第三方推送,那有開發者要問了,為什麼不自己做推送?自己做不是不行,但需要考慮幾個問題:

  • 開發成本問題, ROI 是否可以接受
  • 如果不加入白名單,那麼一旦應用被徹底殺掉,是沒人給你吟唱復活魔法(互相喚起)的

第三方推送主要有廠商推送和非廠商推送

  • 華為、小米、魅族推送
  • 個推、極光、友盟
  • 阿里、騰訊、百度

其中,選型的幾個因素

  • 廠商推送通知是否系統通道(所有廠商支援)
  • 廠商推送透傳是否系統通道(僅魅族)
  • 非廠商推送的市場佔有率(影響共享連線互相喚起的概率)

如何在 Android 手機中檢視某個應用使用的是什麼推送?

adb shell dumpsys activity services | grep igexin複製程式碼

安卓推送這件小事

可以看到,這幾個應用都在使用個推

大眾點評、寶寶樹、餓了麼、滴滴、簡書、領英、 WPS Office 、格瓦拉。

推送接入

如何解耦

由於各個推送 sdk 介面定義不同,為了減少耦合,我們採用多 module 的形式進行接入。

  • app
  • jikepush
    • PushServiceImpl
  • push_廠商1
    • PushPlatformImpl
  • push_廠商2
    • PushPlatformImpl
  • push_非廠商
    • PushPlatformImpl
  • jikecore
    • PushService
    • PushPlatform

在 app 層通過註冊的方式新增需要的 PushPlatform ,之後通過 PushService 介面來進行呼叫,具體的啟動和切換的實現放在 PushServiceImpl 。

其實大部分推送平臺的介面標準都差不多,無非是命名上有差異,所以我們用 PushPlatform 這個介面來遮蔽這種差異。

對於推送啟動和切換的操作,放在 PushService 中。

當啟動某一個推送服務的時候,就關掉其他的推送服務,後臺始終只保持一個推送服務。

registeration id

通常我們啟動一個推送服務後,會收到一個 registeration id(以下簡稱 reg id),用這個 reg id 和我們自己的伺服器進行繫結。

這裡有個需要注意的地方,繫結的操作我們需要呼叫2次。

  • after PushService.start() 因為已經接收到 reg id 後有些推送不再觸發 receive reg id
  • after receive reg id 首次啟動推送是非同步收到 reg id 的

收到 reg id 之後,推送 sdk 會自己儲存下來以便下次使用,但有時候我們使用 sdk 的方法獲取的時候無法獲取到,究其原因是 reg id 的儲存不是我們自己做的,因此, reg id 這麼重要的東西我們自己也要存。

推送需要的系統許可權

推送 sdk 需要獲取手機的 imei 號作為合成 reg id 的必要元素,需要以下兩個許可權

  • Manifest.permission.READ_PHONE_STATE
  • Manifest.permission.WRITE_EXTERNAL_STORAGE

啟動推送服務的時候需要判斷許可權。

推送的切換策略

建議選擇手機 rom 而非手機型號作為切換條件,這樣可以解決部分使用者刷機的問題,比如 Nexus 手機刷了個 MIUI 的情況。

獲取到 rom 資訊後,有3種推送切換策略

策略一

客戶端根據 rom 資訊自動選擇使用哪個推送

  • 優點:無後端工作量,不需要切換
  • 缺點:一旦某個推送掛了一天,無法臨時切換到其他推送

策略二

客戶端上報 rom 資訊,後端選擇使用哪個推送

  • 優點:靈活切換推送
  • 缺點:切換推送重新繫結 reg id 有個時間差

策略三

使用推送 sdk 自己的整合方案,當 sdk 自己的推送服務離線時,切換到廠商推送

  • 優點:最大程度保證推送的穩定性
  • 缺點:整合方案本身的不穩定性影響了推送的穩定性

推送型別

推送型別分為通知和透傳

通知

  • 廠商到達率有優勢
  • 開發成本較高,需要適配不同廠商的介面標準

透傳

  • 可以自己解析、展示、跳轉,靈活性高
  • 開發成本較低,適配一次就夠(通常通過 json )
  • 一些廠商的透傳到達率沒有廠商優勢

整合方式

一般我們整合推送 sdk 有兩種整合方式

  • maven 整合
  • 手動整合

從維護的角度來說, maven 整合的維護成本要小於手動整合,但是我這裡還是推薦手動整合,主要有以下幾個原因。

maven 整合的方式通常 sdk 會要求使用 manifest placeholder 的方式進行 appid appkey appsecret 的注入,但是這種方式需要在 app 層的 build.gradle 去注入,比較耦合。

maven 整合還有一個缺點,準確來說這是 sdk 開發商的問題,總會打包一些我們不需要的資原始檔,其實我只需要一個 jar 包而已,並不是整個 aar 啊。

手動整合第一次寫 manifest 比較麻煩,但是後面只要替換 jar 和 so 檔案就好了,因為 manifest 一般不經常變化,除非有重大版本更新。

推送的展示

接下來就要說說通知 Notification 了,這個東西經過廠商的各種定製,適配起來也有不少麻煩。

廠商的推送樣式差異

部分廠商

  • 不支援 NotificationCompat.BigTextStyle (這都能不支援)
  • 不支援 NotificationCompat.MediaStyle (這個還可以理解)
  • 不支援 Action Button
  • 不支援 Ticker
  • 自定義樣式高度限制

原生系統 Android 版本的推送樣式差異

  • 4.x large icon 需要是方的
  • 5.x 6.xsetColor 設定底色和 small icon 配合形成 large icon
  • 7.x setColor 影響標題顏色

通知的跳轉

  • 透傳:採用 Url 方式進行跳轉
  • 通知:採用 Intent 引數的方式進行跳轉

推送這件小事就說到這裡了。

展望未來

最近,由泰爾實驗室牽頭的安卓統一推送研討會正在進行中,即刻上有一個“安卓推送服務統一進展”的提醒,我們一起關注安卓推送生態的變化吧。

用一句廣告語描述廠商和 app 的相愛相殺:你好,我也好。

參考資料

Android推送技術研究

Android微信智慧心跳方案

相關文章