Android實現推送的那些事

皮皮醬發表於2018-02-23

推送定義:在任何時間地點服務端向客戶端推送一條訊息,如果客戶端線上或者下次上線,就能接收到該訊息。
通常想到的實現方式是:輪詢、tcp長連,其目的都是讓服務端和客戶端之間時刻保持線上狀態
對於客戶端而言,
輪詢:無非是寫個執行緒按某種配置的時間間隔無限迴圈去請求服務端是否有新的訊息,當有新的訊息,就提醒給使用者
tcp長連:與服務端建立tcp長連,這樣服務端就可以直接給客戶端傳送訊息了,當前市面上基本上是以此種方式居多
上面兩種方式都是比較消耗資源的,而這裡我們使用的是另外的一種方式來實現的,如[MobPush](http://mobpush.mob.com/)
udp無連線:其實現基本原理為,客戶端建立socket並向服務端傳送udp包,服務端接收到請求連線的udp包之後,將客戶端id與ip和埠號進行繫結,當要向某個客戶端id傳送訊息時,找到其對應的ip和埠號,然後將訊息組裝成udp包傳送即可,其大致流程如下:

Android實現推送的那些事

而對於客戶端需要解決的如下幾個問題:

1. 如何維護客戶端id與路由之間的繫結關係;

2. 如何延長客戶端的線上狀態(app保活) 

3. 客戶端效能考慮

下面將針對這幾點進行逐步介紹

維護客戶端與路由的繫結關係

這裡我們需要了解一下NAT,所謂NAT就是,在區域網內部網路中使用內部地址,而當內部節點要與外部網路進行通訊時,就在閘道器處,將內部地址替換成公用地址,從而在外部公網上正常使用。

所以當傳送udp包到伺服器時,伺服器拿到的ip和埠其實是客戶端在路由上對映的ip和埠,所以我們需要維護路由上的對映表,這時就需要定期傳送心跳包,以保證路由上的對映關係不會被清除掉。

1. 維護心跳包

主要作用是防止NAT超時, 和探測連線是否斷開,並根據實際情況進行重連操作,其流程如下:

Android實現推送的那些事

2. 網路監測

當網路切換和變化時,會導致對映關係失效,所以我們需要做相應的監測和重連

1. 監聽網路變化,當網路型別變化或者斷開後重新連線上時,進行重連

2. 定期監測ip地址變化,如果監測到ip地址有變化時,則進行重連

APP保活

app保活是一個老生常談的話題,經過廣大開發者多年累積與篩選,網際網路上相關文章層出不窮,目前看來不算什麼硬梗,大多都按套路出行,這裡也套路套路

當應用退到後臺時,為了確保推送通道能夠正常使用而不被系統回收,通常會做一些程式保活和拉活的策略,大體分為以下幾類:

1. 利用系統Service機制拉活

將 Service 設定為 START_STICKY,利用系統機制在 Service 掛掉後自動拉活 

如下兩種情況無法拉活:

1.Service 第一次被異常殺死後會在5秒內重啟,第二次被殺死會在10秒內重啟,第三次會在20秒內重啟,一旦在短時間內 Service 被殺死達到5次,則系統不再拉起。

2.程式被取得 Root 許可權的管理工具或系統工具通過 forestop 停止掉,無法重啟。

經測試,在絕大多數手機任務程式中,手動殺掉程式後,是不會自動重啟的(符合情況2)

2. 設定程式優先順序

當程式退到後臺後,系統在回收資源時,會根據程式優先順序,進行資源回收,優先順序越高越晚被回收,所以儘可能地提高service程式的優先順序,可以在一定程度上保障其在後臺時不被系統回收

程式按照重要性分為如下5類:

1.前臺程式(Foreground process)

2.可見程式(Visible process)

3.服務程式(Service process)

4.後臺程式(Background process)

5.空程式(Empty process)

一般的後臺程式程式屬於第4類,我們可以通過setForeground將service提升到2,但是這種方案必須與一條可見的通知繫結在一起,而這種體驗顯然不能被使用者接受

當然我們可以通過new Notification()的方式設定一個空的通知,與之繫結,但只在4.3以下版本才有效,如下:

```

if (Build.VERSION.SDK_INT < 18) {

service.startForeground(1001, new Notification());//API < 18 ,此方法能有效隱藏Notification上的圖示

}

```

神奇的開發者們發現了一種通過多實現一個TmpService,在MainService和TmpService兩個service中同時傳送具有相同 ID 的 Notification,然後幹掉TmpService,這時Notification會自動消失,而MainService的優先順序已經被提升到2了,從而達到了目的。缺陷是:需要開發者在manifest多配置一個service,可能在不久的將來也會被官方更新掉,不建議採納。
3. 利用系統廣播拉活
該方式是通過AndroidManifest.xml 註冊一些特定的系統廣播方式,來拉活程式,但是這種方式在高版本中已經被官方封掉了,所以也成了一堆沒必要的配置
當然我們可以在程式碼中新增了相關的系統廣播註冊、同時在監聽程式死掉時傳送的廣播,測試在某些低版本的手機上有效

```

IntentFilter intentFilter = new IntentFilter();

intentFilter.addAction(Intent.ACTION_USER_PRESENT);

intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

intentFilter.addAction(Intent.ACTION_BOOT_COMPLETED);

intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);

intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);

intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);

```

4. 使用AlarmManager

使用AlarmManager定時傳送心跳、定時檢查ip變化

但是經測試當系統休眠時,AlarmManager也停止了工作,且在不同sdk版本上需要採用不同的set方式,如下:

```

AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

if (pingPendingIntent != null) {

am.cancel(pingPendingIntent);

}

pingPendingIntent = PendingIntent.getBroadcast(MobSDK.getContext(), 0, new Intent(ALARM_ACTION_PING),

PendingIntent.FLAG_UPDATE_CURRENT);

final long nextTime = SystemClock.elapsedRealtime() + interval * 1000L;

if (Build.VERSION.SDK_INT >= 23) {

am.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pingPendingIntent);

} else if (Build.VERSION.SDK_INT >= 19) {

am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pingPendingIntent);

} else {

am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextTime, pingPendingIntent);

}

```

5. 程式間相互拉活

當某臺手機上有多個應用都在使用sdk時,可根據使用者在後臺配置授權後,選擇性的進行相互之間的拉活

6. 利用native程式拉活

網路中流傳的一種利用 Linux 中的 fork 機制建立 Native 程式,在 Native 程式中監控主程式的存活,當主程式掛掉後,在 Native 程式中立即對主程式進行拉活。

在 Android 中所有程式和系統元件的生命週期受 ActivityManagerService 的統一管理。而且,通過 Linux 的 fork 機制建立的程式為純 Linux 程式,其生命週期不受 Android 的管理。

這種方案在網上流傳已久,聽說在5.0以上版本也不成立,且需要額外新增原生程式碼編譯so,無形的新增了app體積,不採納

7. JobScheduler和賬號同步機制拉活

這種兩種方式同樣需要在AndroidManifest.xml中註冊相關配置和許可權,版本限制,效果一般

8. 將應用加入廠商或管理軟體白名單

9. 第三方push通道接入:

GSM:國內不支援

小米推送、華為推送

效能考慮

APP效能也是老生常談的話題,總結其出發點和最終的目的都是為了減少使用者流量、記憶體佔用、電量消耗等等方面的優化,以達到省電省流量且介面流暢的終極目標。

在開發時,大致可從如下幾個方面思考和稍加註意:

1. 減少網路請求次數,縮小網路中傳輸資料的體積,像推送這種主動的操作,可通過自定義資料傳輸協議來控制流量的消耗

2. 控制喚醒螢幕,避免開啟沒必要的執行緒,合理釋放資源,減少IO操作,避免使用廣播機制,減少cup佔用時間等等方面來控制記憶體和電量的消耗


相關文章