Android - StompClient 封装成工具类(规避不好用的使用方法)

项目背景:最近新入职一家公司,发现公司是用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() 方法。(目前没有调用,你们自己看情况而定)

以上就是我使用过程中遇到问题,自己简单的封装了一下,我看可能是技术比较老,网上没有相关的使用说明,所以想分享一下,不一定最好用的,但对于我来说足够了。

如果有大佬有更好的思路和封装可以分享一下。


                
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值