使用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);
        }
    }


}
相关推荐
拭心10 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王12 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡12 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道13 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库13 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道14 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe14 小时前
Android Hook - 动态加载so库
android
居居飒15 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He18 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗18 小时前
Android笔试面试题AI答之Android基础(1)
android