介紹
WebSocketDemo 現(xiàn)在已經更新到 3.0 版本了,請移步至:
http://www.itdecent.cn/p/98eab73915fd
關于 WebSocket Android 端的使用封裝之前已經做過一次了,但在使用了一段時間之后逐漸發(fā)現(xiàn)了一些問題,一直想改也沒時間,正好最近公司業(yè)務比較少,就趁著這段時間有空閑把代碼優(yōu)化了一下,其實差不多是重新做一套了。
這個版本的使用方式上比之前簡化了很多,集成起來也更容易,并且代碼邏輯更加清晰,模塊與模塊之間的耦合降到最低,運行效率更高,更健壯,好了廢話不說了,先介紹一下使用方式。
如何使用
先放上 Github 地址:
https://github.com/0xZhangKe/WebSocketDemo
好了,首先將代碼集成到自己的項目中,這里有兩種集成方式,第一種是使用 Gradle 依賴這個項目既可,第二種把代碼拷貝到自己項目中,我建議使用第二種方式,這樣你覺得有什么問題自己改起來比較方便,當然了也可以直接給我提 issue 我來改。
集成
Gradle 方式集成
在對應 model 的 build.gradle 中添加依賴:
implementation 'com.github.0xZhangKe:WebSocketDemo:2.0'
然后編譯一下,如果出現(xiàn)類似的錯誤:
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 就可以使用了。
配置統(tǒng)一的消息處理器
在我們實際開發(fā)中可能需要考慮更多的問題,比如數(shù)據(jù)格式的統(tǒng)一規(guī)劃,后臺返回數(shù)據(jù)的統(tǒng)一處理,處理完成后再發(fā)送到下游等等。
機智的我早就想到了解決方案,本項目中使用IResponseDispatcher來分發(fā)數(shù)據(jù),可以看到這是個接口,默認會使用DefaultResponseDispatcher來當做消息分發(fā)器,如果不進行設置 WebSocket 接收到數(shù)據(jù)后會直接發(fā)送給下游。
那么我們先來看一下 IResponseDispatcher:
public interface IResponseDispatcher {
//省略掉其他代碼
/**
* 接收到消息
*
* @param message 接收到的消息
* @param delivery 消息發(fā)射器
*/
void onMessageResponse(Response message, ResponseDelivery delivery);
//省略掉其他代碼
}
IResponseDispatcher 共中有五個方法需要實現(xiàn),大體上都類似的,我們只看其中一個就行。
onMessageResponse 方法中的兩個參數(shù),Response 后面會介紹,這里說一下 ResponseDelivery,我管它叫消息發(fā)射器,其實很簡單,他內部就是維護了一個監(jiān)聽器的 List,當調用其中某個方法時會遍歷調用所有的 Listener 中對應的方法。
當我們處理完數(shù)據(jù)之后通過這個就可以將數(shù)據(jù)發(fā)送到下游的 Activity/Fragment 中,很簡單的吧,當然也可以對消息進行攔截,或者將數(shù)據(jù)包裝成統(tǒng)一的格式再發(fā)送出去。
舉個栗子,我們要將數(shù)據(jù)轉成統(tǒng)一的一個實體在發(fā)送到下游,那么在實現(xiàn)類中可以這么做:
@Override
public void onMessageResponse(Response message, ResponseDelivery delivery) {
delivery.onMessageResponse(new CommonResponse(message.getResponseText(), JSON.parseObject(message.getResponseText(), new TypeReference<CommonResponseEntity>() {
})));
}
上面是把 Response 中的消息數(shù)據(jù)轉成我們根據(jù)后臺數(shù)據(jù)統(tǒng)一格式自定義的 CommonResponseEntity 對象再包裝成一個自定義的 CommonResponse 對象發(fā)送出去。
除此之外,更重要的一點是,當我們將消息數(shù)據(jù)轉成 CommonResponseEntity 之后可以根據(jù)業(yè)務邏輯來進行統(tǒng)一的處理,例如后臺規(guī)定返回數(shù)據(jù)中的 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);
}
}
onSendMessageError 方法后面會介紹
大概就是按照上面來實現(xiàn),更詳細的用法可以看demo中是怎么做的。
配置統(tǒng)一的消息數(shù)據(jù)類型
一般來說,后臺接口返回的數(shù)據(jù)是有個固定的格式的,通過上面的介紹我們已經了解到如何把數(shù)據(jù)轉換成統(tǒng)一的類型發(fā)送到下游,下面我們先來簡單的了解一下 Response,我這里將所有后臺返回的數(shù)據(jù)統(tǒng)一包裝成一個 Response 對象,這是一個接口,你可以根據(jù)自己的需要來實現(xiàn)它:
/**
* WebSocket 響應數(shù)據(jù)接口
* Created by ZhangKe on 2018/6/26.
*/
public interface Response<T> {
/**
* 獲取響應的文本數(shù)據(jù)
*/
String getResponseText();
/**
* 設置響應的文本數(shù)據(jù)
*/
void setResponseText(String responseText);
/**
* 獲取該數(shù)據(jù)的實體,可能為空,具體看實現(xiàn)類
*/
T getResponseEntity();
/**
* 設置數(shù)據(jù)實體
*/
void setResponseEntity(T responseEntity);
}
WebSocket 接收到數(shù)據(jù)后會首先包裝成 TextResponse 對象發(fā)送出去,我們看一下 TextResponse 的代碼:
/**
* 默認的消息響應事件包裝類,
* 只包含文本,不包含數(shù)據(jù)實體
* 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 類型的響應數(shù)據(jù),沒有對數(shù)據(jù)做其他操作,接收到什么就返回什么,其中的 responseText 表示 WebSocket 接收到的文本數(shù)據(jù),除此之外我還提供了兩個用于操作 ResponseEntity 的方法,我們可以將接收到的文本按照統(tǒng)一的格式轉換成一個實體存入這個字段,然后再發(fā)送到下游。
比如后臺接口的數(shù)據(jù)格式如下:
{
"message": "登陸成功",
"data": {
"name": "zhangke",
"sex": "男",
"nationality": "中國"
},
"code": 1000,
"path": "app_user_login"
}
那么我們可以將數(shù)據(jù)轉換成一個統(tǒng)一的泛型數(shù)據(jù)實體:
/**
* 后臺接口返回的數(shù)據(jù)格式
* 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 字段中的數(shù)據(jù)交給對應模塊解析,這里直接轉成 String,然后包裝成一個 CommonResponse 發(fā)送出去:
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;
}
}
錯誤信息的處理
剛剛已經介紹了如何統(tǒng)一處理消息及將消息轉換成對應的實體,下面再說一下如何統(tǒng)一的處理錯誤信息。
所有的錯誤消息將統(tǒng)一包裝成ErrorResponse對象發(fā)送出去,看一下其中的代碼:
/**
* 出現(xiàn)錯誤時的響應
* Created by ZhangKe on 2018/6/25.
*/
public class ErrorResponse {
/**
* 1-WebSocket 未連接或已斷開
* 2-WebSocketService 服務未綁定到當前 Activity/Fragment,或綁定失敗
* 3-WebSocket 初始化未完成
* 11-數(shù)據(jù)獲取成功,但是解析 JSON 失敗
* 12-數(shù)據(jù)獲取成功,但是服務器返回數(shù)據(jù)中的code值不正確
*/
private int errorCode;
/**
* 錯誤原因
*/
private Throwable cause;
/**
* 發(fā)送的數(shù)據(jù),可能為空
*/
private String requestText;
/**
* 響應的數(shù)據(jù),可能為空
*/
private String responseText;
/**
* 錯誤描述,客戶端可以通過這個字段來設置統(tǒng)一的錯誤提示等等
*/
private String description;
/**
* 保留字段,可以自定義存放任意數(shù)據(jù)
*/
private Object reserved;
public ErrorResponse() {
}
/**
* 1-WebSocket 未連接或已斷開
* 2-WebSocketService 服務未綁定到當前 Activity/Fragment,或綁定失敗
* 3-WebSocket 初始化未完成
* 11-數(shù)據(jù)獲取成功,但是解析 JSON 失敗
* 12-數(shù)據(jù)獲取成功,但是服務器返回數(shù)據(jù)中的code值不正確
*/
public int getErrorCode() {
return errorCode;
}
/**
* 1-WebSocket 未連接或已斷開
* 2-WebSocketService 服務未綁定到當前 Activity/Fragment,或綁定失敗
* 3-WebSocket 初始化未完成
* 11-數(shù)據(jù)獲取成功,但是解析 JSON 失敗
* 12-數(shù)據(jù)獲取成功,但是服務器返回數(shù)據(jù)中的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 保留字段,這個用法可以看上面的配置統(tǒng)一的消息處理器那一節(jié)。
錯誤信息的處理同樣也在 IResponseDispatcher 中處理,上面已經介紹了其中的 onMessageResponse ,現(xiàn)在再來說一下 onSendMessageError 方法:
/**
* 統(tǒng)一處理錯誤信息,
* 界面上可使用 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("數(shù)據(jù)格式異常");
Log.e(LOGTAG, "數(shù)據(jù)格式異常", error.getCause());
break;
}
delivery.onSendMessageError(error);
}
其實這里主要就是用來通過錯誤碼給出不同的錯誤提示,其它的也沒做什么,也可以在這里打印一下 Log 啊等等,code==12 時這里沒有設置提示語,因為 12 表示接口已經請求成功了,但是后臺后臺接口給了錯誤的提示,比如密碼錯誤等等,這時候錯誤信息應該是接口中給出,當然我們也可以自己來根據(jù)業(yè)務調整。
關于配置的就是這么多了,下面在介紹一下如何使用。
使用
我提供了一個 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(this, this);
mConnectManager.onCreate();
}
@Override
public void sendText(String text) {
mConnectManager.sendText(text);
}
/**
* 服務綁定成功時的回調,可以在此初始化數(shù)據(jù)
*/
@Override
public void onServiceBindSuccess() {
}
/**
* WebSocket 連接成功事件
*/
@Override
public void onConnected() {
}
/**
* WebSocket 連接出錯事件
*
* @param cause 出錯原因
*/
@Override
public void onConnectError(Throwable cause) {
}
/**
* WebSocket 連接斷開事件
*/
@Override
public void onDisconnected() {
}
@Override
protected void onPause() {
if (isFinishing()) {
mConnectManager.onDestroy();
}
super.onPause();
}
}
代碼很簡潔的吧,有關于對 WebSocketService 的綁定、監(jiān)聽等操作全部放在了 WebSocketServiceConnectManager 類中,這樣規(guī)避了代碼重復問題,如果你想做一下自己的 BaseWebSocketActivity/BaseWebSocketFragment 直接按照這里面的代碼實現(xiàn)既可。
AbsWebSocketActivity/AbsWebSocketFragment 中提供了一系列的方法以供使用,大部分方法一般都不需要用的,主要有三個方法要說一下:
public void onServiceBindSuccess();//WebSocketService 服務綁定成功回調事件,可以在這個回調方法中初始化一下數(shù)據(jù)
public void onMessageResponse(Response message);//接收到消息回調事件
public void onSendMessageError(ErrorResponse error);//消息發(fā)送失敗或接收到錯誤消息事件
onMessageResponse 及 onSendMessageError 方法中的 Response 和 ErrorResponse 參數(shù)上面已經介紹過了,另外還有一個 onServiceBindSuccess 方法,表示服務綁定成功,可以開始發(fā)送數(shù)據(jù)了。
重連機制
連接斷開后會自動重連 20 次,每次間隔 500 毫秒。也可以通過監(jiān)聽網絡連接變化自動重連,這部分我已經寫好了,配置一下既可開啟。
WebSocketSetting.setReconnectWithNetworkChanged(true);
跟上面說的一樣,這個也要在啟動 WebSocketService 之前調用。
別忘了在 AndroidManifest.xml 配置廣播和權限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application>
<!--省略代碼-->
<receiver android:name="com.zhangke.websocket.NetworkChangedReceiver" />
</application>
好了關于如何配置及使用差不多就這樣了,如果還有哪里不清楚的隨時可以問我哦,下面在介紹的是其中的原理,不想看的可以直接跳過。
原理
關于原理我就大概的介紹一下,也沒有太多的代碼,細節(jié)部分我就不說了,先說一下設計。
在整個框架中的核心就是 WebSocketThread 線程,其內部采用的是消息驅動型的設計,使用 Looper.loop() 開啟消息循環(huán),其他模塊將 WebSocket 的所有操作(消息發(fā)送、連接、斷開等等)封裝成消息的形式發(fā)送到該線程。
我們來看一下流程圖:

Service 在創(chuàng)建一個 WebSocketThread 對象后通過獲取該線程的 Handler 來向其發(fā)送控制信息。
關于重連模塊使用的是一個單獨的類 ReconnectManager 來管理,其內部也持有一個 WebSocketThread 對象,當觸發(fā)重連事件時通過 Handler 發(fā)送連接消息既可。
WebSocket 中的各種事件(連接成功、接收到消息等等)通過監(jiān)聽器 SocketListener 通知 Service。
WebSocketThread 講完了我在講一下 WebSocketService ,也是比較重要,先看圖:

上圖描述了 WebSocket 事件從 WebSocketThread 到 WebSocketService 再到 Activity/Fragment 的事件流向,WebSocketService 中通過一個 IResponseDispatcher 接口來分發(fā)事件,默認實現(xiàn)為 DefaultResponseDispatcher ,不做任何處理,直接發(fā)送到下游,也可以自己實現(xiàn)從而實現(xiàn)數(shù)據(jù)攔截、轉換等操作。
好了就說到這里了,具體的一些細節(jié)直接看代碼就行,還是很清晰的,要是有什么疑問直接問我也行。