移动终端作为一种低功耗、断续连接网络的设备,实现服务器主动向移动终端推送消息,是每一个移动APP开发中必不可少的一个功能。针对移动端消息推送常见的解决思路有三种:
轮询(Pull)方式:客户端定时向服务器发送询问消息,一旦服务器有变化则立即同步消息。
推送(Push)方式:移动终端现在服务器端注册并告知关注的消息主体,服务器获得相关的消息之后,根据主体主动推送给移动终端。
常连接方式:移动终端与服务器端保持常连接,保证消息下发的及时性。
考虑网络流量的问题,推送方式在一般情况下是向移动APP发送消息最好的实现方式。为此,移动APP开发中常用如下几种具体解决方案:
C2DM云端推送方案, C2DM是Google为Android 2.2以上系统专门开发的,简单易用。但是发送消息数量和大小有限制,另外国内网络环境也导致APP开发采用此种解决方案风险很大。
基于MQTT协议的推送方案,MQTT是由IBM主导开发和倡导的移动端消息推送协议,是一个轻量级的消息发布/订阅协议,它是实现基于移动APP的消息推送服务器的理想解决方案。
另外还有其他一些解决方案,例如:Apple Push Notification Service (APNS)、RSMB实现推送等。
本文重点讲述基于MQTT协议的移动APP消息推送。MQTT是开源协议,关于协议的具体内容可以详细参考:http://mqtt.org/。基于MQTT协议实现的服务器端软件也比较多,开发人员可以根据自己的开发环境、熟悉程度选择,具体软件介绍可以参考:https://github.com/mqtt/mqtt.github.io/wiki/servers。
基于MQTT协议实现的软件,其消息推送的基本过程均如下:
基于MQTT实现消息推送,选择合适的MQTT Broker是第一步,在我们的测试实验中模拟中,采用Apollo,Apollo是一款支持Java开发开源高性能MQTT服务端实现,关于Apollo详细介绍和安装说明可以参考:http://activemq.apache.org/apollo/documentation/mqtt-manual.html。
为此,我们在如下程序中实现的MQTT消息推送基本场景如下:
1、定义ApolloMessagePublish包,采用简单的JAVA实现模拟Server发送消息给Apollo Broker。
2、定义ApolloMessagerServer包,基于Android 5.1 API开发,实现手机端消息接收。
针对Android手机端的消息接受,我们又分为两部分程序,它们分别是:
MainActivity,用户手机界面显示,我们将把从Apollo获取到的消息,及时在Textview上显示。
MQTTService,Android后台运行Service,MainActivity启动之后,一直在后台运行,获取Apollo下发的消息。
MainActivity与MQTTService之间的通讯,利用Messenger功能实现。
针对ApolloMessagerServer Android端的类图设计如下:
代码展示:
ApolloMessagePublish,包含三个类:Test, SendMessageThread, ApolloMessagePublish,其总核心是ApolloMessagePublish。具体实现代码如下:
public class Test{
public static void main(String[] args){
/*
ApolloMessagePublish MQTTServerDemo=ApolloMessagePublish.getInstance();
MQTTServerDemo.sendMessage();
System.out.println("Successful!");
*/
SimpleDateFormat simpleDateFormat=null;
simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
new SendMessageThread("this is a test,here for your message12345."+simpleDateFormat.format(new Date())).start();
System.out.println("Successful!");
}
}
public class SendMessageThread extends Thread {
private String message;
public SendMessageThread(String message){
this.message=message;
System.out.println("initing is Ok:"+message.toString());
}
public void run(){
ApolloMessagePublish.getInstance().sendMessage(this.message);
System.out.println("Message Sent Successfully:"+this.message.toString());
}
}
public class ApolloMessagePublish {
// 定义变量
private String host = "tcp://localhost:61613";
private String userName = "admin";
private String password = "password";
private MqttClient client;
private MqttTopic topic;
private String myTopic="Topics/htjs/serverToPhone";
private MqttMessage message;
private static ApolloMessagePublish instance = null;
public static ApolloMessagePublish getInstance() {
if (instance == null)
instance = new ApolloMessagePublish();
return instance;
}
private ApolloMessagePublish(){
try {
client=new MqttClient(host, "Server", new MemoryPersistence());
//Create MqttConnectOption and 初始化
MqttConnectOptions options=new MqttConnectOptions();
options.setCleanSession(false);
options.setUserName(userName);
options.setPassword(password.toCharArray());
options.setConnectionTimeout(10);
options.setKeepAliveInterval(20);
client.setCallback(new MqttCallbackHandler());
client.connect(options);
} catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void sendMessage(String msg){
message=new MqttMessage();
message.setQos(1);
message.setRetained(true);
message.setPayload(msg.getBytes());
topic=client.getTopic(myTopic);
try {
MqttDeliveryToken token=topic.publish(message);
token.waitForCompletion();
} catch (MqttPersistenceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ApolloMessagerServer是Android客户端,包含两个类:MainActivity, MQTTService,其中核心是MqttService,具体代码如下:
public class MainActivity extends Activity {
//用于日志输出的名称
private static String TAG = "ClientActivity";
//定义初始化变量
private TextView tv;
private Messenger sMessenger;
private boolean isConn;
private MainActivity MA;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取Activity的视窗
this.tv=(TextView)this.findViewById(R.id.Service_Monitor);
//启动消息订阅Services
this.MA=this;
Log.i(TAG, "----->onCreate finished");
//启动Service
Intent intent = new Intent(MainActivity.this,MQTTService.class);
bindService(intent,conn,Context.BIND_AUTO_CREATE);
Log.i(TAG, "bindService invoked !");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
//获取服务端传递过来的消息,并显示在TextView上
private Messenger cMessenger = new Messenger(new Handler() {
// 获取Service发送过来的消息进行处理
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "----->Client processes message.");
switch (msg.what) {
case 0:
// 此为示例,仅仅显示获取到的消息
String str=(String)msg.obj;
if (str != null)
tv.setText(tv.getText() + "\n" + str);
break;
}
}
});
// 定义Connection,作为启动Services的参数
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
sMessenger = new Messenger(service);
MA.isConn = true;
MA.setTitle("Service is connected.");
Log.i(TAG, "----->ServiceConnection isConn is true");
// 初始化发送给Service的消息,并将cMessenger传递给Service
Message msg = Message.obtain();
msg.replyTo = cMessenger;
msg.what = 0;
try {
sMessenger.send(msg);
Log.i(TAG, "----->Client sent Message to Service");
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
sMessenger = null;
MA.isConn = false;
MA.setTitle("Service is disconnected.");
Log.i(TAG, "----->ServiceConnection isConn is flase");
}
};
}
public class MQTTService extends Service implements MqttCallback {
//用于日志输出的名称名称
private static String TAG = "MQTTService";
//客户端Messenger
private Messenger cMessenger;
// variables for connect to apollo MQ
private String host = "tcp://Host IP:61613";
private String userName = "admin";
private String password = "password";
private MqttClient client;
private MqttConnectOptions connectOptions;
//订阅和发布主题
private String[] myTopic = { "Topics/htjs/phoneToServer", "Topics/htjs/serverToPhone" };
private int[] myQos = { 1, 1 };
private ScheduledExecutorService scheduler;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return sMessenger.getBinder();
}
@Override
public void onCreate() {
Log.i(TAG, "---->onCreate");
init();
}
@Override
public void onDestroy(){
Log.i(TAG, "------>onDestroy");
}
public void init() {
Log.i(TAG,"----->init at host:"+host);
try {
client = new MqttClient(host, "test", new MemoryPersistence());
//初始化连接参数
connectOptions = new MqttConnectOptions();
connectOptions.setUserName(userName);
connectOptions.setPassword(password.toCharArray());
connectOptions.setConnectionTimeout(10);
connectOptions.setKeepAliveInterval(20);
connectOptions.setCleanSession(false);
//设置回调对象,显示获取到的消息信息
client.setCallback(this);
} catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void connect(){
Log.i(TAG,"------>connect");
if(client.isConnected())
{
Log.i(TAG, "---->connect: "+client.isConnected());
}else{
new Thread(new Runnable(){
@Override
public void run(){
try{
// 设置连接参数
client.connect(connectOptions);
// 根据订阅获取消息
client.subscribe(myTopic, myQos);
Log.i(TAG, "connect Successful");
} catch (MqttSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
).start();
}
}
//below is the override function for MqttCallback
@Override
public void connectionLost(Throwable throwable) {
// TODO Auto-generated method stub
Log.i(TAG, "------>connectionLost");
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// TODO Auto-generated method stub
Log.i(TAG,"deliveryComplete---------" + token.isComplete());
}
@Override
public void messageArrived(String topicName, MqttMessage message) throws Exception {
// TODO Auto-generated method stub
Log.i(TAG,"-------->messageArrived:" + message.toString());
//将消息加入消息队列
Message msg=Message.obtain();
msg.what=0;
msg.obj=message.toString();
cMessenger.send(msg);
Log.i(TAG, "------>Server sent message to client");
}
// 定义Handler,重载Handler的消息处理方法
private Handler mHandler = new Handler() {
// 处理消息内容的具体方法定义
@Override
public void handleMessage(Message msg) {
// 定义反馈给客户端的消息内容
switch (msg.what) {
case 0:
cMessenger = msg.replyTo;
Log.i(TAG, "---> 获取客户端Messenger成功");
connect();
}
super.handleMessage(msg);
}
};
// 初始化Messenger,用于消息发送和接收
private Messenger sMessenger = new Messenger(mHandler);
}