融雲技術分享:融雲Android端IM產品的網路鏈路保活技術實踐
- 2019 年 10 月 5 日
- 筆記
本文來自融雲技術團隊原創分享,原文發佈於「 融雲全球互聯網通訊雲」公眾號,原題《IM 即時通訊之鏈路保活》,即時通訊網收錄時有部分改動。
1、引言
眾所周知,IM 即時通訊是一項對即時性要求非常高的技術,而保障消息即時到達的首要條件就是鏈路存活。那麼在複雜的網路環境和中國Android手機被深度訂製化的條件下,如何保障鏈路存活呢?本文詳解了融雲Android端IM產品在基於 TCP 協議實現鏈路保活方面的實踐總結。
學習交流:
– 即時通訊/推送技術開發交流5群:215477170 [推薦] – 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
(本文同步發佈於:http://www.52im.net/thread-2744-1-1.html)
2、相關文章
《為何基於TCP協議的移動端IM仍然需要心跳保活機制?》 《微信團隊原創分享:Android版微信後台保活實戰分享(進程保活篇)》 《微信團隊原創分享:Android版微信後台保活實戰分享(網路保活篇)》 《移動端IM實踐:實現Android版微信的智慧心跳機制》 《移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》 《Android P正式版即將到來:後台應用保活、消息推送的真正噩夢》 《全面盤點當前Android後台保活方案的真實運行效果(截止2019年前)》 《一文讀懂即時通訊應用中的網路心跳包機制:作用、原理、實現思路等》 《融雲技術分享:融雲Android端IM產品的網路鏈路保活技術實踐》
3、IM 系統整體框架

如上圖所示,為了保障鏈路存活,一套成熟的 IM 系統一般會包含消息鏈路和推送鏈路兩條長連接通道。
當有新消息到達時,消息服務首先會判斷消息鏈路是否存活,如果消息鏈路處於存活狀態,消息優先從消息鏈路下發到客戶端,否則會被路由到推送伺服器,由推送鏈路下發。
綜上所述:鏈路保活涉及到消息鏈路和推送鏈路兩條鏈路的保活策略。基於這兩條鏈路使用場景的不同,保活策略上除了心跳機制是相同的,其它保活策略各有不同。下面將逐一解讀。
4、鏈路保活的必要性
基於 TCP 的 Socket 連接建立之後,如果不做任何處理,這個連接會長時間存在並且可用嗎?答案是否定的。
原因有兩點:
1)默認Socket 連接無法及時探測到鏈路的異常情況,即使將 Socket 的屬性參數 KeepAlive 設置為 True 仍然無法及時獲取到鏈路存活狀態。這是因為 Socket 的連接狀態是由一個狀態機進行維護的,連接完畢後,雙方都會處於建立狀態。假如某台伺服器因為某些原因導致負載超高,無法及時響應業務請求,這時 TCP 探測到的仍然是連接狀態,而實際上此鏈路已經不可用了。
2)中國運營商的 NAT 超時機制會把一定時間內沒有數據交互的連接斷開,這個時間可能只有幾分鐘,遠無法滿足我們的長連接需求。
這方面更詳細的技術文章,請見:《為何基於TCP協議的移動端IM仍然需要心跳保活機制?》、《微信團隊原創分享:Android版微信後台保活實戰分享(網路保活篇)》
5、通用保活機制-心跳機制
基於以上原因,要維持 Socket 連接長時間存活,就需要實現自己的保活機制。
最通用的一種保活機制就是心跳機制。即客戶端每隔一段時間給伺服器發送一個很小的數據包,根據能否收到伺服器的響應來判斷鏈路的可用性。為了節省流量,這個包一般非常小(通常是越小越好,比如網易雲信的IM雲產品中1位元組心跳包是作為產品賣點進行宣傳的),甚至沒有內容。

那麼客戶端如何實現定時發送心跳包呢?一般有兩種方式。
一種是通過 Java 里的 Timer 來實現。
最基本的步驟如下:
1)建立一個要執行的任務 TimerTask ;
2)創建一個 Timer 實例,通過 Timer 提供的 schedule() 方法,將 TimerTask 加入到定時器 Timer 中,設置每隔一段時間執行 TimerTask , 在 TimerTask 里發送心跳包。這種方式實現起來較簡單,而且省電,不需要持有 WakeLock 。缺點也很明顯,長時間在後台,進程被回收或者系統休眠後, Timer 機制隨之失效。
另外一種方式是利用Android系統的定時任務管理器 AlarmManager 循環執行發送心跳包的任務。
這種方式不會因為系統休眠而失效,系統休眠後仍然可以通過 WakeLock 喚醒,執行心跳任務。
因此相對 Timer 機制,這種方式比較費電,使用的時候一定要注意如下幾點:
1)首先根據需求合理使用 AlarmManager 的鬧鐘參數。鬧鐘各參數的區別參考下表:

2)其次 AlarmManager 提供了 cancel() 方法,在設置新的定時任務前,通過 cancel() 方法取消系統里設置的同類型任務,避免設置冗餘任務。
最後,Android從 6.0 版本引入了 Doze 模式,並提供了新的鬧鐘設置方法 setExactAndAllowWhileIdle() ,通過該方法設置的鬧鐘時間,系統會智慧調度,將各個應用設置的事務統一在一次喚醒中處理,以達到省電的目的。推薦在Android 6.0 以上系統中,優先使用該方法。
這方面更詳細的技術文章,請見:
《應用保活終極總結(一):Android6.0以下的雙進程守護保活實踐》 《應用保活終極總結(二):Android6.0及以上的保活實踐(進程防殺篇)》 《應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)》 《Android進程保活詳解:一篇文章解決你的所有疑問》 《Android P正式版即將到來:後台應用保活、消息推送的真正噩夢》 《全面盤點當前Android後台保活方案的真實運行效果(截止2019年前)》
6、消息鏈路保活機制
消息鏈路作為收發消息的主要通道,需要最大程度保障鏈路的可用性。在鏈路不可用或者異常斷開時,能及時探測並啟動重連等保障機制。
基於以上特性,消息鏈路除了前面所說的心跳機制外,還另外維護了兩套鏈路優化機制:複合連接機制和重連機制。
複合連接機制的基本步驟如下:
1)客戶端連接導航伺服器,導航伺服器會下發應用對應的配置資訊,其中包括連接伺服器的地址列表;
2)客戶端從第一個伺服器地址嘗試連接,並啟動超時機制,如果連接失敗或沒有及時收到服務響應, 則繼續嘗試連接下一個直到成功連接,將成功連接的地址保存到本地,作為最優地址,後面連接時優先使用此地址。通過這種機制,能保障客戶端優先選用最優鏈路,縮短連接時間。

▲ 複合連接機制原理
重連機制:則是指業務層在檢測到與伺服器的連接斷開後,嘗試 N 次重新連接伺服器,首次斷開 1 秒後會重新連接,如果仍然連接不成功,會在 2 秒後(重連間隔時間為上次重連間隔時間乘 2 )嘗試重新連接伺服器,以此類推當嘗試重連 N 次後,仍然連不上伺服器將不再嘗試重新連接,只有在網路情況發生變化或重新打開應用時才會再次嘗試重連。

▲ 重連機制原理
7、推送鏈路保活機制
推送鏈路作為消息到達的補充手段,要求儘可能延長在後台的存活時間。即使被殺後,仍然能被再次喚醒。 iOS 手機有 APNS 來達到以上效果(詳見《了解iOS消息推送一文就夠:史上最全iOS Push技術詳解》),但Android的官方推送系統 FCM 在中國基本不可用。那在中國Android系統上如何保障推送到達呢?
首先咱們需要先了解下Android系統上進程管理的兩大機制:
1)一種是 LMK 機制,英文是 Low Memory Killer , 基於 Linux 的記憶體管理機制衍生而來。主要是通過進程的 oom_adj 值來判定進程的重要程度,從而決定是否回收這些進程。 oom_adj 的值越低,代表重要度越高,比如 native 進程, framework 層啟動的系統進程,優先順序一般都為負數。其次是前台可見進程,系統也不會回收。然而可見進程退到後台後, oom_adj 的值會立即升高,在系統定時清理時被殺;
2)另外一種機制是Android原生的許可權管理機制( AppOps ),各大廠家在此基礎上又進行了深度訂製化,比如小米的安全中心,華為的手機管家等,都用來進行許可權管理。該許可權管理機制運行在Android系統的框架層,上層各應用的進程如果想嘗試重新啟動,系統首先會去許可權管理中心檢查該進程有沒有自啟動許可權,如果有,才准予啟動。否則,從框架層直接限制系統的啟動。
基於以上兩種機制,推送鏈路的保活也可分為兩大類。
第一類:進程保活:
它的思路是根據 LMK 機制提高進程優先順序,降低被殺的幾率。
主要有以下幾種方法:
1.1)監聽黑屏事件,啟動 1 像素透明 Activity :使應用進程轉為可視進程,降低被殺概率。在螢幕亮時,關閉該 Activity 。
1.2)雙服務守護: A 服務以 startForeground() 形式啟動,發送一個通知, B 服務同樣以 startForeground() 形式啟動,且發送和 A 相同 ID 的通知,然後在 B 服務里調用 stopForeground() 方法,取消通知。這樣 A 服務就會以前台進程的形式存活,且不影響用戶感知。
1.3)根據文件鎖互斥原理,監視 Java 進程存活狀態:若被殺, Linux 層成功持有文件,則通過 exec() 命令,打開一個純 Linux 的可執行文件,開啟一個 Daemon 進程, 該進程因為從 Linux 層啟動,在Android 5.0 之前,優先順序會比較高,不會被殺。在Android 5.0 之後,該方式不再有效。
第二類:進程拉活的策略和Android系統的 AppOps 機制有關:
一般有如下幾種:
1)利用 Service 本身的 Sticky 屬性,在 Service 的 onStartCommand() 中返回 START_STICKY ,這樣當 Service 被殺掉後,系統會自動嘗試重啟。不過在中國訂製化的系統上,這種方式能成功重啟的幾率很低,需要用戶在許可權管理中心打開自啟動等許可權,才能成功拉活;
2)也就是前面講過的心跳機制,不過這裡要求使用 AlarmManager 設置 ELAPSED_REALTIME_WAKEUP 屬性的鬧鐘,在系統休眠後,才會正常接受到心跳事件,從而將進程拉活;
3)通過監聽網路切換,用戶行為等事件,拉起進程;
4)應用間互相拉活。比如系統里有好幾個應用集成了同一個 SDK , 那麼在用戶啟動其中某一個 App 的時候, SDK 會去掃描其它應用,把「兄弟姐妹」 拉活。這種方式對用戶體驗傷害非常大,會造成系統莫名其妙的耗電。
以下保活「黑科技」的詳細介紹文章,請詳讀:
《應用保活終極總結(一):Android6.0以下的雙進程守護保活實踐》 《應用保活終極總結(二):Android6.0及以上的保活實踐(進程防殺篇)》 《應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)》
隨著Android系統版本的迭代,對後台進程的啟動管控越來越嚴。為了解決推送的問題,各手機廠家推出了自己的系統級推送服務。由廠家在 Framework 層統一維護一條推送通道,上層所有應用共同使用該推送鏈路,不需要再維護單獨進程。當前支援系統級推送的廠家有:小米、華為、魅族、 vivo 、OPPO 。
鑒於Android系統對後台進程管控越來越嚴,保活「黑科技」已經不怎麼靈了:
《Android P正式版即將到來:後台應用保活、消息推送的真正噩夢》 《全面盤點當前Android後台保活方案的真實運行效果(截止2019年前)》
集成第三方系統級推送之後,整個消息的收發流程可以參考下圖:

這種系統級別的推送省電,省記憶體,到達率高。應用可以根據手機型號的不同,優先使用廠家系統級別的推送,再配合自身的保活機制,最大程度保障推送的到達率。