安卓推送這件小事

文章來源  https://zhuanlan.zhihu.com/p/26053061?utm_source=gank.io&utm_medium=email

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

推送難,難於上青天。

我們在討論 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.x 由 setColor 設置底色和 small icon 配合形成 large icon
  • 7.x setColor 影響標題顏色

通知的跳轉

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

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

展望未來

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章