Android开发之利用MQTT协议实现消息的即时推送
1)轮询:应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池。
2)SMS:在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图。这是一个不错的想法,我就见过采用这个方案的 应用程序。这个方案的好处是,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,你很难找到免费的短消息发送网关,关于这个方案的实现,可以 参考如下链接:https://labs.ericsson.com/apis/mobile-java-push/。
3)持久连接:这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。Apple的推送服务之所以工作的很好,是因为每一台手机仅仅保 持一个与服务器之间的连接,事实上C2DM也是这么工作的。不过这个方案也存在不足,就是我们很难在手机上实现一个可靠的服务。Android操作系统允 许在低内存情况下杀死系统服务,所以你的通知服务很可能被操作系统Kill掉了。
前两个方案存在明显的不足,第三个方案也有不足,不过我们可以通过良好的设计来弥补,以便于让该方案可以有效的工作。毕竟,我们要知道GMail,GTalk以及GoogleVoice都可以实现实时更新的。
4)采用MQTT协议实现Android推送
MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。
详情参阅:http://www.360doc.com/content/13/0704/14/7471983_297588604.shtml
由于MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。于是经过筛选,本人决定使用MQTT协议实现消息的即时推送!
下面就是本人欲实现推送的几个重要步骤了:
1.服务器
欲实现消息推送,必不可少的就是服务器了,
本人采用的是Apollo的1.71版本的最新服务器,大家可以从Apollo根据自己操作系统的版本下载 最新的服务器,下载完成后,将其解压到一个路径后
第一步:打开cmd命令行窗口,然后cd到解压路径的bin目录下,输入apollo create mqttbroker(这个名字可以任取)然后回车,这个步骤是创建我们自己的mqtt服务器,创建完成后在我们的bin目录下会多出mqttbroker一个文件夹,里面包含所有的我们服务器的配置信息
第二步:进入到mqttbroker的mymqttbroker\etc路径下,将我们自己的tcp链接地址配置到apollo.xml文件中
第三步:在命令行窗口cd 到mqttbroker的mymqttbroker\bin路径下,输入apollo-broker run开启服务,想要确定我们的服务是不是开启成功,只需要在浏览器中输入http://127.0.0.1:61680/ or https://127.0.0.1:61681/,初始密码为admin/password,进入成功,则服务开启成功
2.客户端
这我就不多做介绍了,直接贴代码了
在Android客户端中MainAcitvity的代码如下:
package com.hxht.testmqttclient; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.KeyEvent; import android.widget.RemoteViews; import android.widget.TextView; import android.widget.Toast; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @SuppressWarnings("all") public class MainActivity extends AppCompatActivity { private TextView resultTv; private String host = "tcp://192.168.14.206:1883"; private String userName = "admin"; private String passWord = "password"; private int i = 1; private Handler handler; private MqttClient client; private String myTopic = "test/topic"; private MqttConnectOptions options; private ScheduledExecutorService scheduler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); resultTv = (TextView) findViewById(R.id.result); init(); handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 1) { Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show(); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification(R.drawable.icon, "Mqtt即时推送", System.currentTimeMillis()); notification.contentView = new RemoteViews("com.hxht.testmqttclient", R.layout.activity_notification); notification.contentView.setTextViewText(R.id.tv_desc, (String) msg.obj); notification.defaults = Notification.DEFAULT_SOUND; notification.flags = Notification.FLAG_AUTO_CANCEL; manager.notify(i++, notification); } else if (msg.what == 2) { System.out.println("连接成功"); Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT).show(); try { client.subscribe(myTopic, 1); } catch (Exception e) { e.printStackTrace(); } } else if (msg.what == 3) { Toast.makeText(MainActivity.this, "连接失败,系统正在重连", Toast.LENGTH_SHORT).show(); System.out.println("连接失败,系统正在重连"); } } }; startReconnect(); } private void startReconnect() { scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (!client.isConnected()) { connect(); } } }, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS); } private void init() { try { //host为主机名,test为clientid即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存 client = new MqttClient(host, "test", new MemoryPersistence()); //MQTT的连接设置 options = new MqttConnectOptions(); //设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接 options.setCleanSession(true); //设置连接的用户名 options.setUserName(userName); //设置连接的密码 options.setPassword(passWord.toCharArray()); // 设置超时时间 单位为秒 options.setConnectionTimeout(10); // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制 options.setKeepAliveInterval(20); //设置回调 client.setCallback(new MqttCallback() { @Override public void connectionLost(Throwable cause) { //连接丢失后,一般在这里面进行重连 System.out.println("connectionLost----------"); } @Override public void deliveryComplete(IMqttDeliveryToken token) { //publish后会执行到这里 System.out.println("deliveryComplete---------" + token.isComplete()); } @Override public void messageArrived(String topicName, MqttMessage message) throws Exception { //subscribe后得到的消息会执行到这里面 System.out.println("messageArrived----------"); Message msg = new Message(); msg.what = 1; msg.obj = topicName + "---" + message.toString(); handler.sendMessage(msg); } }); } catch (Exception e) { e.printStackTrace(); } } private void connect() { new Thread(new Runnable() { @Override public void run() { try { client.connect(options); Message msg = new Message(); msg.what = 2; handler.sendMessage(msg); } catch (Exception e) { e.printStackTrace(); Message msg = new Message(); msg.what = 3; handler.sendMessage(msg); } } }).start(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (client != null && keyCode == KeyEvent.KEYCODE_BACK) { try { client.disconnect(); } catch (Exception e) { e.printStackTrace(); } } return super.onKeyDown(keyCode, event); } @Override protected void onDestroy() { super.onDestroy(); try { scheduler.shutdown(); client.disconnect(); } catch (MqttException e) { e.printStackTrace(); } } }
Server.java源码如下:
package com.hxht;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
public class Server extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel panel;
private JButton button;
private MqttClient client;
private String host = "tcp://192.168.14.206:1883";
private String userName = "admin";
private String passWord = "password";
private MqttTopic topic;
private MqttMessage message;
private String myTopic = "test/topic";
public Server() {
try {
client = new MqttClient(host, "Server", new MemoryPersistence());
connect();
} catch (Exception e) {
e.printStackTrace();
}
Container container = this.getContentPane();
panel = new JPanel();
button = new JButton("发布话题");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
try {
MqttDeliveryToken token = topic.publish(message);
token.waitForCompletion();
System.out.println(token.isComplete() + "========");
} catch (Exception e) {
e.printStackTrace();
}
}
});
panel.add(button);
container.add(panel, "North");
}
private void connect() {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);
options.setUserName(userName);
options.setPassword(passWord.toCharArray());
// 设置超时时间
options.setConnectionTimeout(10);
// 设置会话心跳时间
options.setKeepAliveInterval(20);
try {
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
System.out.println("connectionLost-----------");
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("deliveryComplete---------"
+ token.isComplete());
}
@Override
public void messageArrived(String topic, MqttMessage arg1)
throws Exception {
System.out.println("messageArrived----------");
}
});
topic = client.getTopic(myTopic);
message = new MqttMessage();
message.setQos(1);
message.setRetained(true);
System.out.println(message.isRetained() + "------ratained状态");
message.setPayload("FlyingSnow2211----Forever".getBytes());
client.connect(options);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server s = new Server();
s.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
s.setSize(600, 370);
s.setLocationRelativeTo(null);
s.setVisible(true);
}
}
jar包下载地址:https://repo.eclipse.org/content/repositories/paho/org/eclipse/paho/mqtt-client/0.4.0/
OK,到了这里,我们基本就大功告成了,静静地测试一下,OK,连接成功,MQTT就是这个的简单
截图如下:
OK,到这里我们的基于MQTT协议实现的Android端的消息推送就大功告成了!
代码已贴出,正所谓取之于社会,回报于社会,还望各路大神批评指正,不喜勿喷,灰常感谢♪(^∇^*)
本Demo的服务端和客户端代码详情请参阅:
https://github.com/FlyingSnow2211/AndroidIMByMQTT