1. 程式人生 > >WebSocket安卓客戶端實現詳解(一)–連接建立與重連

WebSocket安卓客戶端實現詳解(一)–連接建立與重連

ask 應該 header oid mha 主動推送 未收到 compile tde

http://blog.csdn.net/zly921112/article/details/72973054

前言


這裏特別說明下因為WebSocket服務端是公司線上項目所以這裏url和具體協議我全部抹去了,但我會盡力給大家講明白並且demo我都是測試過,還望各位看官見諒

我們先粗獷的講下流程,掌握個大概的方向,然後在深入講解細節的實現.這裏先解答一個疑惑,為啥我們這要用WebSocket而不是Socket呢,因為WebSocket是一個應用層協議很多東西都規定好了我們直接按他的規定來用就好,而Socket是傳輸層和應用層的一個抽象層很多東西我們還得自己規定相對來說會比較麻煩,所以這裏我們用的WebSocket.

既然WebSocket是一個應用層協議,我們肯定不可能自己去實現,所以第一步是需要找一個實現了該協議的框架,這裏我用的nv-websocket-client,api我就不介紹了,庫中readme已經詳細的介紹了,後面我就直接使用了.

關於通訊協議為了方便,這裏我們使用的是json.

接下來我們先簡單描述下我們將要做的事情

用戶登錄流程


技術分享

第一步用戶輸入賬號密碼登錄成功後,我們將會通過websocket協議建立連接,當連接失敗回調的時候我們嘗試重連,直到連接成功,當然這個嘗試重連的時間間隔我是根據重連失敗次數按一定規則寫的具體後面再說.

第二步當連接建立成功後,我們需要在後臺通過長連接發送請求驗證該用戶的身份也就是上圖的授權,既然前面用戶登錄都成功了一般情況下授權是不會失敗的,所以這裏對於授權失敗並未處理,授權成功後我們開啟心跳,並且發送同步數據請求到服務端獲取還未收到的消息.

客戶端發送請求流程


技術分享

第一步將請求參數封裝成請求對象,然後添加超時任務並且將該請求的回調添加到回調集合.

這裏有點需要說明下,封裝請求參數的時候這裏額外添加了兩個參數seqId和reqCount,這裏我們是通過長連接請求當服務端響應的時候為了能夠找到對應的回調,所以每個請求我們都需要傳給服務端一個唯一標識來標識該請求,這裏我用的seqId,請求成功後服務端再把seqId回傳,我們再通過這個seqId作為key從回調集合中找到對應的回調.而reqCount的話主要針對請求超時的情況,如果請求超時,第二次請求的時候就把reqCount++在放入request中,我們約定同一個請求次數大於三次時候走http補償通道,那麽當request中的reqCount>3的時候我們就通過http發送該請求,然後根據響應回調對應結果.

第二步開始請求,成功或者失敗的話通過seqId找到對應回調執行並從回調集合中移除該回調,然後取消超時任務.如果超時的話根據seqId拿到對應的回調並從回調集合中移除該回調,然後判斷請求次數如果小於等於3次再次通過websocket嘗試請求,如果大於3次通過http請求,根據請求成功失敗情況執行對應回調.

服務端主動推送消息流程


技術分享

先說明下這裏服務端推送的消息僅僅是個事件,不攜帶具體消息.

第一步根據notify中事件類型找到對應的處理類,一般情況下這裏需要同步對應數據.

第二步然後用eventbus通知對應的ui界面更新

第三步如果需要ack,發送ack請求

上面只是一個概括,對於心跳,重連,發送請求這裏有不少細節需要註意的下一節我們將詳細講解

具體實現

理論說完了,接下來我們將一步步實現客戶端代碼.首先我們添加依賴

    compile ‘com.neovisionaries:nv-websocket-client:2.2‘
  • 1
  • 1

然後創建一個單利的WsManager管理websocket供全局調用,

public class WsManager {

    private static WsManager mInstance;

    private WsManager() {
    }

    public static WsManager getInstance(){
        if(mInstance == null){
            synchronized (WsManager.class){
                if(mInstance == null){
                    mInstance = new WsManager();
                }
            }
        }
        return mInstance;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

建立連接


然後添加建立連接代碼,這裏關於WebSocket協議的操作用的都是nv-websocket-client,我也加上了詳細的註釋,實在不理解可以去讀一遍readme文件.

public class WsManager {
    private static WsManager mInstance;
    private final String TAG = this.getClass().getSimpleName();

    /**
     * WebSocket config
     */
    private static final int FRAME_QUEUE_SIZE = 5;
    private static final int CONNECT_TIMEOUT = 5000;
    private static final String DEF_TEST_URL = "測試服地址";//測試服默認地址
    private static final String DEF_RELEASE_URL = "正式服地址";//正式服默認地址
    private static final String DEF_URL = BuildConfig.DEBUG ? DEF_TEST_URL : DEF_RELEASE_URL;
    private String url;

    private WsStatus mStatus;
    private WebSocket ws;
    private WsListener mListener;

    private WsManager() {
    }

    public static WsManager getInstance(){
        if(mInstance == null){
            synchronized (WsManager.class){
                if(mInstance == null){
                    mInstance = new WsManager();
                }
            }
        }
        return mInstance;
    }

    public void init(){
        try {
          /**
           * configUrl其實是緩存在本地的連接地址
           * 這個緩存本地連接地址是app啟動的時候通過http請求去服務端獲取的,
           * 每次app啟動的時候會拿當前時間與緩存時間比較,超過6小時就再次去服務端獲取新的連接地址更新本地緩存
           */
            String configUrl = "";
            url = TextUtils.isEmpty(configUrl) ? DEF_URL : configUrl;
            ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                .setFrameQueueSize(FRAME_QUEUE_SIZE)//設置幀隊列最大值為5
                .setMissingCloseFrameAllowed(false)//設置不允許服務端關閉連接卻未發送關閉幀
                .addListener(mListener = new WsListener())//添加回調監聽
                .connectAsynchronously();//異步連接
            setStatus(WsStatus.CONNECTING);
            Logger.t(TAG).d("第一次連接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 繼承默認的監聽空實現WebSocketAdapter,重寫我們需要的方法
     * onTextMessage 收到文字信息
     * onConnected 連接成功
     * onConnectError 連接失敗
     * onDisconnected 連接關閉
     */
    class WsListener extends WebSocketAdapter{
        @Override
        public void onTextMessage(WebSocket websocket, String text) throws Exception {
            super.onTextMessage(websocket, text);
            Logger.t(TAG).d(text);
        }


        @Override
        public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
            throws Exception {
            super.onConnected(websocket, headers);
            Logger.t(TAG).d("連接成功");
            setStatus(WsStatus.CONNECT_SUCCESS);
        }


        @Override
        public void onConnectError(WebSocket websocket, WebSocketException exception)
            throws Exception {
            super.onConnectError(websocket, exception);
            Logger.t(TAG).d("連接錯誤");
            setStatus(WsStatus.CONNECT_FAIL);
        }


        @Override
        public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer)
            throws Exception {
            super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
            Logger.t(TAG).d("斷開連接");
            setStatus(WsStatus.CONNECT_FAIL);
        }
    }

    private void setStatus(WsStatus status){
        this.mStatus = status;
    }

    private WsStatus getStatus(){
        return mStatus;
    }

    public void disconnect(){
        if(ws != null)
        ws.disconnect();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
public enum WsStatus {
    CONNECT_SUCCESS,//連接成功
    CONNECT_FAIL,//連接失敗
    CONNECTING;//正在連接
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

從註釋我們可以知道,這裏我們是app啟動的時候通過http請求獲取WebSocket連接地址,如果獲取失敗就走本地默認的url建立連接.並且內部自己維護了一個websocket狀態後面發送請求和重連的時候會用上.

其實獲取連接地址這個地方是可以優化的,就是app啟動的時候先比較上次獲取的時間如果大於6小時就通過http請求獲取websocket的連接地址,這個地址應該是個列表,然後存入本地,連接的時候我們可以先ping下地址,選擇耗時最短的地址接入.如果連不上我們在連耗時第二短的地址以此類推.但這裏我們就以簡單的方式做了.

至於建立連接代碼在哪調用的話,我選擇的是主界面onCreate()的時候,因為一般能進入主界面了,就代表用戶已經登錄成功.

WsManager.getInstance().init();
  • 1
  • 1

斷開連接的話在主界面onDestroy()的時候調用

WsManager.getInstance().disconnect();
  • 1
  • 1

重連


建立連接有成功就有失敗,對於失敗情況我們需要重連,那麽下面我們分別說明重連的時機,重連的策略和當前是否應該重連的判斷.

對於重連的時機有如下幾種情況我們需要嘗試重連

  1. 應用網絡的切換.具體點就是可用網絡狀態的切換,比如4g切wifi連接會斷開我們需要重連.

  2. 應用回到前臺的時候,判斷如果連接斷開我們需要重連,這個是盡量保持當應用再前臺的時候連接的穩定.

  3. 收到連接失敗或者連接斷開事件的時候,這個沒什麽好解釋.

  4. 心跳連續3次失敗時候.當然這個連續失敗3次是自己定義的,大夥可以根據自己app的情況定制.

等會我們先展示前三種情況,心跳失敗這個在後面我們把客戶端發送請求講完再說.

上面把需要重連的情景說了,現在講講具體的重連策略.

這裏我定義了一個最小重連時間間隔min和一個最大重連時間間隔max,當重連次數小於等於3次的時候都以最小重連時間間隔min去嘗試重連,當重連次數大於3次的時候我們將重連地址替換成默認地址DEF_URL,將重連時間間隔按min*(重連次數-2)遞增最大不不超過max.

還有最後一個當前是否應該重連的判斷

  1. 用戶是否登錄,可以通過本地是否有緩存的用戶信息來判斷.因為重連成功後我們需要將用戶信息通過WebSocket發送到服務器進行身份驗證所以這裏必須登錄成功.

  2. 當前連接是否可用,這個通過nv-websocket-client庫中的api判斷ws.isOpen().

  3. 當前不是正在連接狀態,這裏我們根據自己維護的狀態來判斷getStatus() != WsStatus.CONNECTING.

  4. 當前網絡可用.

下面我們show code.跟之前相同的代碼這裏就省略了

public class WsManager {

    .....省略部分跟之前代碼一樣.....

    /**
     * 繼承默認的監聽空實現WebSocketAdapter,重寫我們需要的方法
     * onTextMessage 收到文字信息
     * onConnected 連接成功
     * onConnectError 連接失敗
     * onDisconnected 連接關閉
     */
    class WsListener extends WebSocketAdapter {
        @Override
        public void onTextMessage(WebSocket websocket, String text) throws Exception {
            super.onTextMessage(websocket, text);
            Logger.t(TAG).d(text);
        }


        @Override
        public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
            throws Exception {
            super.onConnected(websocket, headers);
            Logger.t(TAG).d("連接成功");
            setStatus(WsStatus.CONNECT_SUCCESS);
            cancelReconnect();//連接成功的時候取消重連,初始化連接次數
        }


        @Override
        public void onConnectError(WebSocket websocket, WebSocketException exception)
            throws Exception {
            super.onConnectError(websocket, exception);
            Logger.t(TAG).d("連接錯誤");
            setStatus(WsStatus.CONNECT_FAIL);
            reconnect();//連接錯誤的時候調用重連方法
        }


        @Override
        public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer)
            throws Exception {
            super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
            Logger.t(TAG).d("斷開連接");
            setStatus(WsStatus.CONNECT_FAIL);
            reconnect();//連接斷開的時候調用重連方法
        }
    }


    private void setStatus(WsStatus status) {
        this.mStatus = status;
    }


    private WsStatus getStatus() {
        return mStatus;
    }


    public void disconnect() {
        if (ws != null) {
            ws.disconnect();
        }
    }


    private Handler mHandler = new Handler();

    private int reconnectCount = 0;//重連次數
    private long minInterval = 3000;//重連最小時間間隔
    private long maxInterval = 60000;//重連最大時間間隔


    public void reconnect() {
        if (!isNetConnect()) {
            reconnectCount = 0;
            Logger.t(TAG).d("重連失敗網絡不可用");
            return;
        }

        //這裏其實應該還有個用戶是否登錄了的判斷 因為當連接成功後我們需要發送用戶信息到服務端進行校驗
        //由於我們這裏是個demo所以省略了
        if (ws != null &&
            !ws.isOpen() &&//當前連接斷開了
            getStatus() != WsStatus.CONNECTING) {//不是正在重連狀態

            reconnectCount++;
            setStatus(WsStatus.CONNECTING);

            long reconnectTime = minInterval;
            if (reconnectCount > 3) {
                url = DEF_URL;
                long temp = minInterval * (reconnectCount - 2);
                reconnectTime = temp > maxInterval ? maxInterval : temp;
            }

            Logger.t(TAG).d("準備開始第%d次重連,重連間隔%d -- url:%s", reconnectCount, reconnectTime, url);
            mHandler.postDelayed(mReconnectTask, reconnectTime);
        }
    }


    private Runnable mReconnectTask = new Runnable() {

        @Override
        public void run() {
            try {
                ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                    .setFrameQueueSize(FRAME_QUEUE_SIZE)//設置幀隊列最大值為5
                    .setMissingCloseFrameAllowed(false)//設置不允許服務端關閉連接卻未發送關閉幀
                    .addListener(mListener = new WsListener())//添加回調監聽
                    .connectAsynchronously();//異步連接
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };


    private void cancelReconnect() {
        reconnectCount = 0;
        mHandler.removeCallbacks(mReconnectTask);
    }


    private boolean isNetConnect() {
        ConnectivityManager connectivity = (ConnectivityManager) WsApplication.getContext()
            .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity != null) {
            NetworkInfo info = connectivity.getActiveNetworkInfo();
            if (info != null && info.isConnected()) {
                // 當前網絡是連接的
                if (info.getState() == NetworkInfo.State.CONNECTED) {
                    // 當前所連接的網絡可用
                    return true;
                }
            }
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142

上面代碼通過handler實現了一定時間間隔的重連,然後我們在WsListener監聽中的onConnectError()onDisconnected()調用了reconnect()實現重連,onConnected()中調用了cancelReconnect()取消重連並初始化重連次數.

所以當需要重連的時候我們調用reconnect()方法,如果失敗onConnectError()onDisconnected()回調會再次調用reconnect()實現重連,如果成功onConnected()中會調用cancelReconnect()取消重連並初始化重連次數.

並且這裏我們已經實現了需要重連的情景3,收到連接失敗或者連接斷開事件的時候進行重連.

接下來我們實現情景1和2

  1. 應用網絡的切換.具體點就是可用網絡狀態的切換,比如4g切wifi連接會斷開我們需要重連.

  2. 應用回到前臺的時候,判斷如果連接斷開我們需要重連,這個是盡量保持當應用再前臺的時候連接的穩定.

對於可用網絡的切換這裏通過廣播來監聽實現重連

public class NetStatusReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {

            // 獲取網絡連接管理器
            ConnectivityManager connectivityManager
                = (ConnectivityManager) WsApplication.getContext()
                .getSystemService(Context.CONNECTIVITY_SERVICE);
            // 獲取當前網絡狀態信息
            NetworkInfo info = connectivityManager.getActiveNetworkInfo();

            if (info != null && info.isAvailable()) {
                Logger.t("WsManager").d("監聽到可用網絡切換,調用重連方法");
                WsManager.getInstance().reconnect();//wify 4g切換重連websocket
            }

        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

應用回到前臺情況的重連.

通過Application.ActivityLifecycleCallbacks實現app前後臺切換監聽如下

public class ForegroundCallbacks implements Application.ActivityLifecycleCallbacks {

    public static final long CHECK_DELAY = 600;
    public static final String TAG = ForegroundCallbacks.class.getName();
    private static ForegroundCallbacks instance;
    private boolean foreground = false, paused = true;
    private Handler handler = new Handler();
    private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
    private Runnable check;

    public static ForegroundCallbacks init(Application application) {
        if (instance == null) {
            instance = new ForegroundCallbacks();
            application.registerActivityLifecycleCallbacks(instance);
        }
        return instance;
    }

    public static ForegroundCallbacks get(Application application) {
        if (instance == null) {
            init(application);
        }
        return instance;
    }

    public static ForegroundCallbacks get(Context ctx) {
        if (instance == null) {
            Context appCtx = ctx.getApplicationContext();
            if (appCtx instanceof Application) {
                init((Application) appCtx);
            }
            throw new IllegalStateException(
                    "Foreground is not initialised and " +
                            "cannot obtain the Application object");
        }
        return instance;
    }

    public static ForegroundCallbacks get() {

        return instance;
    }

    public boolean isForeground() {
        return foreground;
    }

    public boolean isBackground() {
        return !foreground;
    }

    public void addListener(Listener listener) {
        listeners.add(listener);
    }

    public void removeListener(Listener listener) {
        listeners.remove(listener);
    }

    @Override
    public void onActivityResumed(Activity activity) {
        paused = false;
        boolean wasBackground = !foreground;
        foreground = true;
        if (check != null)
            handler.removeCallbacks(check);
        if (wasBackground) {

            for (Listener l : listeners) {
                try {
                    l.onBecameForeground();
                } catch (Exception exc) {

                }
            }
        } else {

        }
    }

    @Override
    public void onActivityPaused(Activity activity) {
        paused = true;

        if (check != null)
            handler.removeCallbacks(check);
        handler.postDelayed(check = new Runnable() {
            @Override
            public void run() {
                if (foreground && paused) {
                    foreground = false;
                    for (Listener l : listeners) {
                        try {
                            l.onBecameBackground();
                        } catch (Exception exc) {

                        }
                    }
                } else {

                }
            }
        }, CHECK_DELAY);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }

    public interface Listener {
        public void onBecameForeground();

        public void onBecameBackground();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131

然後在application中初始化該監聽,當應用回到前臺的時候嘗試重連

public class WsApplication extends Application {


    @Override
    public void onCreate() {
        super.onCreate();
        initAppStatusListener();
    }

    private void initAppStatusListener() {
        ForegroundCallbacks.init(this).addListener(new ForegroundCallbacks.Listener() {
            @Override
            public void onBecameForeground() {
                Logger.t("WsManager").d("應用回到前臺調用重連方法");
                WsManager.getInstance().reconnect();
            }

            @Override
            public void onBecameBackground() {

            }
        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

到這裏連接的建立和重連講完了,還剩客戶端發送請求和服務端主動通知消息.

本來我準備一篇把WebSocket客戶端實現寫完的,現在才一半就已經這麽多了,索性分為幾篇算了,下篇我們將介紹 WebSocket安卓客戶端實現詳解(二)–客戶端發送請求.

這裏附上本篇的源碼
WebSocket安卓客戶端實現詳解(一)–連接建立與重連源碼傳送門

WebSocket安卓客戶端實現詳解(一)–連接建立與重連