對於移動 App 來講,IM 功能正變得愈來愈重要,它可以建立起人與人之間的鏈接。社交類產品中,用戶與用戶之間的溝通能夠產生出更好的用戶粘性。
在複雜的 Android 生態環境下,多種因素都會形成消息推送不能及時達到客戶端。另外,不穩定的移動網絡也給數據傳輸的速率和可靠性增長了障礙。
本文詳解了網易雲信 IM SDK 在應對弱網環境、移動端硬件限制以及 Android 複雜的生態現狀時的探索與心得。如何實現不影響用戶體驗的後臺保活,改善的長鏈接加推送組合方案,以及在弱網環境大數據傳輸的優化實踐。linux
1.什麼是 IM
2. IM SDK 如何實現不影響用戶體驗的後臺保活
3.如何作長鏈接加推送組合方案
4.如何在弱網環境下優化大數據傳輸
IM 由兩個字組成:Instant, Messaging
即時性要求有新消息時可以當即收到,若是程序在後臺,則要能當即收到推送通知。
通訊則要求穩定可靠,系統不宕機,程序不崩潰,安全,傳遞消息時不會被攔截監聽,消息不丟,順序不亂,不重複,若是包含音視頻聊天,則要求延遲低,流暢不卡頓。
要真正作出一套穩定可靠的商用級 IM 系統,挑戰很是大。
第一個問題是消息推送。iOS 有 APNS 作推送,至關穩定。Android 自己也有GCM 能夠用,可是在國內有「牆」,直接就把 GCM 等等 Google 的服務所有擋在外面。爲了實現即時穩定的消息推送,從易信時代開始,網易就開始研究,隨着時間的推移,困難和方法也在不停的變化。
對於 IM,當 App 退到後臺,是必須還可以收到新消息提醒的,沒有 GCM,怎麼辦?在最初,惟一能作的,就是後臺運行了。這幾乎是接收推送的惟一途徑,就算是到如今,也是最主要的途徑。Android 從設計上,就是支持真後臺運行的,後臺運行的特性也是 Android 如今能如此成功的緣由之一,但另外一面,Android 長久以來一直襬脫不了的卡頓,耗電等壞名聲,後臺運行也拖不了干係。所以,系統對於後臺運行也不會聽任自流。android
第一個障礙是 Android 的 Low Memory Killer 機制。手機的內存有限,當後臺運行的進程愈來愈多,內存剩餘量也就隨之減小。當有一個新的 APP 想要啓動,若是內存不夠,LMK 機制就會啓動,從正在運行的進程中挑選一個清理掉,釋放出空間,而後新的 APP 就能夠運行了。
LMK 有兩個尺度去評判。一個是進程優先級,優先級越低,被清理的可能性越大,另外一個是內存佔用,佔的內存越多,被清理的權重天然也越大。
由於 LMK 機制的存在,雖然 APP 容許在後臺運行,但一樣也面臨隨時被清理的風險。所以,網易須要在被清理後及時的從新啓動。常規的,有4種方式可以作到。git
就是在 Service 的 onStartCommand 中返回sticky flag,這樣當 service 被 kill 掉後,系統會將它加入重啓的 pending 列表,在後面合適的時機再把 service 重啓。
鬧鐘,有循環鬧鐘和一次性鬧鐘兩種,在鬧鐘觸發後啓動對應的組件。
經過監聽各類系統事件,好比開機,網絡變化,mount/unmounts 等,在這些事件發生時啓動組件,由於這種方式會形成在這些事件發生時系統容易卡頓,在 7.0 裏面,Android 增長了限制。
這是在 5.0 裏面新增的,容許 App 在特定事件發生時作一些動做,好比充電,切換到 wifi 等。
雖然說不管怎麼作,App 終究免不了一死,但經過對照 LMK 的評判準則,仍是能夠下降APP被清理的機率的。第一個就是下降進程的內存佔用。若是採用單進程的模式,因爲進程中包含了 UI,Webview,各類圖片緩存等內容,內存必然會居高不下,降不下來。IM 軟件通常都會採用雙進程甚至多進程的策略,將 push 進程獨立出來,在 push 進程裏只處理網絡鏈接和 push 業務,不參與任何其餘業務邏輯,更不包含任何 UI。github
如下是網易雲信 Android SDK 的架構,按照分層的結構模式設計。最底下青色的一層是 push 層,他就是做爲一個獨立進程運行的。他只負責處理網絡長鏈接的相關工做,好比安全加密,心跳,鑑權,封包解包等工做,全部業務邏輯都交給UI進程的服務模塊去作。來看一下雲信 Demo 的進程內存佔用狀況。上面一個是主進程,看第四列 PSS 的數據,內存佔用是 50M 左右,下面一個是 push 進程,內存佔用只有 10M 左右。當處於後臺時,push 進程被清理機率比 UI 主進程低不少。算法
下降被清理機率的第二個手段是提高進程優先級。先看這個例子,這是綠色守護的一個截圖,最上面是「暫不自動休眠」,由於這裏列出的兩個 App 的狀態都是工做中,對應的進程優先級是「可視進程」。但這兩個 App 並無提供桌面小部門在運行,也沒有指示前臺服務的常駐通知欄提醒,事實上,他們就只是在後臺運而已。一般進程退到後臺後,其進程優先級類型就變成了較低的後臺進程,而不是這樣的「可視進程」,他們是經過什麼方法來提高優先級,下降被清理機率呢?緩存
Android 在設計前臺服務上有一個漏洞,經過兩個服務配合就能建立一個隱形的前臺服務。這裏有兩個已經啓動的 Service: A 和 B。先在 A 中調用 startForeground,提供一個 NOTIFY_ID, 而後 A 就變成前臺服務了,同時有了一個 ID 爲 NOTIFY_ID 的常駐通知欄提醒,而後網易在 B 中也調用startForeground,提供相同的 NOTIFY_ID, B 也變成了前臺服務,由於兩個通知ID相同,所以這一次就不會建立新的通知欄提醒了。而後再在 A 中調用stopForeground,A 的前臺屬性被取消,同時,常駐通知欄提醒也會被移除,可是,Service B 並不會受到任何影響,仍是前臺服務,這是再把 A 停掉,進程就只剩下前臺服務 B 了,進程也變成了前臺進程,但用戶不會有任何感知。安全
正常來講,作了上面 3 步以後,進程就可以比較穩定的在後臺運行了。網絡
但在有些狀況下,推送進程卻永遠起不來。跟蹤以後發現,除了系統可以殺掉後臺運行的進程外,用戶也同樣是能夠殺死進程的。用戶殺掉進程的方式有兩種,一種是在最近任務列表中將 App 劃掉,這種方式和系統殺掉進程效果相同。另一種就是經過這裏的 force stop,這種方式比系統清理更加完全。不但 App正在運行的進程會被清理,app 當前在重啓列表中的待重啓服務,註冊的各類鬧鐘,事件監聽組件等都會被移除,除非用戶在主動點擊或者系統重啓等外力,App 無法再本身從新爬起來了。
在有些國內的像 MIUI 一類的 ROM 上,用戶從最近任務列表中將 App 移除,效果居然也是 force stop。正常來講,若是是用戶主動操做,App 自己也不該該再重啓了。但有些時候這個並非用戶本意,何況,對於 IM 軟件來講,消息推送是必定要獲得保障的,不然不明正確的吃瓜羣衆們會以爲是軟件不行,連消息推送都作很差。架構
第一個是經過兩次 fork 加上 exec 的方式。兩個 fork 後,第一次 fork 的進程退出,第二次 fork 出來的進程就會被 init 進程領養。用戶此時再 force stop,由於這個進程復進程是 init,而不是 Zygote,所以不會被清理。因爲這個進程仍是從 android 進程 fork 出來的,帶有 android 運行時環境以及復進程的資源,因此內存會比較大,這裏能夠再經過 exec 命令,打開一個純 linux 的可執行文件,開啓一個 daemon 進程,其內存佔用大概只有 100K+,對用戶也就徹底無感了。利用這個後臺進程,能夠定時的將 push 進程拉起來。此種方式只在 5.0 如下的系統中有效,在 4.4 及以上系統中,SELinux 特性是強制開啓的,exec 沒有權限執行,同時在 5.0 以後,ActivityManager 在作 force stop 以及移除任務時,只要是具備相同的 uid 的進程,就會所有清理掉,再也不漏掉沒有虛擬機環境的進程。app
最後一個後臺保活的手段是一個大殺器,也是帶有強烈的中國特點。由於前面所列的全部保活手段都不是那麼保險,所以想出來這麼一個互相保活的方式。當一個 App 進程起來後,他就去掃描已安裝的應用列表,看看有沒有本身的兄弟姐妹,好比說同一個長的 App,或者是集成了同一個 SDK 的 APP,若是有,就把這些 App 都拉起來。這也就是如今比較出名的「全家桶」方案。雖然說這種方法確實可以帶來較高的後臺存活率,特別是那些大廠和應用普遍的 SDK,可是這種方式對於用戶的傷害也很是大,若是有後臺推送的必要性,且不會對用戶體驗形成太大傷害時,此方式還可使用,但若是隻是爲了推廣告,則會對用戶形成傷害,反過來,也可能會致使用戶直接卸載 App。
如今各類手機管理軟件都會對這種全家桶喚醒方式作限制,特別是在 root 過的機器上,能夠作到徹底切斷這些喚醒路徑。同時,不少 ROM 也會自帶管理軟件,限制後臺運行和後臺喚醒,以便給設備換取更長的續航。在目前國內的 Android 生態環境中,不管採用什麼方式,想要一直在後臺運行時愈來愈難了,須要從新想另外的辦法來保障消息推送。另外一方面,做爲開發者,也有義務爲用戶提供更好體驗的軟件,而不是無休止的在後臺浪費用戶的資源。
隨着即時通信以及音頻處理和壓縮技術的不斷髮展,效果更好、適用範圍更廣、性能更高的算法和新的技術必將不斷涌現,若是你有好的技術或者分享,歡迎關注網易雲信官方博客和 GitHub:
關注更多技術乾貨內容: 網易雲信博客
歡迎關注 網易雲信 GitHub
歡迎關注 網易雲信官網