最近公司有个项目,需要服务端发出消息,由android客户端作为消费者接收。网路上有很多关于android端作为发送端的实现,本文只讲关于android端作为消费者的实现方式
按照惯例,有问题先去官网找demo,于是我看到了这个:
附上地址,有兴趣的小伙伴可以自己去官方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):
为了保证所有消费者都能收到消息,就需要让每个消费者处于不同的队列中。
如图2模式
这样的话,创建队列的任务就要放在客户端实现。服务端只要将待发送的数据放到交换机上即可。在这里我们的做法是 :在每台机器程序第一次启动产生一个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