而本文中咱們將講解一下App的長鏈接實現。通常而言長鏈接已是App的標配了,推送功能的實現基礎就是長鏈接,固然了咱們也能夠經過輪訓操做實現推送功能,可是輪訓通常及時性比較差,並且網絡消耗與電量銷燬比較多,所以通常推送功能都是經過長鏈接實現的。java
那麼如何實現長鏈接呢?如今通常有這麼幾種實現方式:服務器
使用第三方的長鏈接服務;網絡
經過NIO等方案實現長鏈接服務;session
經過MINA等第三方框架實現長鏈接;框架
介紹:這是最簡單的方式,咱們能夠經過接入極光推送,百度推送,友盟等第三方服務實現長鏈接,經過接入第三方的API咱們能夠很方便的接入第三方的長鏈接,推送服務,可是這種方式定製化程度不太好,若是對長鏈接服務不是要求特別高,對定製化要求不是很高的話基本能夠考慮這種方式(目前主流的App都是使用第三方的長鏈接服務)
優點:簡單,方便
劣勢:定製化程度不高ide
介紹:經過NIO的方式實現長鏈接,這種方式對技術要求程度比較高,基本都是經過java API實現長鏈接,實現心跳包,實現異常狀況的容錯等操做,能夠說經過NIO實現長鏈接對技術要求很高,通常若是沒有成行的技術方案比建議這麼作,就算實現了長鏈接,後期鏈接的維護,對電量,流量的損耗等都須要持續的優化。
優點:定製化比較高
劣勢:技術要求高,須要持續的維護oop
介紹:MINA是一個第三方的NIO框架,該框架實現了一整套的長鏈接機制,包括長鏈接的創建,心跳包的實現,異常機制的容錯等。使用MINA實現長鏈接能夠定製化的實現一些特有的功能,而且比NIO方案較爲簡單,由於其已經封裝了一些長鏈接的特有機制,好比心跳包,容錯等。
優點:可定製,較NIO方法簡單
劣勢:也須要必定的技術儲備優化
在咱們的Android客戶端中長鏈接的實現機制採用–MINA方式。這裏多說一句,一開始的長鏈接採用的是NIO方案,可是採用這種方案以後踩了不少坑,包括心跳,容錯等機制都是本身寫的,因此耗費了大量的時間,並且對手機電量的消耗很大,最後決定使用MINA NIO框架從新實現一遍長鏈接,後來通過實測,長鏈接的穩定性還有耗電量,流量的消耗等指標方面有了很大的提升。ui
下面我將簡單的介紹一下經過NIO實現長鏈接的具體流程:this
引入MINA jar包,在App啓動頁面,登陸頁面啓動長鏈接;
建立後臺服務,在服務中建立MINA長鏈接;
實現心跳包,重寫一些容錯機制;
實現長鏈接斷了以後的重連機制,而且重連次數有限制不能一直重連;
長鏈接斷了以後實現輪訓操做,這裏的輪訓服務只有在長鏈接斷了以後才啓動,在長鏈接恢復以後關閉;
如下就是在長鏈接中實現的具體代碼:
/**
* 在Application的onCreate方法中執行啓動長鏈接的操做
**/
@Override
public void onCreate() {
... // 登陸後開啓長鏈接 if (UserConfig.isPassLogined()) { L.i("用戶已登陸,開啓長鏈接..."); startLongConn(); } ... }
/** * 開始執行啓動長鏈接服務 */ 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); } } } }