相信大家對推送這項技術(shù)并不陌生。如果沒(méi)聽(tīng)說(shuō)過(guò),那么作為一個(gè)充滿(mǎn)好奇心的孩子,你一定想過(guò)這個(gè)問(wèn)題:睡覺(jué)前我明明關(guān)閉了淘寶、網(wǎng)易新聞等 app,為什么第二天他們又自動(dòng)出現在我手機的通知欄上呢?這其實(shí)就是推送系統干的好事:在你睡覺(jué)的時(shí)候,服務(wù)器悄悄的向你的手機推送了一個(gè)消息,然后喚醒了你已經(jīng)關(guān)閉的 app。事實(shí)上,無(wú)論你愿意與否,現在大多數 ‘有節操’ 的 app,都已經(jīng)內置了推送系統,并時(shí)刻準備著(zhù)登上你的通知欄的 ‘頭條’。
傳統的 app 架構里,通常是 app 主動(dòng)向服務(wù)器請求數據,服務(wù)器被動(dòng)的提供數據。以新聞客戶(hù)端 app 為例:app 被用戶(hù)打開(kāi)的時(shí)候,會(huì )通過(guò)網(wǎng)絡(luò ) (無(wú)論 3g、4g 還是 wifi) 連接到服務(wù)器上,向服務(wù)器請求最新的新聞。服務(wù)器收到請求,從自己的數據庫里查詢(xún)最新的新聞,返回給 app。app 收到服務(wù)器返回的數據,經(jīng)過(guò)一系列的解析處理操作,最終把最新的新聞呈現給用戶(hù)。一次通信就完成了。然而如果此時(shí)服務(wù)器上又有了新的新聞,無(wú)論多么重要,在用戶(hù)沒(méi)有主動(dòng)刷新的情況下,是沒(méi)有辦法讓用戶(hù)看到的。推送就是為了解決這樣的困境的,它給了服務(wù)器一個(gè)展示自我的機會(huì ),主動(dòng)連接上所有的 app,告訴他們我有新的新聞了,你們再來(lái)請求一次吧,于是收到推送的 app(即時(shí)此時(shí)已經(jīng)被用戶(hù)關(guān)閉了)又去服務(wù)器請求最新的新聞,這樣用戶(hù)就能看到最新的新聞了。
從技術(shù)上來(lái)講,實(shí)現一個(gè)推送系統需要服務(wù)器端和終端的配合。一種方法是輪詢(xún),也就是不停的向服務(wù)器發(fā)起請求。這其實(shí)很好理解,作為 app,我既然不知道什么時(shí)候會(huì )發(fā)生新的新聞,那我一遍一遍的問(wèn)好了,而且我知道這樣一定會(huì )成功的。顯而易見(jiàn),這種方法 app 端費時(shí)費力不說(shuō),電量流量也扛不住啊,服務(wù)器要處理如此量大的請求,必然也是非常頭疼的。另一種方法是服務(wù)器和 app 建立一個(gè)長(cháng)時(shí)間連接的通道,通過(guò)這個(gè)通道,不僅 app 可以向服務(wù)器請求數據,服務(wù)器也可以向 app 發(fā)送數據,看起來(lái)非常完美,但是如果 app 被用戶(hù)關(guān)閉的話(huà),通道就斷掉了。好在 android 系統給 app 提供了一個(gè)這樣的環(huán)境,app 可以啟動(dòng)一個(gè)后臺服務(wù)來(lái)維持這個(gè)通道,即使 app 被關(guān)掉了,服務(wù)依然可以運行,通道依然還在工作(ios 后面會(huì )講)?;氐角懊娴睦?,你在睡覺(jué)前關(guān)掉了淘寶,但是并沒(méi)有關(guān)閉淘寶的后臺服務(wù),淘寶依然可以接收服務(wù)器推送來(lái)的指令,把自己的喚醒。
那么如何維持這樣的一條長(cháng)時(shí)間連接的通道呢?就好比兩個(gè)人打電話(huà),一開(kāi)始聊的熱情有來(lái)有往,后來(lái)慢慢沉默下來(lái)了,幾分鐘之后,電話(huà)的另一頭沒(méi)有任何動(dòng)靜,如何知道那邊的人還在呢?很簡(jiǎn)單,只需要另一頭的人每隔幾分鐘說(shuō)一個(gè)字就行。同樣的道理,app 會(huì )每隔一段時(shí)間向服務(wù)器報告自己還活著(zhù),就像心跳一樣,服務(wù)器收到后,就知道這個(gè)通道是可以繼續使用的了。然而天下沒(méi)有免費的午餐,發(fā)送心跳是有代價(jià)的,一般手機鎖屏之后,為了省電 CPU 是出于休眠狀態(tài)的,然而發(fā)送心跳就會(huì )喚醒 CPU,必然會(huì )增加電量的消耗。這還只是一個(gè)長(cháng)連接通道的情況,如果手機里裝了 2、30 個(gè)帶有推送的 app 呢?先別急著(zhù)抱怨,聰明的 android 工程師和 ios 工程師早就想到了這一點(diǎn),他們分別設計了 GCM 和 apns 來(lái)解決多個(gè) app 有多個(gè)長(cháng)連接通道的問(wèn)題。以 apns 為例,ios 開(kāi)通了一條系統級別的長(cháng)連接通道,通道的一端是手機的所有 app,另一端是蘋(píng)果的服務(wù)器。app 的服務(wù)器如果有新的消息需要推送的話(huà),先把消息發(fā)送到蘋(píng)果的服務(wù)器上,再利用蘋(píng)果的服務(wù)器通過(guò)長(cháng)連接通道發(fā)送到用戶(hù)手機,然后通知具體的 app。這樣就做到了即使手機安裝了 100 個(gè) app,也只需要向一條通道里發(fā)送心跳。
回到 Android,系統提供的 GCM 只能在 Android2.2 以上才能使用,3.0 以下必須要安裝 Googleplay 并登陸了 Google 賬號才能支持。而國內發(fā)行的手機大多是閹割掉了 google 服務(wù)的。因此,對于 Android 系統來(lái)說(shuō),各家 app 只能各顯神通,開(kāi)發(fā)自己的專(zhuān)用長(cháng)連接通道了。然而這時(shí)候他們遇到了 app 的天敵:管家和衛士們。前文說(shuō)了,app 想要及時(shí)收到服務(wù)器推送的消息,關(guān)鍵在于自己與服務(wù)器的長(cháng)連接通道不被關(guān)閉,也就是自己的后臺服務(wù)可以一直在后臺運行,而管家和衛士們的一鍵清理功能就是專(zhuān)治這種 “毒瘤” 的。道高一尺魔高一丈,app 在與管家和斗士們的長(cháng)期斗爭中,總結了一系列躲避被清理掉的方法,什么定時(shí)自啟能力、什么相互喚醒、什么前臺進(jìn)程等等,當然這就是另一個(gè)話(huà)題了,我們后面會(huì )講到。
總結起來(lái),app 和后臺的連接方式有兩種。一種叫 pull,也叫輪詢(xún),就是定期的不斷向后臺請求,缺點(diǎn)是耗電,費流量,不環(huán)保。對于一名有追求的程序員,他應該會(huì )比較惡心這種方式的,你千萬(wàn)不要對他說(shuō),我不管你怎么實(shí)現,我就要這種效果這種傻逼話(huà)了,凡事應該找到最優(yōu)路徑。另一種叫 push,app 和后臺一直維持了一條通信通道,兩端不定期的就會(huì )偷摸的約會(huì ),告訴對方 “I ‘m Here”,也能順帶把信息互相攜帶了。缺點(diǎn)是要維持一條長(cháng)連接通道,這條通道容易被其他程序殺死,要多想復活辦法。
聯(lián)系客服