使用StompProtocolAndroid连接MQ通信

Android开发遇到需要和MQ队列通信,使用StompProtocolAndroid可以实现。

StompProtocolAndroid官网:GitHub - NaikSoftware/StompProtocolAndroid: STOMP protocol via WebSocket for Android

首先app的build.gradle添加依赖

复制代码
    implementation "com.github.NaikSoftware:StompProtocolAndroid:1.6.4"//StompProtocolAndroid
    implementation "io.reactivex.rxjava2:rxjava:2.2.5"
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'

项目的build.gradle中添加下面内容。如果是新版studio,是在setting.gradle里面配置

复制代码
allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
}

整理一个工具类StompClientUtil,直接调用即可使用

调用方式,在onCreate中调用initStompClient()做初始化,

调用addTopicData()添加回调监听,队列返回的数据,都通过这里监听处理。QUEUE_KEY是自己定义的队列名称字符串,可以通过设备id定义每个设备一个队列名称,在callBacke中处理队列返回的信息。

调用sendMessage()方法向队列发送消息,QUEUE_KEY是队列名,第二个参数是发送的内容。

复制代码
@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initClick();
        
        StompClientUtil.getInstance().initStompClient();
        StompClientUtil.getInstance().addTopicData(this, QUEUE_KEY, new StompClientUtil.TopicCallBack() {
            @Override
            public void callBack(StompMessage stompMessage) {
                Log.i(TAG, stompMessage.getPayload());
            }
        });
    }


    private void initClick(){
        binding.btnSend.setOnClickListener(this);
    }

    @Override
    public void onClick(@Nullable View view) {
        super.onClick(view);
        if (view.equals(binding.btnSend)){
            StompClientUtil.getInstance().sendMessage(QUEUE_KEY, "发送内容");
        }
    }

其中sendMessage()方法具体如下。其中

String conTopic = "/exchange/exchange_web.../key_...." + topicKey;

可以看出,和MQ通信是通过交换机的方式,"/exchange/exchange_web.../key_...."是MQ指定的发送路径,拼接上自己的QUEUE_KEY,在MQ中会生成一个发送队列。通过这个队列和MQ通信。这种方式生成的独立是自动消息的,一旦失去监听,队列也会自动消失。

复制代码
/**
     * 发送数据
     * @param topicKey 生成的随机数
     * @param data 发送数据
     */
    public void sendMessage(String topicKey, String data){
        if (TextUtils.isEmpty(data)){
            return;
        }
        if (stompClient == null) {
            initStompClient();
            return;
        }
        if (!stompClient.isConnected()) {
            Log.i(TAG, "sendMessage 发现isConnected false");
            handler.sendEmptyMessage(0);
            return;
        }
        Log.i(TAG, "真的发送出去啦啦啦XXXXXXXXXXXXXXXXXX");
        String conTopic = "/exchange/exchange_web.../key_...." + topicKey;
        stompClient.send(conTopic, data)
                .unsubscribeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onComplete() {
                        Log.e(TAG, "STOMP echo send successfully");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "STOMP echo send onError" + e.getMessage());
                    }
                });

addTopicData()方法具体如下。其中

String topic = "/exchange/exchange_transd.../key_...."+conTopic;

"/exchange/exchange_transd.../key_...."是MQ指定了接收数据的路径,拼接上自己的QUEUE_KEY,在MQ中会生成一个接收队列,客户端通过这里接收数据。

复制代码
/**
     * @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)) {
//            Log.e(TAG, "该对象已存在相同监听:"+disKey);
            return;
        }
        String topic = "/exchange/exchange_transd.../key_...."+conTopic;
        callbackMap.put(disKey, topicCallBack);
        topicMap.put(disKey, topic);
        Disposable disposable = stompClient.topic(topic, headers)
                .doOnError(throwable -> {
                    Log.e(TAG, "doOnError on subscribe topic=" + topic + throwable);
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(stompMessage -> {
                    Log.e(TAG, "stomp监听消息=" + stompMessage);
                    if (callbackMap != null && callbackMap.size() > 0 && callbackMap.get(disKey) != null) {
                        callbackMap.get(disKey).callBack(stompMessage);
                    }
                }, throwable -> {
                    Log.e(TAG, "throwable on subscribe topic" + throwable);
                });
        compositeDisposable.add(disposable);
        disposableMap.put(disKey, disposable);
    }

完整代码如下:

复制代码
package com.example.yuntest.utils;

import android.annotation.SuppressLint;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;

import java.util.ArrayList;
import java.util.Map;

import io.reactivex.CompletableObserver;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import ua.naiksoftware.stomp.Stomp;
import ua.naiksoftware.stomp.StompClient;
import ua.naiksoftware.stomp.dto.StompCommand;
import ua.naiksoftware.stomp.dto.StompHeader;
import ua.naiksoftware.stomp.dto.StompMessage;

public class StompClientUtil {
    
    public static final String URL = "ws://IP地址:端口号/ws";//连接地址
    public static final String MQTT_USERNAME = " "; // mqtt连接用户名
    public static final String MQTT_PASSWORD = " "; // mqtt连接密码

    private static final String TAG = "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;
    }


    @SuppressLint("CheckResult")
    public void initStompClient() {
        if (stompClient != null) {
            return;
        }
        disposableMap = new ArrayMap<>();
        callbackMap = new ArrayMap<>();
        topicMap = new ArrayMap<>();
        initComposite();
        stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, URL);
        stompClient.lifecycle().subscribe(lifecycleEvent -> {
            switch (lifecycleEvent.getType()) {
                case OPENED:
                    reconnectionNum = 0;
                    Log.e(TAG, "debug stomp 已连接");
                    break;
                case ERROR:
                    // javax.net.ssl.SSLException: Read error: ssl=0x7a879fa788 需要处理
                    Log.e(TAG, " debug stomp 连接错误: " + lifecycleEvent.getException());
                    if (reconnectionNum < reconnectionMaxNum) {
                        handler.sendEmptyMessage(0);
                    }
                    break;
                case CLOSED:
                    Log.e(TAG, " debug stomp closed: ");
                    break;
                case FAILED_SERVER_HEARTBEAT:
                    Log.e(TAG, " Stomp fail server heartbeat");
                    break;
            }
        }, throwable -> Log.e(TAG, " 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;
    }


    @SuppressLint("HandlerLeak")
    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void dispatchMessage(Message msg) {
            if (msg.what == 0) { //重连
                reConnect();
            } else if (msg.what == 1) { //重新添加监听
                Log.e(TAG, "重连中");
                reconnectionNum++;
                connect();
                reConnectTopic();
            }
        }
    };

    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) {
                        Log.e(TAG, "Disposable code:" + d.hashCode());
                    }

                    @Override
                    public void onComplete() {
                        Log.e(TAG, "断开连接,准备重连");
                        handler.sendEmptyMessage(1);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "Disconnect error:" + e);
                    }
                });
            }
        };
        timer.start();
    }


    private void connect() {
        if (stompClient == null) {
            initStompClient();
        }
        headers = new ArrayList<>();
        headers.add(new StompHeader("name", MQTT_USERNAME));
        headers.add(new StompHeader("password", MQTT_PASSWORD));
        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)) {
//            Log.e(TAG, "该对象已存在相同监听:"+disKey);
            return;
        }
        String topic = "/exchange/exchange_transd.../key_...."+conTopic;
        callbackMap.put(disKey, topicCallBack);
        topicMap.put(disKey, topic);
        Disposable disposable = stompClient.topic(topic, headers)
                .doOnError(throwable -> {
                    Log.e(TAG, "doOnError on subscribe topic=" + topic + throwable);
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(stompMessage -> {
                    Log.e(TAG, "stomp监听消息=" + stompMessage);
                    if (callbackMap != null && callbackMap.size() > 0 && callbackMap.get(disKey) != null) {
                        callbackMap.get(disKey).callBack(stompMessage);
                    }
                }, throwable -> {
                    Log.e(TAG, "throwable on subscribe topic" + throwable);
                });
        compositeDisposable.add(disposable);
        disposableMap.put(disKey, disposable);
    }


    private void reConnectTopic() {
        if (callbackMap == null || stompClient == null || compositeDisposable == null
                || callbackMap.size() < 1) {
            return;
        }
        Log.e(TAG, "重新监听");
        for (Map.Entry<String, TopicCallBack> entry : callbackMap.entrySet()) {
            String mapKey = entry.getKey();
            TopicCallBack mapValue = entry.getValue();
            Log.e(TAG, "key:" + mapKey);
            compositeDisposable.remove(disposableMap.get(mapKey));
            Disposable disposable = stompClient.topic(topicMap.get(mapKey), headers)
                    .doOnError(throwable -> {
                        Log.e(TAG, "doOnError on subscribe topic=" + topicMap.get(mapKey) + throwable);
                        handler.sendEmptyMessage(0);
                    })
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(stompMessage -> {
                        Log.e(TAG, "stomp监听消息=" + stompMessage.getPayload());
                        if (mapValue != null) {
                            mapValue.callBack(stompMessage);
                        }
                    }, throwable -> {
                        Log.e(TAG, "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)) {
            Log.e(TAG, "不存在对应监听");
            return;
        }
        topicMap.remove(disKey);
        callbackMap.remove(disKey);
        compositeDisposable.remove(disposableMap.get(disKey));
    }

    /**
     * 是否连接中
     * @return
     */
    public boolean isConnected(){
        return stompClient.isConnected();
    }

    /**
     * 发送数据
     * @param topicKey 生成的随机数
     * @param data 发送数据
     */
    public void sendMessage(String topicKey, String data){
        if (TextUtils.isEmpty(data)){
            return;
        }
        if (stompClient == null) {
            initStompClient();
            return;
        }
        if (!stompClient.isConnected()) {
            Log.i(TAG, "sendMessage 发现isConnected false");
            handler.sendEmptyMessage(0);
            return;
        }
        Log.i(TAG, "真的发送出去啦啦啦XXXXXXXXXXXXXXXXXX");
        String conTopic = "/exchange/exchange_web.../key_...." + topicKey;
        stompClient.send(conTopic, data)
                .unsubscribeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new CompletableObserver() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onComplete() {
                        Log.e(TAG, "STOMP echo send successfully");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(TAG, "STOMP echo send onError" + e.getMessage());
                    }
                });
    }
    
    /**
     * 示例:/topic/bid_list
     */
    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();
                        }
                        Log.e(TAG, "STOMP echo send successfully");
                    }

                    @Override
                    public void onError(Throwable e) {
                        if (sendMessageCallBack != null) {
                            sendMessageCallBack.onError();
                        }
                        Log.e(TAG, "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);
        }
    }


}
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui