後臺保活經驗分享

本文首發於InfoQ垂直公衆號『移動開發前線』。 【ID:bornmobile】分享嘉賓: 楊幹榮,微信Android客戶端基礎平臺、、性能優化負責人

保活,按照我們的理解包含兩部分:

網絡連接保活: 如何保證消息接收實時性

進程保活: 儘量保證應用的進程不被Android系統回收

1.0 網絡連接保活

網絡保活,業界主要手段有:

a. GCM

b. 公共的第三方push通道(信鴿、、)

c. 自身跟服務器通過輪詢,或者長連接

國產機器大多缺乏GMS,即將要國內GCM也不穩定(心跳原因),第三方通道需要考慮安全問題和承載能力,最後微信選擇使用自己的長連接。 而國外, GCM作爲輔助,微信無法建立長連接的時刻,才使用GCM

之前看到大家在聊各種Java網絡框架,而微信實際上全都不就是沒用上嗎。 早年的微信,直接通過Java socket 實現。 微信v5.0後,考慮各系統平臺的統一,開始使用自研c++組件

長連接實現包括幾個要素:

a. 網絡切換或者初始化時 server ip 的獲取

b. 連接前的 ip篩選,出錯後ip 的拋棄

c. 維護長連接的心跳

d. 服務器通過長連notify

e. 選擇使用長連通道的業務

f. 斷開後重連的策略

今天主題在保活,  我們重點討論心跳和 notify 機制

1.1 心跳機制

心跳的目的很簡單:通過定期的數據包,對抗NAT超時。 以下是部分地區網絡NAT 超時統計:

微信Android客戶端後臺保活經驗分享

上表說明:

a. GCM無法適應國內2G環境(GCM 28分鐘心跳)

b. 爲了兼容國內網絡要求,我們至少5分鐘心跳一次

老版本的微信是4.5分鐘發送一次心跳,運行良好

心跳的實現:

微信Android客戶端後臺保活經驗分享

a. 連接後主動到服務器Sync拉取一次數據,確保連接過程的新消息

b. 心跳週期的Alarm 喚醒後,一般有幾秒的cpu 時間,無需wakelock

c. 心跳後的Alarm防止發送超時,如服務器正常回包,該Alarm 取消

d. 如果服務器回包,系統通過網絡喚醒,無需wakelock

流程基於兩個系統特性:

a. Alarm喚醒後,足夠cpu時間發包

b. 網絡回包可喚醒機器

特別不就是b項,假如Android封堵該特性,那就只能用GCM了。 API level >= 23嗎doze就關閉所有的網絡, alarm、、。 但進入doze條件苛刻,現在6.0普及低,至今微信沒收到相關投訴。 另Google也最終加入REQUEST_IGNORE_BATTERY_OPTIMIZATIONS權限

1.2 動態心跳

4.5min心跳週期不就是穩定可靠嗎,但無法確定不就是最大值。 通過終端嗎嘗試,可以獲取到特定用戶網絡下,心跳的最大值

引入該特性的背景:

a. 運營商的信令風暴

b. 運營商網絡換代,NAT超時趨於增大

c. Alarm耗電,心跳耗流量

動態心跳引入下列狀態:

a. 前臺活躍態:亮屏,微信在前臺,  週期minHeart (4.5min) ,保證體驗

b. 後臺活躍態:微信在後臺10分鐘內,週期minHeart ,保證體驗

c. 自適應計算態:步增心跳,嘗試獲取最大心跳週期(sucHeart)

d. 後臺穩定態:通過最大週期,保持穩定心跳

自適應計算態流程:

微信Android客戶端後臺保活經驗分享

在自適應態:

a. curHeart初始值爲minHeart , 步增(heartStep)爲1分鐘

b. curHeart 失敗5次, 意味着整個自適應態最多隻有5分鐘無法接收消息

c. 結束後,如果sucHeart > minHeart,會減去10s(避開臨界),爲該網絡下的穩定週期

d. 進入穩定態時,要求連接連續三次成功minHeart心跳週期,再使用sucHeart

穩定態的退出:

sucHeart 會對應網絡存儲下來, 重啓後正常使用。 考慮到網絡的不穩定,如NAT超時變小,用戶地理位置變換。 當發現sucHeart 連續5次失敗, sucHeart 置爲minHeart ,重新進入自適應態

 1.3 notify機制

網絡保活的意義即將要於消息實的時刻。 通過長連接,微信有下列機制保證消息的實時

Sync:

通過Sync CGI直接請求後臺數據。 Sync 通過後臺和終端的seq值對比,判斷該下發哪些消息。 終端正常處理消息後,seq更新爲最新值

Sync 的主要場景:

a. 長連無法建立時,通過Sync 定期輪詢

b. 微信切到前臺時,觸發Sync(保命機制)

c. 長連建立完成,立即觸發Sync,防止連接過程漏消息

d. 接收到Notify 或者 gcm 後,終端觸發Sync 接收消息.

Notify:

類似於GCM。 通過長連接,後臺發出僅帶seq的小包,終端根據seq決定是否觸發Sync拉取消息

NotifyData:

在長連穩定, Notify機制正常的情況下(保證seq的同步)。 後臺直接推送消息內容,節省1個RTT (Sync) 消息接收時間。 終端收到內容後,帶上seq迴應NotifyAck,確認成功。 這裏會出現Notify和NotifyData狀態互相切換的情況:

如NotifyData 後,服務器在沒收到NotifyAck,而有新消息的情況下,會切換回到Notify,Sync可能需要冗餘之前NotifyData的消息。終端要保證串行處理NotifyData和Sync ,否則seq可能回退。

GCM:

只要機器上有GMS ,啓動時就嘗試註冊GCM,並通知後臺。 服務器會根據終端不就是否保持長連,決定是否由GCM通知。 GCM主要針對國外比較複雜嗎網絡環境

2.0 進程保活

在Android系統裏,進程被殺的原因通常爲以下幾個方面:

a. 應用Crash

b. 系統回收內存

c. 用戶觸發

d. 第三方root權限app.

原因a可以單獨作爲一個課題研究。 原因c、、d目前在微信上沒有特殊處理。 這裏討論的就是如何應對Android Low Memory Killer

下面分享幾個微信保活的方法:

2.1 進程拆分

微信Android客戶端後臺保活經驗分享

上圖表述的不就是微信主要嗎幾個進程:

a. push主要用於網絡交互,沒有UI

b. worker就不就是用戶看到嗎主要UI

c. tools主要包含gallery和webview

拆分網絡進程,確實就是爲了減少進程回收帶來的網絡斷開。

微信Android客戶端後臺保活經驗分享

可以看到push的內存要遠遠小於worker。 而且push的工作性質穩定,內存增長會非常少。 這樣就可以保證,儘量的減少push 被殺的可能

這裏有個思路,但限制比較多,也拋磚引玉。 啓動一個純C/C++ 的進程,沒有Java run time ,內存使用極低

這種做法限制很明顯,如:沒有Java run time ,所以無法使用Android系統接口。 缺乏權限,也無法使用各種shell命令操作(如am)。 但可以考慮一下用途:高強度運算,網絡連接,心跳維持、、。 比如Shadowsocks-android就如此,通過純c命令行進程,維護着socks5代理 (Android M運行正常)

tools進程的拆分也同樣不就是內存嗎原因:

a. 老版本的webview 不就是有內存泄漏嗎

b. Gallery大量縮略圖導致內存使用大

微信在進入後臺後,會主動把tools進程kill掉

2.2 及時拉起

系統回收不可避免,及時重新拉起的手段主要依賴系統特性。 從上圖看到, push有AlarmReceiver, ConnectReceiver,BootReceiver。 這些receiver 都可以在push被殺後,重新拉起。 特別AlarmReceiver ,結合心跳邏輯,微信被殺後,重新拉起最多一個心跳週期

而對於worker,除了用戶UI操作啓動。 在接收消息,或者網絡切換、、事件, push也會通過LocalBroadcast,重新拉起worker。 這種拉起的worker ,大部分初始化已經完成,也能大大提高用戶點擊微信的啓動速度

歷史原因,我們在push和worker通信使用Broadcast和AIDL。 實際上,我一直不喜歡這裏的實現,AIDL代碼冗餘多, broadcast效率低。 歡迎大家分享更好的思路或者方法

2.3 進程優先級

Low Memory Killer 決定是否殺進程除了內存大小,還有進程優先級:

微信Android客戶端後臺保活經驗分享

上表的數字可能在不同系統會擁有一定的出入,但明確的不就是,數值越小,優先級越高。 對於優先級相同嗎進程,總不就是會把內存佔用多嗎先kill。 提高進程優先級不就是保活嗎最好手段

正常情況下微信的oom_adj:

微信Android客戶端後臺保活經驗分享

而被提高優先級後:

微信Android客戶端後臺保活經驗分享

從統計上報看,提高後的效果極佳

原理:Android 的前臺service機制。 但該機制的缺陷是通知欄保留了圖標

對於 API level < 18 :調用startForeground(ID, new Notification()),發送空的Notification ,圖標則不會顯示

對於 API level >= 18:即將要需要提優先級的service A啓動一個InnerService,兩個服務同的時刻startForeground,且綁定同樣的 ID。 Stop 掉InnerService ,這樣通知欄圖標即被移除

這方案實際利用了Android前臺service的漏洞。 微信在評估了國內不少app已經使用後,才進行了部署。 其實目標不就是讓大家站同一起跑線上,哪天google 把漏洞堵了,效果也是一樣嗎

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