一直以來,APP程式保活
都是 各軟體提供商
和 個人開發者
頭疼的問題。畢竟一切的商業模式都建立在使用者對APP的使用上,因此保證APP程式的喚醒
,提升使用者的使用時間
,便是軟體提供商和個人開發者的永恆追求。
面對國內GCM
(Google Cloud Messaging)推送服務不可用,也未出現一個統一市場PUSH平臺
的現狀。早期的第三方軟體一般通過維持一個終端
與遠端伺服器
之間的TCP長連線
,達到PUSH拉活
和訊息及時送達
的目的。
而為了維持這個TCP長連線
不斷開,前提條件就是保證自己APP的後臺服務程式,不會被殺死(因為只有活著的終端程式才能定期與遠端伺服器通訊,保證長連線不斷連)。
因此在Android釋出的早期,各種技術論壇和GitHub出現了五花八門、各顯神通
的App程式保活
方案;如今隨著Android系統的逐漸完善,各種程式保活方案不斷受到限制,想要做到Android程式保活已經不太容易。
一般來說,Android程式保活
主要有以下兩方面工作:
- 程式保活:保持
程式不被系統殺死
或程式被殺後可以重新拉起
。
早期Android,對於後臺執行的Service服務程式
限制較小,第三方軟體保證自己APP後臺服務程式長時間執行
相對容易,但仍然是有被系統殺死的可能。
這個階段隨著APP的不斷增多,許多第三方APP開始利用系統漏洞(早期的Android系統尚不完善,可利用的漏洞較多),在APP進入後臺
或系統準備休眠時
,通過各種所謂的黑科技
(實際為流氓手段)保證自己的APP程式不被殺死
,甚至阻止系統休眠
。
伴隨隨著這類軟體的不斷增多,最終造成的結果是,使用者側手機卡頓
、待機時間短
、耗電量增加
。 - TCP保活:保證
終端
與遠端伺服器
之間的TCP長連線
不斷連。
在App程式保活
的基礎上,一般通過使用Android系統RCT時鐘 Alarm
每5~10分鐘喚醒一次系統,併傳送一條只有幾個位元組的TCP保活訊息
,來維持終端
與遠端伺服器
之間的TCP長連線
不斷開。
一、Android早期程式保活
這裡簡單回顧一下,Android早期App程式保活
的各種方案。
- 通過Service的onStartCommand方法中返回
START_STICKY
:
當Service的onStartCommand方法返回START_STICKY
時,若當前Service因記憶體不足被系統清理掉,待記憶體再次空閒時,系統將會嘗試重新建立此Service,一旦建立成功後將回撥onStartCommand方法,但其中的Intent將是NULL。 - 1畫素透明Activity程式保活:
監測到Android系統息屏時,啟動一個1畫素的前臺透明Activity
,欺騙系統,使其認為該應用為一個前臺應用而不會被清理(據說淘寶早期就使用過這種方案)。 - 開啟前臺服務:
利用系統漏洞,建立一個不包含系統通知欄
或透明系統通知欄
的前臺服務
,提升App程式的系統優先順序,使其不被系統清理。 - Fork Native守護程式:
Android 5.0 以前,App內部 fork 出來的native程式
不受系統管控。系統在殺死 App程式時,只會殺死對應的Java程式
。
因此誕生了一大批流氓軟體
,通過fork native程式
,在 App 的 Java 程式被殺死的時候,通過 ActivityManager 重新拉起被殺死的程式,從而達到“程式永生”
的目的(這個方案據說最初由360提出,後來大家紛紛效仿)。 - 通過其他活躍App拉起:
程式保活的後期,又出現一種程式保活的方案。
多個App組成一個聯盟,只要有一個App被使用者使用,其他所有聯盟內的App程式都會被拉起,以此來保證訊息的及時到達。
例如:整合個推、極光推送
的App,可以通過前臺活躍的App
拉起不活躍的App程式
,提升所有整合個推、極光推送
的第三方App推送訊息的到達率。
注:
當前隨著Android系統的不斷完善,以上方案大多不再有效。
二、Android各版本後臺程式限制
Android系統原始碼的開放,加之早期的系統尚不完善,眾多應用軟體提供商通過對AOSP各版本原始碼的深入解讀,提出了各種所謂黑科技的保活方案
,保證自己的應用程式程式不被系統清理。這段時期可謂是魑魅橫行、群魔亂舞;Android手機使用者則是苦不堪言,Android手機卡頓、待機時間短等問題也為人所詬病。
隨著Android系統的不斷完善,Google和國內終端手機廠商也對Android系統做了很多的改進,如今已經基本封死了第三方應用各種所謂黑科技流氓保活方案。
- Android後臺程式限制;
- 國內手機廠商後臺程式限制;
2.1 Android後臺程式限制
如今Google每年釋出的AOSP(Android Open Source Project)新版本中,每個版本均不斷增加後臺服務程式限制的相關條款
,一方面是為了限制第三方App後臺服務程式的執行,節省使用者手機資源與電量消耗;另一方面,增加後臺服務程式限制也是在保護使用者的隱私。
Android 5.0 開始,Android系統開始以uid為標識,查殺APP程式組
,通過殺死整個程式組
來殺死App程式,因此通過fork native 程式守護App程式這種方式從此不再有效。
Android 6.0開始,引入了Doze模式
使用者拔下裝置的電源插頭,並在螢幕關閉後的一段時間後,裝置會進入Doze模式
。
- 在Doze模式下,系統會嘗試通過限制應用訪問網路和 佔用CPU資源的措施來節省電量,阻止應用訪問網路,並延遲作業與Alarm鬧鐘。
- 系統會定期退出Doze模式一小段時間,讓應用完成其延遲的活動。在此維護期內,系統會執行所有待處理的同步操作、Alarm鬧鐘,並允許應用訪問網路。
- 隨著時間的推移,系統進入維護期的次數越來越少,這有助於在裝置未連線至充電器的情況下長期處於Doze模式狀態降低耗電量。
Android 7.0開始,加強了Doze模式
,進入Doze模式不再要求裝置靜止狀態。
只要螢幕關閉了一段時間,且裝置未插入電源,裝置就會進入Doze模。
Android 8.0開始,加強了應用後臺執行限制:
- 不能再通過
startService
建立後臺服務,否則將丟擲異常;但可通過Context.startForegroundService()
方法啟動一個帶通知欄提醒的前臺服務; - 應用處於後臺時,會對後臺應用檢索使用者當前位置資訊的頻率進行限制。應用每小時僅接收幾次位置資訊更新。
- 廣播限制:第三方應用將無法通過在
AndroidManifest
註冊靜態廣播來接收大部分的系統隱式廣播,以減少App對手機的喚醒,從而節省手機的電量;動態註冊不受影響。
Android 9.0開始,進一步加強了應用後臺執行限制:
- 後臺應用不再可以訪問麥克風和攝像頭,感測器(加速度感測器、陀螺儀);
- 建立前臺服務需要申請普通許可權:
FOREGROUND_SERVICE
。
Android 10開始,再進一步加強了應用後臺執行限制:
- 應用在後臺執行時,訪問手機位置需要動態申請
ACCESS_BACKGROUND_LOCATION
許可權,使用者則可以選擇拒絕; - 應用在後臺執行時,對後臺應用啟動Activity進行限制(執行前臺服務的應用仍然會被應用看做“後臺”應用)。
Android 11開始,再進一步完善了應用後臺訪問位置限制:
- 在Android11裝置上,對於targetSdkVersion=30(Android 11)的應用,需先申請前臺位置許可權,後申請後臺位置許可權。
- 在Android11裝置上,對於targetSdkVersion=30(Android 11)的應用,同時申請前臺、後臺位置許可權時,系統會忽略該請求,無任何響應(需首先獲取前臺位置許可權,再次申請後臺位置許可權)。
- 在Android11裝置上,對於targetSdkVersion<=29(Android 10)的應用,同時申請前臺、後臺位置許可權時,對話方塊不再提示始終允許字樣,而是提供了位置許可權的設定入口,需要使用者在設定頁面選擇始終允許才能獲得後臺位置許可權。
2.2 手機廠商後臺程式限制
Google新發布的各版本主要還是限制第三方App後臺程式服務的執行
,而國內各終端廠商則在AOSP的基礎上增加了Alarm
的限制。
國內手機廠商(華米OV等)在手機系統休眠後,第三方App註冊的Alarm定時喚醒鬧鐘幾乎全部無效!!!
“Google的後臺程式限制” 與 “國內手機廠商Alarm限制”相疊加的雙重影響是:
徹底封死了 “第三方手機軟體” 利用 “Alarm鬧鐘” 定時喚醒手機系統,維持 “終端” 與 “遠端伺服器” 之間的TCP長連線。達到 遠端伺服器 可隨時拉起 終端App,提升APP使用者端使用時長(提升APP的DAU)這一方案。
- 國內手機廠商 Alarm 限制;
- 國內手機廠商 後臺程式 限制;
2.2.1 手機廠商 Alarm 限制
國內終端手機廠商,不同廠商對應的Alarm限制方案也不相同,但目前已知的大概有以下兩個方向:
- Alarm 對齊機制:
當手機系統黑屏休眠時
,部分國內的手機品牌,會忽略 “第三方APP” 設定的 “Alarm喚醒週期”,強制將所有註冊Alarm鬧鐘的APP,做Alarm喚醒週期對齊
。
例如:假設有三款App,其設定的Alarm喚醒週期分別為1分鐘、3分鐘、5分鐘。手機會直接忽略以上三款App的Alarm喚醒週期設定,強制將Alarm喚醒對齊為每5分鐘喚醒一次。
系統休眠時,將所有App的喚醒時間做對齊,來減少手機的喚醒次數,節省使用者的待機電量消耗
。 - Alarm 無效:
當手機系統黑屏休眠時
,部分國內的手機品牌,忽略 “第三方APP” 註冊的全部Alarm,導致第三方應用註冊的Alarm無效。
2.2.2 手機廠商 後臺程式 限制
除了以上Alarm限制外,對於後臺執行程式,不同終端廠商也進行了不同的限制。例如,部分終端廠商增加了後臺程式耗電監測機制。
- 後臺程式耗電監測機制:
當手機系統黑屏休眠時
,部分國內的手機品牌,會啟動一個耗電監測程式
,若發現某個後臺程式持續耗電,將直接殺死耗電程式。
三、手機廠商程式白名單
前邊說道第三方App程式若在後臺執行,會受到國內廠商Alarm限制、後臺耗電監測等限制。但也有例外情況,比如:微信
。
這裡可以將其歸結為以下兩個白名單:
Alarm 對齊白名單
:
微信
這個體量的App 會被直接新增到,國內手機廠商的Alarm 對齊白名單
中。白名單中的程式,系統休眠時的Alarm喚醒
將不再受到限制。系統守護程式白名單
:
若能達到微信的體量,國內手機廠商甚至可能會將其新增到系統守護程式白名單
。該白名單中的程式,若某些原因後臺程式被殺,系統守護程式會在一定時間內迅速拉起該程式,從而保證程式的活躍。
四、Push推送 建議方案
前邊回顧了這幾年在程式保活這個問題上
,各軟體提供商
與Android系統研發製造商
之間互相博弈過程。
如今在“Google的後臺程式限制” 與 “國內手機廠商Alarm限制”
雙重限制下,第三方軟體希望做到App程式常駐後臺
已經不太可能。
但每一個第三方App,基本都存在訊息Push的需求。對於訊息PUSH需求,第三方APP可使用的方案是什麼?
- 接入終端手機廠商 Push通道;
- 程式新增到廠商程式保活白名單;
4.1 接入廠商 Push通道
在當前環境下的建議實現方案:接入終端手機廠商Push通道。
接入各終端廠商的Push通道,是最簡單的實現方案。
廠商的Push通道常駐記憶體,所有第三方App接入廠商Push後 即能保證訊息及時準確的到達,又可以減少終端使用者手機的常駐程式,延長使用者手機的待機時長
,可以說是對雙方都有利。
- 接入廠商PUSH 終端側實現方案;
- 接入廠商Push的 到達率上的注意點。
4.1.1 接入廠商PUSH 終端方案
接入廠商PUSH 終端方案,終端側可通過建立與維護一個單獨的推送 Module 模組
,其中整合 華為、小米、VIVO、OPPO、中興 5家
廠商的Push SDK;其他終端廠商的Push到達,可通過接入 個推
或 極光推送
來實現。
華米OV
等頭部手機廠商:
通過接入華米OV
等頭部廠商PUSH通道,保證市場上大多數手機使用者的PUSH到達率;- 其他非頭部手機廠商:
通過極光
或個推
等第三方市場佔有率較高的PUSH實現方案,來覆蓋除 華米OV 等頭部手機廠商外的其他終端手機,保證市場上少部分手機使用者的PUSH到達率;
採用這個方案,研發人員需要開發和維護6個Push SDK組成的Module模組。因此,接入廠商PUSH,優點和缺點可歸結如下。
- 優點:可以很好的保證 PUSH到達率;
- 缺點:開發與維護成本增加。
國內頭部手機終端廠商較多,研發的同學在一個App中需要同時維護 華為、小米、VIVO、OPPO、中興、魅族 等一系列廠商的PUSH SDK,研發維護成本急劇增加。
4.1.2 接入廠商Push的注意點
- 接入廠商Push的注意點;
- 將來終端手機廠商 PUSH渠道 可能收費。
接入廠商Push的注意點
接入廠商PUSH,在PUSH到達上仍有不同的限制條件,這一點也需要相關研發人員仔細研究各終端廠商PUSH的接入文件
。
例如OPPO,存在一個PUSH配額
問題:
OPPO PUSH配額是 OPPO推送訊息的數量限制規則。每天的Push量 超過這個PUSH配額,OPPO將不再下發Push。
OPPO PUSH配額官方描述:
https://open.oppomobile.com/wiki/doc#id=10200
OPPO PUSH配額官方描述如下:
將來終端手機廠商 PUSH渠道 可能收費。
目前終端手機廠商的PUSH渠道均是免費為開發者使用的,但隨著全市場的PUSH通道均為各終端廠商把控後,第三方APP的訊息PUSH也許存在收費的可能。個人認為廠商PUSH渠道 收費
在不久的將來可能性還是很大的。
4.2 新增到廠商程式保活白名單
終端手機廠商追求的還是利益,因此只要給錢或有錢賺,沒什麼不可談的。
與國內各終端廠商談合作 新增到對應的程式白名單中。
這個方案,需要與各終端廠商合作 達成利益上的同盟或找到利益契合點,從而使終端廠商為您的App開放程式保活白名單。
若 您的公司與各終端廠商達成了利益同盟
,恭喜您可以考慮採用維持一個終端與遠端伺服器的TCP長連線
,實現訊息及時到達終端,提升使用者端App使用時長的PUSH方案了。
五、TCP 動態心跳方案
前邊提到,若APP使用者體量足夠大
或 與各終端廠商達成了利益同盟
,可以考慮維持一個終端與遠端伺服器的TCP長連線
,實現訊息及時到達終端,提升使用者端App使用時長。
- TCP心跳 相關標準;
- 維持 TCP心跳 不斷連;
- TCP動態心跳 方案。
5.1 TCP心跳 相關標準
在Android下,通過自建 TCP長連線 來進行Push訊息推送,TCP長連線若存活,訊息Push才能及時送達。
而說到TCP心跳,那什麼是TCP心跳,TCP訊息的結構又是什麼?
rfc5626 4.4.1 Keep-Alive with CRLF
標準中,關於SIP訊息的TCP心跳給出了標準。
- Keep-Alive with CRLF;
- CRLF 詳細說明;
- ping pong 心跳訊息。
5.1.1 Keep-Alive with CRLF
rfc5626 4.4.1 Keep-Alive with CRLF
中,SIP訊息TCP心跳標準可以如下描述:
- 終端側需每隔一段時間(心跳間隔時間)需傳送一個“ping”(double CRLF)訊息,到遠端伺服器側;
- 終端傳送“ping”訊息後,若在10s之內未收到遠端服務端的“pong”(CRLF)訊息,則終端認為與服務端的連線失敗。
5.1.2 CRLF 詳細說明
根據rfc5626 4.4.1 Keep-Alive with CRLF
標準:
- 終端上行的ping訊息為 CRLFCRLF;
- 遠端伺服器下行的pong訊息為 CRLF;
pong訊息為CRLF
,含義是 回車
+換行
符;
ping訊息為double CRLF
,也就是CRLFCRLF
含義是 回車換行回車換行
符。
CRLF 在ASCII表中與 16進位制資料 的對應關係,如下圖所示:
ASCII的對應表中檢視:
- ping心跳訊息
CRLFCRLF
,對應的16進位制資料為0d0a0d0a
; - pong心跳訊息
CRLF
,對應的16進位制資料為0d0a
;
心跳 | 含義 | 縮寫 | 十六進位制 |
---|---|---|---|
ping | 終端上行心跳資料 | CRLFCRLF | 0d0a0d0a |
pong | 遠端伺服器下行心跳資料 | CRLF | 0d0a |
5.1.3 ping pong 心跳訊息
這一節關於 ping pong心跳訊息,從其訊息傳送接收流程、WireShark現網資料抓包、訊息結構舉例 三方面進行介紹。
- ping pong 心跳流程;
- ping pong 心跳WireShark抓包;
- ping pong 心跳訊息舉例。
ping pong 心跳流程:
ping pong 心跳訊息的傳送/接收流程,如下圖所示:
- 終端側需每隔一段時間(心跳間隔時間)需傳送一個“ping”(double CRLF 0xd0xa0xd0xa)訊息,到遠端伺服器側;
- 終端傳送“ping”訊息後,若在10s之內未收到遠端服務端的“pong”(CRLF 0xd0xa)訊息,則終端認為與服務端的連線失敗。
ping pong 心跳WireShark抓包:
ping pong 心跳WireShark抓包如下圖所示:
ping pong 心跳訊息舉例:
ping pong 心跳訊息的舉例如下所示:
// 終端側傳送: TCP Ping:0d 0a 0d 0a
Frame 2427: 60 bytes on wire (480 bits), 60 bytes captured (480 bits)
Linux cooked capture v1
Internet Protocol Version 4, Src: 10.xxx.xxx.xxx, Dst: 183.xxx.xxx.xxx
Transmission Control Protocol, Src Port: 46649, Dst Port: 5460, Seq: 21, Ack: 11, Len: 4
Source Port: 46649
Destination Port: 5460
[Stream index: 3]
[TCP Segment Len: 4]
Sequence Number: 21 (relative sequence number)
Sequence Number (raw): 149531750
[Next Sequence Number: 25 (relative sequence number)]
Acknowledgment Number: 11 (relative ack number)
Acknowledgment number (raw): 3481893389
0101 .... = Header Length: 20 bytes (5)
Flags: 0x018 (PSH, ACK)
Window: 65535
[Calculated window size: 65535]
[Window size scaling factor: -1 (unknown)]
Checksum: 0x727d [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
[SEQ/ACK analysis]
[Timestamps]
TCP payload (4 bytes)
Data (4 bytes)
Data: 0d0a0d0a
[Length: 4]
// 終端側接收:TCP ACK
Frame 2432: 56 bytes on wire (448 bits), 56 bytes captured (448 bits)
Linux cooked capture v1
Internet Protocol Version 4, Src: 183.xxx.xxx.xxx, Dst: 10.xxx.xxx.xxx
Transmission Control Protocol, Src Port: 5460, Dst Port: 46649, Seq: 11, Ack: 25, Len: 0
Source Port: 5460
Destination Port: 46649
[Stream index: 3]
[TCP Segment Len: 0]
Sequence Number: 11 (relative sequence number)
Sequence Number (raw): 3481893389
[Next Sequence Number: 11 (relative sequence number)]
Acknowledgment Number: 25 (relative ack number)
Acknowledgment number (raw): 149531754
0101 .... = Header Length: 20 bytes (5)
Flags: 0x010 (ACK)
Window: 21476
[Calculated window size: 21476]
[Window size scaling factor: -1 (unknown)]
Checksum: 0x7279 [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
[SEQ/ACK analysis]
[Timestamps]
// 終端側接收:TCP Pong:0d 0a
Frame 2433: 58 bytes on wire (464 bits), 58 bytes captured (464 bits)
Linux cooked capture v1
Internet Protocol Version 4, Src: 183.xxx.xxx.xxx, Dst: 10.xxx.xxx.xxx
Transmission Control Protocol, Src Port: 5460, Dst Port: 46649, Seq: 11, Ack: 25, Len: 2
Source Port: 5460
Destination Port: 46649
[Stream index: 3]
[TCP Segment Len: 2]
Sequence Number: 11 (relative sequence number)
Sequence Number (raw): 3481893389
[Next Sequence Number: 13 (relative sequence number)]
Acknowledgment Number: 25 (relative ack number)
Acknowledgment number (raw): 149531754
0101 .... = Header Length: 20 bytes (5)
Flags: 0x018 (PSH, ACK)
Window: 21476
[Calculated window size: 21476]
[Window size scaling factor: -1 (unknown)]
Checksum: 0x727b [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
[SEQ/ACK analysis]
[Timestamps]
TCP payload (2 bytes)
Data (2 bytes)
Data: 0d0a
[Length: 2]
// 終端側傳送:TCP ACK
Frame 2434: 56 bytes on wire (448 bits), 56 bytes captured (448 bits)
Linux cooked capture v1
Internet Protocol Version 4, Src: 10.xxx.xxx.xxx, Dst: 183.xxx.xxx.xxx
Transmission Control Protocol, Src Port: 46649, Dst Port: 5460, Seq: 25, Ack: 13, Len: 0
Source Port: 46649
Destination Port: 5460
[Stream index: 3]
[TCP Segment Len: 0]
Sequence Number: 25 (relative sequence number)
Sequence Number (raw): 149531754
[Next Sequence Number: 25 (relative sequence number)]
Acknowledgment Number: 13 (relative ack number)
Acknowledgment number (raw): 3481893391
0101 .... = Header Length: 20 bytes (5)
Flags: 0x010 (ACK)
Window: 65535
[Calculated window size: 65535]
[Window size scaling factor: -1 (unknown)]
Checksum: 0x7279 [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
[SEQ/ACK analysis]
[Timestamps]
5.2 維持 TCP心跳 不斷連
TCP長連線的存活且有效,終端才能維持與遠端伺服器的TCP心跳,保證訊息及時準確的到達。
因此影響TCP長連線穩定狀態的因素,值得研發人員重點關注:
- 運營商 NAT超時;
- 終端 網路狀態變化。
5.2.1 運營商 NAT超時
NAT(Network Address Translation)
是運營商的一個地址轉換閘道器。生活中最常見的NAT裝置,是我們家中使用的路由器
。
國內執行商網路下,因為 IP v4 的 IP 數量有限,運營商分配給手機終端的 IP 是運營商內網的 IP,手機要連線 Internet,需要通過運營商的閘道器做一個網路地址轉換(Network Address Translation,NAT)。
簡單的說運營商的閘道器需要維護一個外網 IP、埠到內網 IP、埠的對應關係,以確保內網的手機可以跟 Internet 的伺服器通訊。
內網地址 | 外網地址 |
---|---|
192.168.0.3:8888 | 120.132.92.21:9202 |
192.168.0.2:5566 | 120.132.92.21:9200 |
如上表所示:
- NAT裝置會根據NAT表對 傳送 和 接收 的資料做修改, 比如將
192.168.0.3:8888
發出去的資料封包改成120.132.92.21:9202
,外部就認為他們是在和120.132.92.21:9202
通訊。 - NAT裝置會將
120.132.92.21:9202
收到的封包資料 IP和埠改成192.168.0.3:8888
再發給內網的主機,這樣內部和外部就能互相通訊了。 - 但如果
192.168.0.3:8888 == 120.132.92.21:9202
這一對映因為某些原因被NAT裝置淘汰了,那麼外部裝置就無法與192.168.0.3:8888
通訊了。
為了節省資源,大部分國內行動網路運營商 在鏈路一段時間沒有資料通訊時,會淘汰 NAT 表中的對應項,造成通訊雙方鏈路的中斷。
因此,為了應對運營商NAT超時,終端
需每隔一段時間
向 遠端伺服器
傳送一個 TCP保活訊息
,也就是TCP保活心跳
。
以上也就是為什麼終端與遠端伺服器,需要維持TCP心跳的原因。
5.2.2 終端 網路狀態變化
終端網路狀態變化,也會使TCP長連線斷連,比如:終端行動網路與WIFI網路切換、網路斷開與重新連 等
因此,終端 APP 需監聽相應網路狀態變化事件:若發現終端網路狀態發生變化,需重新建立TCP長連線
。
5.3 TCP 動態心跳方案
- 跟隨手機狀態變化 調整心跳狀態;
- TCP 動態心跳週期的計算;
- 冗餘心跳。
5.3.1 動態調整 心跳狀態
這裡我們可以將手機不同狀態划進行劃分,比如:
應用處於前臺時為活躍態,剛剛進入後臺或息屏時為次活躍態,應用進入後臺一段時間後為後臺狀態,手機斷網或關機後為IDEL狀態。
可根據使用者手機不同狀態變化,動態調整應用的TCP心跳週期。
- 若應用處於前臺活躍態:固定心跳。
當應用處於前臺活躍狀態時,為了保證訊息及時準確的達到,使用固定心跳,保證使用者體驗。 - 應用剛進入後臺(或息屏)的次活躍態:使用幾次固定心跳。
應用進入後臺(或息屏)時,先用幾次固定跳維持長連結,保證使用者剛剛息屏或應用剛剛進入後臺,訊息的及時與準確到達,然後進入後臺自適應心跳計算。 - 應用進入後臺(或息屏)一段時間後:使用動態心跳。
應用進入後臺(或息屏)一段時間後,採用動態心跳,減少因心跳引起的空中通道資源消耗,以及因心跳引起的終端喚醒與電量消耗。 - 使用者切換網路或重新聯網:重新建立TCP長連線。
使用者斷網後,直接進入IDEL狀態,此時需檢測使用者網路狀態變化,若使用者聯網後,則重建TCP長連線;
使用者切換網路狀態(WIFI、行動網路互相切換),也需要重新建立TCP連線; - 應用重新進入前臺活躍態:固定心跳。
應用重新進入前臺活躍狀態,重新採用固定心跳,保證使用者訊息及時準確到達。
注:應用進入後臺(或者息屏)時,先用幾次最小心跳維持長連結,然後採用動態心跳。這樣做的目的是 儘量選擇使用者不活躍的時間段,來減少因動態心跳可能產生的 訊息送達不及時,從而對使用者體驗產生影響。
5.3.2 動態計算 心跳週期
動態計算心跳前,假設預定義定義幾個資料常量:
- MinHeart 最小心跳間隔時間;
- MaxHeart 最大心跳間隔時間;
- HeartStep 心跳增加時間步長;
預定義幾個資料變數:
- successHeart 動態探測 穩定後的心跳間隔時間;
- curHeart 當前成功心跳 初始為MinHeart;
注:
MinHeart、MaxHeart、HeartStep為預先定義的固定資料常量。
successHeart、curHeart為我們要探測的資料變數。
如上圖所示:
- 應用進入後臺(或者息屏)時,先用最小心跳間隔 MinHeart 維持心跳長連結;
- 若連續三次MinHeart心跳均成功,則認為下一次相同時間間隔的心跳大概率也會成功;此時下次心跳,可以嘗試增加一次心跳步進HeartStep;
- 增加TCP心跳步進後,再次進行三次心跳探測,若連續三次均成功,則繼續增加HeartStep步進,向上探測;
- 若出現失敗,則同一個curHeart累計出現3次失敗時,則認為這個時間為NAT超時時間;
同一個 curHeart 需要累計3次失敗,才認定為失敗,是為了排除使用者處於弱網環境的情況下,比如地鐵快速行進中。 - 同一個 curHeart 累計3次失敗時,則認為找到了NAT超時時間。
下次心跳使用 successHeart = curHeart- HeartStep 作為心跳時間週期。 - 使用 successHeart 作為心跳週期,若連續成功,則一直使用 successHeart 作為心跳週期;
- 使用 successHeart 作為心跳週期,若連續出現3次失敗,則認為探測資料失敗,或者使用者更換了網路環境,需重新探測。
5.3.2 冗餘心跳
在使用者對手機的的一些主動操作時,需增加冗餘心跳,確保及時收到訊息。
- 當使用者點亮螢幕(或熄滅螢幕)時,立刻做一次心跳;
- 當應用切換到前臺(或切換到後臺)時,立刻做一次心跳;
- 當使用者切換網路狀態時,重新建立TCP長連線;
六、參考:
1畫素Activity程式保活:
https://blog.csdn.net/zhenufo/article/details/79317068
Optimize for Doze and App Standby:
https://developer.android.google.cn/training/monitoring-device-state/doze-standby
Android 7.0 行為變更:
https://developer.android.google.cn/about/versions/nougat/android-7.0?hl=zh-cn
Android 8.0 行為變更:
https://developer.android.google.cn/about/versions/oreo/android-8.0-changes?hl=zh-cn
Android 9.0 行為變更:
https://developer.android.google.cn/about/versions/pie/android-9.0-changes-28?hl=zh-cn
Android10 行為變更:
https://developer.android.google.cn/about/versions/10/privacy?hl=zh-cn
Android11 行為變更:
https://developer.android.google.cn/about/versions/11/behavior-changes-all?hl=zh-cn
IPV6部署之後 是否還會大量使用NAT?
https://www.zhihu.com/question/27316663
Using Native IPv6 via Comcast in San Francisco:
https://blog.kylemanna.com/ipv6/using-native-ipv6-via-comcast-in-san-francisco/
rfc5626 SIP TCP心跳:
https://datatracker.ietf.org/doc/html/rfc5626#section-4.4.1
TCP Keep-Alive 和 應用層探活:
https://www.jianshu.com/p/00aec37b6be8
WebSocket細節 長連線保活及其原理:
https://baijiahao.baidu.com/s?id=1661934194124740212&wfr=spider&for=pc
2020年Android最新保活實現原理揭祕:
https://cloud.tencent.com/developer/news/585273
Andoird TCP通訊:
https://www.cnblogs.com/duwenqidu/p/12361811.html
關於TCP長連線、NAT超時、心跳包
https://www.cnblogs.com/sjjg/p/5830009.html
Android微信智慧心跳方案:
https://mp.weixin.qq.com/s/ghnmC8709DvnhieQhkLJpA