项目背景:最近新入职一家公司,发现公司是用Stomp协议集成的一个基础管道通信功能,也是第一次接触,看完代码很好奇,为什么要写多个连接和多个topic监听?看前人代码是有封装成一个连接的想法的,为什么没做?出于代码封装原则,所以我决定重做一次。
老代码引入的三方库:
implementation "com.github.NaikSoftware:StompProtocolAndroid:1.6.6"//webSocket
本人就是最这个引入的基础上做封装;
回答上面的问题,为什么需要多个连接,为什么不封装,相信使用过的朋友们都知道,上面的库有一些较麻烦的点:
1、重连client,老的topic监听不起作用,监听没效果;
2、回调是到异步线程中;
3、stompClient.lifecycle().subscribe();
client监听有,errror和closed改选那个为重连的契机?
4、重连怎么避免过快的刷新重连;
5、重连假如包含token可变字段改如何;
6、退出当前Activity如何删除对应的监听,避免资源浪费,内存泄漏;
以上是我实践碰到的问题。
针对以上问题,我封装了一个工具类:StompClient
为了多一重重连机制的保障,多引入一个三方库:
implementation 'com.caption:netmonitorlibrary:1.0.0'//广播监听网络变化
废话不多说,直接上StompClientUtil代码:
public class StompClientUtil {
private static StompClientUtil instance;
public CompositeDisposable compositeDisposable;
private StompClient stompClient;
private final int reconnectionMaxNum = 900; //重连最大次数 时间约等于一个小时
private int reconnectionNum = 0; //重连次数
private ArrayList<StompHeader> headers;
private NetChangeObserver mNetChangeObserver = null;
private CountDownTimer timer;
private Map<String, Disposable> disposableMap;
private Map<String, TopicCallBack> callbackMap;
private Map<String, String> topicMap;
public static StompClientUtil getInstance() {
if (instance == null) {
synchronized (StompClientUtil.class) {
if (instance == null) {
instance = new StompClientUtil();
}
}
}
return instance;
}
public void initStompClient() {
if (stompClient != null) {
return;
}
disposableMap = new ArrayMap<>();
callbackMap = new ArrayMap<>();
topicMap = new ArrayMap<>();
initComposite();
stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, HttpUtils.WEB_SOCKET_BASE_URL);
stompClient.lifecycle().subscribe(lifecycleEvent -> {
switch (lifecycleEvent.getType()) {
case OPENED:
reconnectionNum = 0;
LogUtil.e("debug stomp 已连接");
break;
case ERROR:
// javax.net.ssl.SSLException: Read error: ssl=0x7a879fa788 需要处理
LogUtil.e(" debug stomp 连接错误: " + lifecycleEvent.getException());
if (reconnectionNum < reconnectionMaxNum) {
handler.sendEmptyMessage(0);
}
break;
case CLOSED:
LogUtil.e(" debug stomp closed: ");
break;
case FAILED_SERVER_HEARTBEAT:
LogUtil.e(" Stomp fail server heartbeat");
break;
}
}, throwable -> LogUtil.e(" Stomp connect Throwable:" + Log.getStackTraceString(throwable)));
stompClient.withClientHeartbeat(1000 * 10).withServerHeartbeat(1000 * 10);
connect();
getNetWork();
}
private void getNetWork() {
// 网络改变的一个回掉类
mNetChangeObserver = new NetChangeObserver() {
@Override
public void onNetConnected(com.caption.netmonitorlibrary.netStateLib.NetUtils.NetType type) {
if (type == com.caption.netmonitorlibrary.netStateLib.NetUtils.NetType.NONE) {
onNetworkDisConnected();
return;
}
onNetworkConnected(type);
}
@Override
public void onNetDisConnect() {
onNetworkDisConnected();
}
};
NetStateReceiver.registerObserver(mNetChangeObserver);
}
private void onNetworkConnected(NetUtils.NetType type) { //辅助一层,确保重连
if (!stompClient.isConnected() && timer == null && reconnectionNum < reconnectionMaxNum) {
handler.sendEmptyMessage(0);
}
}
private void onNetworkDisConnected() {
}
public StompClient getStompClient() {
return stompClient;
}
Handler handler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
if (msg.what == 0) { //重连
reConnect();
} else if (msg.what == 1) { //重新添加监听
LogUtil.e("重连中");
reconnectionNum++;
connect();
reConectTopic();
}
}
};
public void stompDiyReConnect() {
if (stompClient == null) {
initStompClient();
}
handler.sendEmptyMessage(0);
}
private void reConnect() {
if (timer != null) {
return;
}
timer = new CountDownTimer(4 * 1000, 1000) {
@Override
public void onTick(long l) {
}
@Override
public void onFinish() {
timer = null;
stompClient.disconnectCompletable().subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
LogUtil.e("Disposable code:" + d.hashCode());
}
@Override
public void onComplete() {
LogUtil.e("断开连接,准备重连");
handler.sendEmptyMessage(1);
}
@Override
public void onError(Throwable e) {
LogUtil.e("Disconnect error:" + e);
}
});
}
};
timer.start();
}
private void connect() {
if (stompClient == null) {
initStompClient();
}
headers = new ArrayList<>();
String sendToken = "";
if (UserUtils2.getToken() != null) {
sendToken = UserUtils2.getToken().getAccessToken();
}
LogUtil.e("---------token:" + sendToken);
headers.add(new StompHeader("appPlatform", "Android"));//软件平台
headers.add(new StompHeader("token", sendToken));//token
headers.add(new StompHeader("destination", "/app/bid"));//destination---为了send的时候添加
stompClient.connect(headers);
}
/**
* @param tempInstance
* @param conTopic
* @param topicCallBack
*/
public void addTopicData(Object tempInstance, String conTopic, TopicCallBack topicCallBack) {
if (stompClient == null || compositeDisposable == null || instance == null) {
return;
}
String disKey = tempInstance.getClass().getCanonicalName() + conTopic;
if (callbackMap.containsKey(disKey)) {
LogUtil.e("该对象已存在相同监听");
return;
}
callbackMap.put(disKey, topicCallBack);
topicMap.put(disKey, conTopic);
Disposable disposable = stompClient.topic(conTopic, headers)
.doOnError(throwable -> {
LogUtil.e("doOnError on subscribe topic=" + conTopic + throwable);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(stompMessage -> {
LogUtil.d("stomp监听消息=" + stompMessage.getPayload());
if (callbackMap != null && callbackMap.size() > 0 && callbackMap.get(disKey) != null) {
callbackMap.get(disKey).callBack(stompMessage);
}
}, throwable -> {
LogUtil.e("throwable on subscribe topic" + throwable);
});
compositeDisposable.add(disposable);
disposableMap.put(disKey, disposable);
}
private void reConectTopic() {
if (callbackMap == null || stompClient == null || compositeDisposable == null
|| callbackMap.size() < 1) {
return;
}
LogUtil.e("重新监听");
for (Map.Entry<String, TopicCallBack> entry : callbackMap.entrySet()) {
String mapKey = entry.getKey();
TopicCallBack mapValue = entry.getValue();
LogUtil.e("key:" + mapKey);
compositeDisposable.remove(disposableMap.get(mapKey));
Disposable disposable = stompClient.topic(topicMap.get(mapKey), headers)
.doOnError(throwable -> {
LogUtil.e("doOnError on subscribe topic=" + topicMap.get(mapKey) + throwable);
handler.sendEmptyMessage(0);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(stompMessage -> {
LogUtil.d("stomp监听消息=" + stompMessage.getPayload());
if (mapValue != null) {
mapValue.callBack(stompMessage);
}
}, throwable -> {
LogUtil.e("throwable on subscribe topic" + throwable);
});
compositeDisposable.add(disposable);
disposableMap.put(mapKey, disposable);
}
}
public void removeTopic(Object tempInstance, String conTopic) {
if (compositeDisposable == null || stompClient == null) {
return;
}
if (disposableMap == null || disposableMap.size() < 1 || callbackMap == null) {
return;
}
String disKey = tempInstance.getClass().getCanonicalName() + conTopic;
if (!disposableMap.containsKey(disKey)) {
LogUtil.e("不存在对应监听");
return;
}
topicMap.remove(disKey);
callbackMap.remove(disKey);
compositeDisposable.remove(disposableMap.get(disKey));
}
/**
* 示例:/topic/bid_list
*
* @param conTopic
*/
@SuppressLint("CheckResult")
public void sendMTestessage(String conTopic, SendMessageCallBack sendMessageCallBack) {
if (stompClient == null) {
initStompClient();
return;
}
if (!stompClient.isConnected()) {
handler.sendEmptyMessage(0);
return;
}
stompClient.send(conTopic, "Echo STOMP " + new Date().toString())
.unsubscribeOn(Schedulers.newThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
if (sendMessageCallBack != null) {
sendMessageCallBack.onSuccess();
}
LogUtil.d("STOMP echo send successfully");
}
@Override
public void onError(Throwable e) {
if (sendMessageCallBack != null) {
sendMessageCallBack.onError();
}
LogUtil.d("STOMP echo send onError");
}
});
}
/**
* 示例:/topic/bid_list
*/
@SuppressLint("CheckResult")
public void sendMessage(String body, SendMessageCallBack sendMessageCallBack) {
if (stompClient == null) {
initStompClient();
return;
}
if (!stompClient.isConnected()) {
handler.sendEmptyMessage(0);
return;
}
stompClient.send(new StompMessage(StompCommand.SEND, headers, body))
.unsubscribeOn(Schedulers.newThread())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
if (sendMessageCallBack != null) {
sendMessageCallBack.onSuccess();
}
LogUtil.d("STOMP echo send successfully");
}
@Override
public void onError(Throwable e) {
if (sendMessageCallBack != null) {
sendMessageCallBack.onError();
}
LogUtil.d("STOMP echo send onError");
}
});
}
public interface TopicCallBack {
void callBack(StompMessage stompMessage);
}
public interface SendMessageCallBack {
void onSuccess();
void onError();
}
private void initComposite() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
compositeDisposable = new CompositeDisposable();
}
//取消订阅
public void unSubcribe() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
public void stopStomp() {
if (stompClient != null) {
stompClient.disconnect();
}
if (timer != null) {
timer.cancel();
}
if (disposableMap != null) {
disposableMap.clear();
disposableMap = null;
}
NetStateReceiver.removeRegisterObserver(mNetChangeObserver);
unSubcribe();
stompClient = null;
timer = null;
if (handler != null) {
handler.removeMessages(0);
}
}
}
使用建议:
1、在MainActivity 进行初始化,全局可用。或者只在需要的时候初始化,切记,初始化一次即可。
使用:initStompClient()方法即可
2、在需要监听的页面添加Topic.
使用:addTopicData()方法即可,参数:第一个为当前Activity,或者Fragment,其他的就是需要监听的topic和需要回调的方法,例如
addTopicData(this,"topic/xxxxxx",CallBack(回调));
3、删除页面的监听
使用:removeTopic(),同上,少一个回调的参数,例如
removeTopic(this,"topic/xxxxxx",CallBack(回调));
4、发送消息(这个比较简单好用):sendMessage()//自己可以去改我的代码变成想要的即可
5、最后断开连接方法:StopStomp() 方法。(目前没有调用,你们自己看情况而定)
以上就是我使用过程中遇到问题,自己简单的封装了一下,我看可能是技术比较老,网上没有相关的使用说明,所以想分享一下,不一定最好用的,但对于我来说足够了。
如果有大佬有更好的思路和封装可以分享一下。