上一篇文章中我們講解了Android應用內頁面跳轉協議-scheme協議,通過該協議我們可以跳轉至指定的Activity,并在該Activity中解析scheme用于跳轉到指定的頁面,我們可以利用scheme協議實現應用內頁面跳轉、H5頁面與Native頁面相互跳轉、通知欄消息跳轉相應頁面等,具體可參考: [Android產品研發(十一)–>使用scheme實現頁面跳轉](http://blog.csdn.net/qq_23547831/article/details/51685310)。
而本文中我們將講解一下App的長連接實現。一般而言長連接已經是App的標配了,推送功能的實現基礎就是長連接,當然了我們也可以通過輪訓操作實現推送功能,但是輪訓一般及時性比較差,而且網絡消耗與電量銷毀比較多,因此一般推送功能都是通過長連接實現的。
那么如何實現長連接呢?現在一般有這么幾種實現方式:
* 使用第三方的長連接服務;
* 通過NIO等方案實現長連接服務;
* 通過MINA等第三方框架實現長連接;
**幾種長連接服務的具體實現,以及各自的優缺點。**
1. 使用第三方的長連接服務
介紹:這是最簡單的方式,我們可以通過接入極光推送,百度推送,友盟等第三方服務實現長連接,通過接入第三方的API我們可以很方便的接入第三方的長連接,推送服務,但是這種方式定制化程度不太好,如果對長連接服務不是要求特別高,對定制化要求不是很高的話基本可以考慮這種方式(目前主流的App都是使用第三方的長連接服務)
優勢:簡單,方便
劣勢:定制化程度不高
2. 使用NIO等方案實現長連接服務
介紹:通過NIO的方式實現長連接,這種方式對技術要求程度比較高,基本都是通過java API實現長連接,實現心跳包,實現異常情況的容錯等操作,可以說通過NIO實現長連接對技術要求很高,一般如果沒有成行的技術方案比建議這么做,就算實現了長連接,后期連接的維護,對電量,流量的損耗等都需要持續的優化。
優勢:定制化比較高
劣勢:技術要求高,需要持續的維護
3. 使用MINA等第三方框架實現長連接
介紹:MINA是一個第三方的NIO框架,該框架實現了一整套的長連接機制,包括長連接的建立,心跳包的實現,異常機制的容錯等。使用MINA實現長連接可以定制化的實現一些特有的功能,并且比NIO方案較為簡單,因為其已經封裝了一些長連接的特有機制,比如心跳包,容錯等。
優勢:可定制,較NIO方法簡單
劣勢:也需要一定的技術儲備
**長連接具體實現**
在我們的Android客戶端中長連接的實現機制采用–MINA方式。這里多說一句,一開始的長連接采用的是NIO方案,但是采用這種方案之后踩了很多坑,包括心跳,容錯等機制都是自己寫的,所以耗費了大量的時間,而且對手機電量的消耗很大,最后決定使用MINA NIO框架重新實現一遍長連接,后來經過實測,長連接的穩定性還有耗電量,流量的消耗等指標方面有了很大的提高。
下面我將簡單的介紹一下通過NIO實現長連接的具體流程:
* 引入MINA jar包,在App啟動頁面,登錄頁面啟動長連接;
* 創建后臺服務,在服務中創建MINA長連接;
* 實現心跳包,重寫一些容錯機制;
* 實現長連接斷了之后的重連機制,并且重連次數有限制不能一直重連;
* 長連接斷了之后實現輪訓操作,這里的輪訓服務只有在長連接斷了之后才啟動,在長連接恢復之后關閉;
以下就是在長連接中實現的具體代碼:
- 在Application的onCreate方法中檢測App是否登錄,若登錄的話啟動長連接
~~~
/**
* 在Application的onCreate方法中執行啟動長連接的操作
**/
@Override
public void onCreate() {
...
// 登錄后開啟長連接
if (UserConfig.isPassLogined()) {
L.i("用戶已登錄,開啟長連接...");
startLongConn();
}
...
}
~~~
* 通過鬧鐘服務實現具體的啟動長連接service的操作,即每隔60秒鐘判斷長連接是否啟動,若未啟動則實現啟動操作
~~~
/**
* 開始執行啟動長連接服務
*/
public void startLongConn() {
quitLongConn();
L.i("長連接服務已開啟");
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, LongConnService.class);
intent.setAction(LongConnService.ACTION);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
long triggerAtTime = SystemClock.elapsedRealtime();
manager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime, 60 * 1000, pendingIntent);
}
~~~
* 下面的代碼就是長連接服務的具體實現
~~~
/**
* 后臺長連接服務
**/
public class LongConnService extends Service {
public static String ACTION = "com.youyou.uuelectric.renter.Service.LongConnService";
private static MinaLongConnectManager minaLongConnectManager;
public String tag = "LongConnService";
private Context context;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
context = getApplicationContext();
// 執行啟動長連接的操作
startLongConnect();
ObserverManager.addObserver("LongConnService", stopListener);
return START_STICKY;
}
public ObserverListener stopListener = new ObserverListener() {
@Override
public void observer(String from, Object obj) {
closeConnect();
}
};
@Override
public void onDestroy() {
super.onDestroy();
closeConnect();
}
/**
* 開始執行啟動長連接的操作
*/
private void startLongConnect() {
if (Config.isNetworkConnected(context)) {
if (minaLongConnectManager != null && minaLongConnectManager.checkConnectStatus()) {
L.i("長連接狀態正常...");
return;
}
if (minaLongConnectManager == null) {
startThreadCreateConnect();
} else {
if (minaLongConnectManager.connectIsNull() && minaLongConnectManager.isNeedRestart()) {
L.i("session已關閉,需要重新創建一個session");
minaLongConnectManager.startConnect();
} else {
L.i("長連接已關閉,需要重開一個線程來重新創建長連接");
startThreadCreateConnect();
}
}
}
}
private final AtomicInteger mCount = new AtomicInteger(1);
private void startThreadCreateConnect() {
if (UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null) {
System.gc();
new Thread(new Runnable() {
@Override
public void run() {
// 執行具體啟動長連接操作
minaLongConnectManager = MinaLongConnectManager.getInstance(context);
minaLongConnectManager.crateLongConnect();
}
}, "longConnectThread" + mCount.getAndIncrement()).start();
}
}
private void closeConnect() {
if (minaLongConnectManager != null) {
minaLongConnectManager.closeConnect();
}
minaLongConnectManager = null;
// 停止長連接服務LongConnService
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
~~~
* 而下面的代碼就是長連接的具體實現操作,具體的代碼有相關注釋說明
~~~
/**
* 具體實現長連接的管理對象
**/
public class MinaLongConnectManager {
private static final String TAG = MinaLongConnectManager.class.getSimpleName();
/**
* 服務器端口號
*/
public static final int DEFAULT_PORT = 18156;
/**
* 連接超時時間,30 seconds
*/
public static final long SOCKET_CONNECT_TIMEOUT = 30 * 1000L;
/**
* 長連接心跳包發送頻率,60s
*/
public static final int KEEP_ALIVE_TIME_INTERVAL = 60;
private static Context context;
private static MinaLongConnectManager minaLongConnectManager;
private static NioSocketConnector connector;
private static ConnectFuture connectFuture;
public static IoSession session;
private static ExecutorService executorService = Executors.newSingleThreadExecutor();
/**
* 長連接是否正在連接中...
*/
private static boolean isConnecting = false;
private MinaLongConnectManager() {
EventBus.getDefault().register(this);
}
public static synchronized MinaLongConnectManager getInstance(Context ctx) {
if (minaLongConnectManager == null) {
context = ctx;
minaLongConnectManager = new MinaLongConnectManager();
}
return minaLongConnectManager;
}
/**
* 檢查長連接的各種對象狀態是否正常,正常情況下無需再創建
*
* @return
*/
public boolean checkConnectStatus() {
if (connector != null && connector.isActive() && connectFuture != null && connectFuture.isConnected() && session != null && session.isConnected()) {
return true;
} else {
return false;
}
}
public boolean connectIsNull() {
return connector != null;
}
/**
* 創建長連接,配置過濾器鏈和心跳工廠
*/
public synchronized void crateLongConnect() {
// 如果是長連接正在創建中
if (isConnecting) {
L.i("長連接正在創建中...");
return;
}
if (!Config.isNetworkConnected(context)) {
L.i("檢測到網絡未打開,無法正常啟動長連接,直接return...");
return;
}
// 檢查長連接的各種對象狀態是否正常,正常情況下無需再創建
if (checkConnectStatus()) {
return;
}
isConnecting = true;
try {
connector = new NioSocketConnector();
connector.setConnectTimeoutMillis(SOCKET_CONNECT_TIMEOUT);
if (L.isDebug) {
if (!connector.getFilterChain().contains("logger")) {
// 設置日志輸出工廠
connector.getFilterChain().addLast("logger", new LoggingFilter());
}
}
if (!connector.getFilterChain().contains("codec")) {
// 設置請求和響應對象的編解碼操作
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new LongConnectProtocolFactory()));
}
// 創建心跳工廠
ClientKeepAliveMessageFactory heartBeatFactory = new ClientKeepAliveMessageFactory();
// 當讀操作空閑時發送心跳
KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.READER_IDLE);
// 設置是否將事件繼續往下傳遞
heartBeat.setForwardEvent(true);
// 設置心跳包請求后超時無反饋情況下的處理機制,默認為關閉連接,在此處設置為輸出日志提醒
heartBeat.setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler.LOG);
//設置心跳頻率
heartBeat.setRequestInterval(KEEP_ALIVE_TIME_INTERVAL);
if (!connector.getFilterChain().contains("keepAlive")) {
connector.getFilterChain().addLast("keepAlive", heartBeat);
}
if (!connector.getFilterChain().contains("reconnect")) {
// 設置長連接重連過濾器,當檢測到Session(會話)斷開后,重連長連接
connector.getFilterChain().addLast("reconnect", new LongConnectReconnectionFilter());
}
// 設置接收和發送緩沖區大小
connector.getSessionConfig().setReceiveBufferSize(1024);
connector.getSessionConfig().setSendBufferSize(1024);
// 設置讀取空閑時間:單位為s
connector.getSessionConfig().setReaderIdleTime(60);
// 設置長連接業務邏輯處理類Handler
LongConnectHandler longConnectHandler = new LongConnectHandler(this, context);
connector.setHandler(longConnectHandler);
} catch (Exception e) {
e.printStackTrace();
closeConnect();
}
startConnect();
}
/**
* 開始或重連長連接
*/
public synchronized void startConnect() {
if (connector != null) {
L.i("開始創建長連接...");
boolean isSuccess = beginConnect();
// 創建成功后,修改創建中狀態
if (isSuccess) {
isNeedRestart = false;
if (context != null) {
// 長連接啟動成功后,主動拉取一次消息
LoopRequest.getInstance(context).sendLoopRequest();
}
} else {
// 啟動輪詢服務
startLoopService();
}
isConnecting = false;
// printProcessorExecutor();
} else {
L.i("connector已為null,不能執行創建連接動作...");
}
}
/**
* 檢測MINA中線程池的活動狀態
*/
private void printProcessorExecutor() {
Class connectorClass = connector.getClass().getSuperclass();
try {
L.i("connectorClass:" + connectorClass.getCanonicalName());
Field field = connectorClass.getDeclaredField("processor");
field.setAccessible(true);
Object connectorObject = field.get(connector);
if (connectorObject != null) {
SimpleIoProcessorPool processorPool = (SimpleIoProcessorPool) connectorObject;
Class processPoolClass = processorPool.getClass();
Field executorField = processPoolClass.getDeclaredField("executor");
executorField.setAccessible(true);
Object executorObject = executorField.get(processorPool);
if (executorObject != null) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorObject;
L.i("線程池中當前線程數:" + threadPoolExecutor.getPoolSize() + "\t 核心線程數:" + threadPoolExecutor.getCorePoolSize() + "\t 最大線程數:" + threadPoolExecutor.getMaximumPoolSize());
}
} else {
L.i("connectorObject = null");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 開始創建Session
*
* @return
*/
public boolean beginConnect() {
if (session != null) {
session.close(false);
session = null;
}
if (connectFuture != null && connectFuture.isConnected()) {
connectFuture.cancel();
connectFuture = null;
}
FutureTask<Boolean> futureTask = new FutureTask<>(new Callable<Boolean>() {
@Override
public Boolean call() {
try {
InetSocketAddress address = new InetSocketAddress(NetworkTask.getBASEURL(), DEFAULT_PORT);
connectFuture = connector.connect(address);
connectFuture.awaitUninterruptibly(3000L);
session = connectFuture.getSession();
if (session == null) {
L.i(TAG + "連接創建失敗...當前環境:" + NetworkTask.getBASEURL());
return false;
} else {
L.i(TAG + "長連接已啟動,連接已成功...當前環境:" + NetworkTask.getBASEURL());
return true;
}
} catch (Exception e) {
return false;
}
}
});
executorService.submit(futureTask);
try {
return futureTask.get();
} catch (Exception e) {
return false;
}
}
/**
* 關閉連接,根據傳入的參數設置session是否需要重新連接
*/
public synchronized void closeConnect() {
if (session != null) {
session.close(false);
session = null;
}
if (connectFuture != null && connectFuture.isConnected()) {
connectFuture.cancel();
connectFuture = null;
}
if (connector != null && !connector.isDisposed()) {
// 清空里面注冊的所以過濾器
connector.getFilterChain().clear();
connector.dispose();
connector = null;
}
isConnecting = false;
L.i("長連接已關閉...");
}
private volatile boolean isNeedRestart = false;
public boolean isNeedRestart() {
return isNeedRestart;
}
public void onEventMainThread(BaseEvent event) {
if (event == null || TextUtils.isEmpty(event.getType()))
return;
if (EventBusConstant.EVENT_TYPE_NETWORK_STATUS.equals(event.getType())) {
String status = (String) event.getExtraData();
// 當網絡狀態變化的時候請求startQuery接口
if (status != null && status.equals("open")) {
if (isNeedRestart && UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null) {
L.i("檢測到網絡已打開且長連接處于關閉狀態,需要啟動長連接...");
Intent intent = new Intent(context, LongConnService.class);
intent.setAction(LongConnService.ACTION);
context.startService(intent);
}
}
}
}
/**
* 出現異常、session關閉后,接收事件進行長連接重連操作
*/
public void onEventMainThread(LongConnectMessageEvent event) {
if (event.getType() == LongConnectMessageEvent.TYPE_RESTART) {
long currentTime = System.currentTimeMillis();
// 票據有效的情況下進行重連長連接操作
if (UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null
&& ((currentTime / 1000) < UserConfig.getUserInfo().getUnvalidSecs())) {
// 等待2s后重新創建長連接
SystemClock.sleep(1000);
if (Config.isNetworkConnected(context)) {
L.i("出現異常情況,需要自動重連長連接...");
startConnect();
} else {
isNeedRestart = true;
L.i("長連接出現異常,需要重新創建session會話...");
}
}
} else if (event.getType() == LongConnectMessageEvent.TYPE_CLOSE) {
L.i("收到session多次close的消息,此時需要關閉長連接,等待下次鬧鐘服務來啟動...");
closeConnect();
}
}
/**
* 啟動輪詢服務
*/
public void startLoopService() {
// 啟動輪詢服務
// 暫時不考慮加入網絡情況的判斷...
if (!LoopService.isServiceRuning) {
// 用戶是登錄態,啟動輪詢服務
if (UserConfig.isPassLogined()) {
// 判斷當前長連接的狀態,若長連接已連接,則不再開啟輪詢服務
if (MinaLongConnectManager.session != null && MinaLongConnectManager.session.isConnected()) {
LoopService.quitLoopService(context);
return;
}
LoopService.startLoopService(context);
} else {
LoopService.quitLoopService(context);
}
}
}
}
~~~
以上是通過NIO實現App長連接的部分核心代碼,NIO中其實已經實現了長連接的核心流程,我們需要做的就是按照其流程實現長連接,需要注意的是要處理好異常情況,重連機制等。
當長連接創建成功之后需要重新拉取一次服務器端的長連接消息,并且這里的長連接做了容錯處理,當長連接斷了之后需要有重連機制,一直啟動輪訓服務,當長連接修復之后輪訓服務退出。以上只是通過MINA框架實現的長連接操作的核心流程,還有一些長連接實現的操作細節這里就不做過多的說明。
**總結**:
基本上對于App來說長連接已經是標配了,產品開發人員可以根據具體的產品需求選擇不同的實現方式,一般而言使用第三方的推送服務已經可以滿足大部分的需求了,當然了若是相對技術有所追求的話也可以選擇自己實現一套長連接服務,不過其中可能存在一些坑需要填,希望這里的長連接實現能夠對大家對長連接實現上有所幫助。
* 可以通過使用第三方長連接服務或者是自己實現連接的方式;
* 自定義實現長連接可以通過使用NIO或者是第三方NIO框架,比如MINA實現;
* 長連接實現中通過心跳包的機制實現App與服務器的長時間連接;
* 可以通過鬧鐘的機制定時檢測長連接服務是否可靠,長連接是否出現異常等;
* 為了消息的及時性,在長連接出現異常情況時可通過創建輪訓服務的機制實現對消息的獲取,待長連接恢復之后關閉輪訓服務;
另外對產品研發技術,技巧,實踐方面感興趣的同學可以參考我的:
[android產品研發(一)-->實用開發規范 ](http://blog.csdn.net/qq_23547831/article/details/51534013)
[android產品研發(二)-->啟動頁優化 ](http://blog.csdn.net/qq_23547831/article/details/51541277)
[android產品研發(三)-->基類Activity ](http://blog.csdn.net/qq_23547831/article/details/51546974)
[android產品研發(四)-->減小Apk大小](http://blog.csdn.net/qq_23547831/article/details/51559066)
[android產品研發(五)-->多渠道打包](http://blog.csdn.net/qq_23547831/article/details/51569261)
[Android產品研發(六)–>Apk混淆](http://blog.csdn.net/qq_23547831/article/details/51581491)
[android產品研發(七)-->Apk熱修復](http://blog.csdn.net/qq_23547831/article/details/51587927)
[Android產品研發(八)–>App數據統計](http://blog.csdn.net/qq_23547831/article/details/51612429)
[Android產品研發(九)–>App網絡傳輸協議](http://blog.csdn.net/qq_23547831/article/details/51655330)
[Android產品研發(十)–>不使用靜態變量保存數據](http://blog.csdn.net/qq_23547831/article/details/51685310)
[Android產品研發(十一)–>應用內跳轉scheme協議](http://blog.csdn.net/qq_23547831/article/details/51685310)
- 前言
- (一)–>實用開發規范
- (二)-->啟動頁優化
- (三)-->基類Activity
- (四)-->減小Apk大小
- (五)-->多渠道打包
- (六)-->Apk混淆
- (七)-->Apk熱修復
- (八)-->App數據統計
- (九)-->App網絡數據解析
- (十)-->盡量不使用靜態變量保存數據
- (十一)-->應用內跳轉Scheme協議
- (十二)-->App長連接實現
- (十三)-->App輪詢操作
- (十四)-->App升級與更新
- (十五)-->內存對象序列化
- (十六)-->開發者選項
- (十七)-->Hybrid開發
- (十八)-->webview問題集錦
- (十九)-->Android studio中的單元測試
- (二十)-->代碼Review
- (二十一)-->Android中的UI優化
- (二十二)-->Android實用調試技巧
- (二十三)-->Android中保存靜態秘鑰實踐
- (二十四)-->內存泄露場景與檢測
- (二十五)-->MVC/MVVM/MVP簡單理解