1. 程式人生 > >WebSocket安卓客戶端實現及程式碼封裝

WebSocket安卓客戶端實現及程式碼封裝

640?wx_fmt=png

今日科技快訊

B站App在多家應用商店已被下架。近日央視曾點名批評B站含有內容低俗的動漫作品,央視表示,在B站的熱播的一些動漫作品中存在穿著暴露的少女,曖昧的語言和動作,甚至涉及“兄妹戀”等亂倫內容。

作者簡介

明天就是週六啦,提前祝大家週末愉快!

本篇來自 張可 的投稿,分享了自己封裝WebSocket的開源庫,希望對大家有所幫助!

張可 的部落格地址:

https://blog.csdn.net/u013872857

介紹

關於 WebSocket Android 端的使用封裝之前已經做過一次了,但在使用了一段時間之後逐漸發現了一些問題,一直想改也沒時間,正好最近公司業務比較少,就趁著這段時間有空閒把程式碼優化了一下,其實差不多是重新做一套了。

這個版本的使用方式上比之前簡化了很多,整合起來也更容易,並且程式碼邏輯更加清晰,模組與模組之間的耦合降到最低,執行效率更高,更健壯,好了廢話不說了,先介紹一下使用方式。

如何使用

先放上 Github 地址:

https://github.com/0xZhangKe/WebSocketDemo

好了,首先將程式碼整合到自己的專案中,這裡有兩種整合方式,第一種是使用 Gradle 依賴這個專案既可,第二種把程式碼拷貝到自己專案中,我建議使用第二種方式,這樣你覺得有什麼問題自己改起來比較方便,當然了也可以直接給我提 issue 我來改。

整合

Gradle 方式整合

在對應 model 的 build.gradle 中新增依賴:

implementation 'com.github.0xZhangKe:WebSocketDemo:2.0'

然後編譯一下,如果出現類似的錯誤:

Failed to resolve: com.github.0xZhangKe:WebSocketDemo:2.0

那意味著你還沒新增 Github 的倉庫,到專案根目錄中的 build.gradle 中新增如下程式碼:

maven { url = 'https://jitpack.io' }

然後 sync 一下即可。

第二種整合方式

這個就很簡單了,直接把 websocketlib 中的程式碼拷貝到自己的專案中就行,具體怎麼做就看你的個人喜好。

相關配置

按照上面的步驟整合進來之後再做一些簡單的配置可以使用了。

配置 WebSocket 連線地址

首先,最重要的一點,配置 WebSocket 連線地址:

WebSocketSetting.setConnectUrl("Your WebSocket connect url");

這一步必須在啟動 WebSocketService 使用前呼叫,我是在 Application 中配置的,建議你們也這麼做,可以看一下 demo 的使用方式。這一步配置完成後一個簡單的 WebSocketService 就可以使用了。

配置統一的訊息處理器

在我們實際開發中可能需要考慮更多的問題,比如資料格式的統一規劃,後臺返回資料的統一處理,處理完成後再發送到下游等等。

機智的我早就想到了解決方案,本專案中使用IResponseDispatcher()來分發資料,可以看到這是個介面,預設會使用DefaultResponseDispatcher來當做訊息分發器,如果不進行設定 WebSocket 接收到資料後會直接傳送給下游。

那麼我們先來看一下 IResponseDispatcher:

public interface IResponseDispatcher {

    //省略掉其他程式碼

    /**
     * 接收到訊息
     *
     * @param message 接收到的訊息
     * @param delivery 訊息發射器
     */

    void onMessageResponse(Response message, ResponseDelivery delivery);

    //省略掉其他程式碼
}

IResponseDispatcher 共中有五個方法需要實現,大體上都類似的,我們只看其中一個就行。

onMessageResponse 方法中的兩個引數,Response 後面會介紹,這裡說一下 ResponseDelivery,我管它叫訊息發射器,其實很簡單,他內部就是維護了一個監聽器的 List,當呼叫其中某個方法時會遍歷呼叫所有的 Listener 中對應的方法。

當我們處理完資料之後通過這個就可以將資料傳送到下游的 Activity/Fragment 中,很簡單的吧,當然也可以對訊息進行攔截,或者將資料包裝成統一的格式再發送出去。

舉個栗子,我們要將資料轉成統一的一個實體在傳送到下游,那麼在實現類中可以這麼做:

   @Override
    public void onMessageResponse(Response message, ResponseDelivery delivery) {
         delivery.onMessageResponse(new CommonResponse(message.getResponseText(), JSON.parseObject(message.getResponseText(), new TypeReference<CommonResponseEntity>() {
         })));
    }

上面是把 Response 中的訊息資料轉成我們根據後臺資料統一格式自定義的 CommonResponseEntity 物件再包裝成一個自定義的 CommonResponse 物件傳送出去。

除此之外,更重要的一點是,當我們將訊息資料轉成 CommonResponseEntity 之後可以根據業務邏輯來進行統一的處理,例如後臺規定返回資料中的 code 欄位等於 1000 時才代表介面呼叫成功,那麼我們就可以直接在這裡做判斷了,而不是每個地方都要判斷一次:

    @Override
    public void onMessageResponse(Response message, ResponseDelivery delivery) {
        try {
            CommonResponse commonResponse = new CommonResponse(message.getResponseText(), JSON.parseObject(message.getResponseText(), new TypeReference<CommonResponseEntity>() {
            }));
            if (commonResponse.getResponseEntity().getCode() >= 1000) {
                delivery.onMessageResponse(commonResponse);
            } else {
                ErrorResponse errorResponse = new ErrorResponse();
                errorResponse.setErrorCode(12);
                errorResponse.setDescription(commonResponse.getResponseEntity().getMsg());
                errorResponse.setResponseText(message.getResponseText());
                //將已經解析好的 CommonResponseEntity 物件儲存起來以便後面使用
                errorResponse.setReserved(responseEntity);
                //IResponseDispatcher內的一個方法,表示接收到錯誤訊息,通過errorCode指定錯誤型別
                onSendMessageError(errorResponse, delivery);
            }
        } catch (JSONException e) {
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setResponseText(message.getResponseText());
            errorResponse.setErrorCode(11);
            errorResponse.setCause(e);
            onSendMessageError(errorResponse, delivery);
        }
    }

大概就是按照上面來實現,更詳細的用法可以看demo中是怎麼做的。

配置統一的訊息資料型別

一般來說,後臺介面返回的資料是有個固定的格式的,通過上面的介紹我們已經瞭解到如何把資料轉換成統一的型別傳送到下游,下面我們先來簡單的瞭解一下 Response,我這裡將所有後臺返回的資料統一包裝成一個 Response 物件,這是一個介面,你可以根據自己的需要來實現它:

/**
 * WebSocket 響應資料介面
 * Created by ZhangKe on 2018/6/26.
 */

public interface Response<T{

    /**
     * 獲取響應的文字資料
     */

    String getResponseText();

    /**
     * 設定響應的文字資料
     */

    void setResponseText(String responseText);

    /**
     * 獲取該資料的實體,可能為空,具體看實現類
     */

    getResponseEntity();

    /**
     * 設定資料實體
     */

    void setResponseEntity(T responseEntity);
}

WebSocket 接收到資料後會首先包裝成 TextResponse 物件傳送出去,我們看一下 TextResponse 的程式碼:

/**
 * 預設的訊息響應事件包裝類,
 * 只包含文字,不包含資料實體
 * Created by ZhangKe on 2018/6/27.
 */

public class TextResponse implements Response<String{

    private String responseText;

    public TextResponse(String responseText) {
        this.responseText = responseText;
    }

    public String getResponseText() {
        return responseText;
    }

    public void setResponseText(String responseText) {
        this.responseText = responseText;
    }

    public String getResponseEntity() {
        return null;
    }

    public void setResponseEntity(String responseEntity) {
    }
}

可以看到其中只包含了 String 型別的響應資料,沒有對資料做其他操作,接收到什麼就返回什麼,其中的 responseText 表示 WebSocket 接收到的文字資料,除此之外我還提供了兩個用於操作 ResponseEntity 的方法,我們可以將接收到的文字按照統一的格式轉換成一個實體存入這個欄位,然後再發送到下游。

比如後臺介面的資料格式如下:

{
    "message""登陸成功",
    "data": {
        "name""zhangke",
        "sex""男",
        "nationality""中國"
    },
    "code"1000,
    "path""app_user_login"
}

那麼我們可以將資料轉換成一個統一的泛型資料實體:

/**
 * 後臺介面返回的資料格式
 * Created by ZhangKe on 2018/6/27.
 */

public class CommonResponseEntity {

    private String message;
    private String data;
    private int code;
    private String path;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

data 欄位中的資料交給對應模組解析,這裡直接轉成 String,然後包裝成一個 CommonResponse 傳送出去:

public class CommonResponse implements Response<CommonResponseEntity{

    private String responseText;
    private CommonResponseEntity responseEntity;

    public CommonResponse(String responseText, CommonResponseEntity responseEntity) {
        this.responseText = responseText;
        this.responseEntity = responseEntity;
    }

    @Override
    public String getResponseText() {
        return responseText;
    }

    @Override
    public void setResponseText(String responseText) {
        this.responseText = responseText;
    }

    @Override
    public CommonResponseEntity getResponseEntity() {
        return this.responseEntity;
    }

    @Override
    public void setResponseEntity(CommonResponseEntity responseEntity) {
        this.responseEntity = responseEntity;
    }
}
錯誤資訊處理

剛剛已經介紹瞭如何統一處理訊息及將訊息轉換成對應的實體,下面再說一下如何統一的處理錯誤資訊。

所有的錯誤訊息將統一包裝成ErrorResponse物件傳送出去,看一下其中的程式碼:

/**
 * 出現錯誤時的響應
 * Created by ZhangKe on 2018/6/25.
 */

public class ErrorResponse {

    /**
     * 1-WebSocket 未連線或已斷開
     * 2-WebSocketService 服務未繫結到當前 Activity/Fragment,或繫結失敗
     * 3-WebSocket 初始化未完成
     * 11-資料獲取成功,但是解析 JSON 失敗
     * 12-資料獲取成功,但是伺服器返回資料中的code值不正確
     */

    private int errorCode;
    /**
     * 錯誤原因
     */

    private Throwable cause;
    /**
     * 傳送的資料,可能為空
     */

    private String requestText;
    /**
     * 響應的資料,可能為空
     */

    private String responseText;
    /**
     * 錯誤描述,客戶端可以通過這個欄位來設定統一的錯誤提示等等
     */

    private String description;

    /**
     * 保留欄位,可以自定義存放任意資料
     */

    private Object reserved;

    public ErrorResponse() {
    }

    /**
     * 1-WebSocket 未連線或已斷開
     * 2-WebSocketService 服務未繫結到當前 Activity/Fragment,或繫結失敗
     * 3-WebSocket 初始化未完成
     * 11-資料獲取成功,但是解析 JSON 失敗
     * 12-資料獲取成功,但是伺服器返回資料中的code值不正確
     */

    public int getErrorCode() {
        return errorCode;
    }

    /**
     * 1-WebSocket 未連線或已斷開
     * 2-WebSocketService 服務未繫結到當前 Activity/Fragment,或繫結失敗
     * 3-WebSocket 初始化未完成
     * 11-資料獲取成功,但是解析 JSON 失敗
     * 12-資料獲取成功,但是伺服器返回資料中的code值不正確
     */

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public Throwable getCause() {
        return cause;
    }

    public void setCause(Throwable cause) {
        this.cause = cause;
    }

    public String getRequestText() {
        return requestText;
    }

    public void setRequestText(String requestText) {
        this.requestText = requestText;
    }

    public String getResponseText() {
        return responseText;
    }

    public void setResponseText(String responseText) {
        this.responseText = responseText;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Object getReserved() {
        return reserved;
    }

    public void setReserved(Object reserved) {
        this.reserved = reserved;
    }
}

其中包括了五種錯誤型別,處理錯誤訊息時就按照錯誤碼來判斷既可,另外還提供了一個 reserved 保留欄位,這個用法可以看上面的配置統一的訊息處理器那一節。

錯誤資訊的處理同樣也在 IResponseDispatcher 中處理,上面已經介紹了其中的 onMessageResponse ,現在再來說一下 onSendMessageError 方法:

   /**
     * 統一處理錯誤資訊,
     * 介面上可使用 ErrorResponse#getDescription() 來當做提示語
     */

    @Override
    public void onSendMessageError(ErrorResponse error, ResponseDelivery delivery) {
        switch (error.getErrorCode()) {
            case 1:
                error.setDescription("網路錯誤");
                break;
            case 2:
                error.setDescription("網路錯誤");
                break;
            case 3:
                error.setDescription("網路錯誤");
                break;
            case 11:
                error.setDescription("資料格式異常");
                Log.e(LOGTAG, "資料格式異常", error.getCause());
                break;
        }
        delivery.onSendMessageError(error);
    }

其實這裡主要就是用來通過錯誤碼給出不同的錯誤提示,其它的也沒做什麼,也可以在這裡列印一下 Log 啊等等,code==12 時這裡沒有設定提示語,因為 12 表示介面已經請求成功了,但是後臺後臺介面給了錯誤的提示,比如密碼錯誤等等,這時候錯誤資訊應該是介面中給出,當然我們也可以自己來根據業務調整。

關於配置的就是這麼多了,下面在介紹一下如何使用。

使用

我提供了一個 AbsWebSocketActivity 和一個 AbsWebSocketFragment 抽象基類,需要使用 WebSocket 的介面只需要繼承這兩個中的某一個就行,看一下 AbsWebSocketActivity 的程式碼:

/**
 * 已經綁定了 WebSocketService 服務的 Activity,
 * <p>
 * Created by ZhangKe on 2018/6/25.
 */

public abstract class AbsWebSocketActivity extends AppCompatActivity implements IWebSocketPage {

    protected final String LOGTAG = this.getClass().getSimpleName();

    private WebSocketServiceConnectManager mConnectManager;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mConnectManager = new WebSocketServiceConnectManager(thisthis);
        mConnectManager.onCreate();
    }

    @Override
    public void sendText(String text) {
        mConnectManager.sendText(text);
    }

    /**
     * 服務繫結成功時的回撥,可以在此初始化資料
     */

    @Override