RabbitMQ在android上的使用

最近公司有个项目,需要服务端发出消息,由android客户端作为消费者接收。网路上有很多关于android端作为发送端的实现,本文只讲关于android端作为消费者的实现方式

按照惯例,有问题先去官网找demo,于是我看到了这个:

f*ck.png

附上地址,有兴趣的小伙伴可以自己去官方demo学习一下~
https://github.com/cloudamqp/android-example

1.集成

implementation 'com.rabbitmq:amqp-client:${version}'

2.常规代码实现

private fun initConnect() {
        val factory = ConnectionFactory()
        factory.host = Constants.MQ_HOST
        factory.port = Constants.MQ_PORT
        factory.username = Constants.MQ_USERNAME
        factory.password = Constants.MQ_PASSWORD
        startConsumer()
    }
fun startConsumer() {
        val QUEUE_NAME = QUEUE_PRIFIX+DEVICEID
        val thread = Thread(Runnable {
            try {
                val connection: Connection = factory.newConnection()
                val channel: Channel = connection.createChannel()
                val consumer = object : DefaultConsumer(channel) {
                      override fun handleDelivery(
                      consumerTag: String?,
                      envelope: Envelope?,
                      properties: AMQP.BasicProperties?,
                      body: ByteArray?
                  ) {
                      super.handleDelivery(consumerTag, envelope, properties, body)
                      val message = String(body!!, Charsets.UTF_8)
                      Log.e("TAG", "message is :$message")
                      //TODO 切换到主线程更新UI等
                      val msg: Message = handler.obtainMessage()
                      bundle.putString("msg", message)
                      msg.data = bundle
                      mHandler.sendMessage(msg)
                 }
              }
              channel.basicQos(1)
                })
            } catch (e: IOException) {
                e.printStackTrace()
            } catch (e: TimeoutException) {
                e.printStackTrace()
            }
        })
        thread.start()
    }

3.特殊的场景

原因

由于我们实际的应用场景是在工厂中,在生产异常后抛出一条异常消息,由工人接收并处理。由于RabbitMQ的”负载均衡“的工作模式,在绑定一个队列(Queue)的多个消费者(consumer)中,在一个消费者接收到消息后,其他消费者便不能再收到此条消息。也就是说,这种场景下,并不能实现所有工人都知悉异常产生这一需求。

解决方式:

常规实现,是由服务端创建队列并绑定交换机,再由客户端绑定这一队列,所以模式大概是这样的(图1):


图1.png

为了保证所有消费者都能收到消息,就需要让每个消费者处于不同的队列中。
如图2模式


图2.png

这样的话,创建队列的任务就要放在客户端实现。服务端只要将待发送的数据放到交换机上即可。在这里我们的做法是 :在每台机器程序第一次启动产生一个UUID并保存在本地,使用此UUID作为队列名。

fun startConsumer() {
        val QUEUE_NAME = QUEUE_PRIFIX+DEVICEID//DEVICEID即保存的UUID
        val thread = Thread(Runnable {
            try {
                //连接
                val connection = factory.newConnection()
                //通道
                val channel = connection.createChannel()
                channel.queueDeclare(QUEUE_NAME, false, false, false, null)
                channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_KEY)
                //实现Consumer的最简单方法是将便捷类DefaultConsumer子类化。可以在basicConsume 调用上传递此子类的对象以设置订阅:
                channel.basicConsume(QUEUE_NAME, false, object : DefaultConsumer(channel) {
                    override fun handleDelivery(
                        consumerTag: String?,
                        envelope: Envelope,
                        properties: AMQP.BasicProperties?,
                        body: ByteArray?
                    ) {
                      super.handleDelivery(consumerTag, envelope, properties, body)
                      val message = String(body!!, Charsets.UTF_8)
                      Log.e("TAG", "message is :$message")
                      //TODO 切换到主线程更新UI等
                      val msg: Message = handler.obtainMessage()
                      bundle.putString("msg", message)
                      msg.data = bundle
                      mHandler.sendMessage(msg)
                    }
                })
            } catch (e: IOException) {
                e.printStackTrace()
            } catch (e: TimeoutException) {
                e.printStackTrace()
            }
        })
        thread.start()
    }

可以看出,和常规做法的代码的差别主要在这两行:

channel.queueDeclare(QUEUE_NAME, false, false, false, null)
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, EXCHANGE_KEY)

通过方法名不难看出,第一行是声明一个队列,第二行是进行队列的绑定,分别传入队列名,交换机名,并通过EXCHANGE_KEY(路由key)实现队列和交换机的数据交换。
至此,我们的工作基本完成。

ps:路由key要和MQ的设置相同,并不能为空,否则无法接收

4.常见的问题:

Q1:

PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'queue_name' in vhost '/': received 'false' but current is 'true', class-id=50, method-id=10)

A1:

原因是使用不同的参数定义同一个队列
channel.queueDeclare(TASK_QUEUE_NAME, false, false, false, null);
解决方法:
channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);

(通常是客户端和服务端重复定义引起,只用一端定义队列也可以解决)

Q2:

Caused by: com.rabbitmq.client.ShutdownSignalException: connection error

A2:

浏览器中rabbitmq访问端口是15672,但是java访问端口却是5672.在访问时用的是15672所以报错。
解决方式:客户端连接端口改为5672

你可能感兴趣的:(RabbitMQ在android上的使用)