由于wmqtt.jar库在android4.0以上实现有问题会报MqttException Null异常,原因是该库只支持4.0以下版本。无奈只有寻找其他解决方案,最后选择的是Paho库中的client版本,org.eclipse.paho.client.mqttv3.jar。利用该库可以在android4.0以上正常连接Mqtt的服务器,博主用的android5.1进行实验的。
博主利用mqtt实现的主要功能是android端要与特定的智能硬件进行通信,而且是双向通信,android要给智能硬件发控制信息,智能硬件要给android返回状态信息;关于订阅与发布的id问题是其中的关键,android端要获取智能硬件的id,作为android发布信息的主题,在绑定完智能硬件之后,智能硬件发布以智能硬件的id+后缀作为智能硬件的发布信息id,而android端订阅智能硬件的id+后缀的主题。(启动的时候默认订阅服务器主题,服务器发送目前已经绑定了的智能硬件的主题,拉取主题列表。主题列表中有智能硬件在线、离线状态,更新客户端状态。外部设置进行其他智能硬件的主题订阅,在接口中添加主题订阅操作,在接口中添加自定义主题发布操作。)
android端为了每个活动都可以更方便得对确定主题的发布,与订阅主题的接收。在android建立一个基础活动类MqttBaseActivity,而其他需要进行mqtt操作的活动都继承该活动,减少活动中非主要业务代码冗余,提高代码可维护性。
要实现上述设想,需要进行3方面的工作:
1.MqttService类实现;
2.MqttBaseActivity类实现;
3.主活动调用示例;
MqttService提供给外界的接口主要包括内容订阅,自定义主题消息发送,全局广播消息接收。
package com.splxtech.powermanagor.engine;
import java.util.Locale;
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.MqttDefaultFilePersistence;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.internal.MemoryPersistence;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.provider.Settings.Secure;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.splxtech.powermanagor.IMqttService;
public class MqttService extends Service implements MqttCallback
{
public static final String DEBUG_TAG = "MqttService"; // Debug TAG
private static final String MQTT_THREAD_NAME = "MqttService[" + DEBUG_TAG + "]"; // Handler Thread ID
private static final String MQTT_BROKER = ""; // Broker URL or IP Address
private static final int MQTT_PORT = 1883; // Broker Port
public static final int MQTT_QOS_0 = 0; // QOS Level 0 ( Delivery Once no confirmation )
public static final int MQTT_QOS_1 = 1; // QOS Level 1 ( Delevery at least Once with confirmation )
public static final int MQTT_QOS_2 = 2; // QOS Level 2 ( Delivery only once with confirmation with handshake )
private static final int MQTT_KEEP_ALIVE = 240000; // KeepAlive Interval in MS
private static final String MQTT_KEEP_ALIVE_TOPIC_FORAMT = "/users/%s/keepalive"; // Topic format for KeepAlives
private static final byte[] MQTT_KEEP_ALIVE_MESSAGE = { 0 }; // Keep Alive message to send
private static final int MQTT_KEEP_ALIVE_QOS = MQTT_QOS_0; // Default Keepalive QOS
private static final boolean MQTT_CLEAN_SESSION = true; // Start a clean session?
private static final String MQTT_URL_FORMAT = "tcp://%s:%d"; // URL Format normally don't change
private static final String ACTION_START = DEBUG_TAG + ".START"; // Action to start
private static final String ACTION_STOP = DEBUG_TAG + ".STOP"; // Action to stop
private static final String ACTION_KEEPALIVE= DEBUG_TAG + ".KEEPALIVE"; // Action to keep alive used by alarm manager
private static final String ACTION_RECONNECT= DEBUG_TAG + ".RECONNECT"; // Action to reconnect
private static final String DEVICE_ID_FORMAT = "andr_%s"; // Device ID Format, add any prefix you'd like
// Note: There is a 23 character limit you will get
// An NPE if you go over that limit
private boolean mStarted = false; // Is the Client started?
private String mDeviceId; // Device ID, Secure.ANDROID_ID
private Handler mConnHandler; // Seperate Handler thread for networking
private MqttDefaultFilePersistence mDataStore; // Defaults to FileStore
private MemoryPersistence mMemStore; // On Fail reverts to MemoryStore
private MqttConnectOptions mOpts; // Connection Options
private MqttTopic mKeepAliveTopic; // Instance Variable for Keepalive topic
private MqttClient mClient; // Mqtt Client
private AlarmManager mAlarmManager; // Alarm manager to perform repeating tasks
private ConnectivityManager mConnectivityManager; // To check for connectivity changes
private LocalBroadcastManager localBroadcastManager;
public static final String MQTT_RECE_MESSAGE_ACTION = "com.splxtech.powermanagor.engine.mqttservice.recemessage";
/**
* Start MQTT Client
* @param
* @return void
*/
public static void actionStart(Context ctx) {
Intent i = new Intent(ctx,MqttService.class);
i.setAction(ACTION_START);
ctx.startService(i);
}
/**
* Stop MQTT Client
* @param
* @return void
*/
public static void actionStop(Context ctx) {
Intent i = new Intent(ctx,MqttService.class);
i.setAction(ACTION_STOP);
ctx.startService(i);
}
/**
* Send a KeepAlive Message
* @param
* @return void
*/
public static void actionKeepalive(Context ctx) {
Intent i = new Intent(ctx,MqttService.class);
i.setAction(ACTION_KEEPALIVE);
ctx.startService(i);
}
/**
* Initalizes the DeviceId and most instance variables
* Including the Connection Handler, Datastore, Alarm Manager
* and ConnectivityManager.
*/
@Override
public void onCreate() {
super.onCreate();
mDeviceId = String.format(DEVICE_ID_FORMAT,
Secure.getString(getContentResolver(), Secure.ANDROID_ID));
HandlerThread thread = new HandlerThread(MQTT_THREAD_NAME);
thread.start();
mConnHandler = new Handler(thread.getLooper());
try {
mDataStore = new MqttDefaultFilePersistence(getCacheDir().getAbsolutePath());
} catch(MqttPersistenceException e) {
e.printStackTrace();
mDataStore = null;
mMemStore = new MemoryPersistence();
}
mOpts = new MqttConnectOptions();
mOpts.setCleanSession(MQTT_CLEAN_SESSION);
// Do not set keep alive interval on mOpts we keep track of it with alarm's
mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
mConnectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
}
/**
* Service onStartCommand
* Handles the action passed via the Intent
*
* @return START_REDELIVER_INTENT
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
String action = intent.getAction();
Log.i(DEBUG_TAG,"Received action of " + action);
if(action == null) {
Log.i(DEBUG_TAG,"Starting service with no action\n Probably from a crash");
} else {
if(action.equals(ACTION_START)) {
Log.i(DEBUG_TAG,"Received ACTION_START");
start();
} else if(action.equals(ACTION_STOP)) {
stop();
} else if(action.equals(ACTION_KEEPALIVE)) {
keepAlive();
} else if(action.equals(ACTION_RECONNECT)) {
if(isNetworkAvailable()) {
reconnectIfNecessary();
}
}
}
return START_REDELIVER_INTENT;
}
/**
* Attempts connect to the Mqtt Broker
* and listen for Connectivity changes
* via ConnectivityManager.CONNECTVITIY_ACTION BroadcastReceiver
*/
private synchronized void start() {
if(mStarted) {
Log.i(DEBUG_TAG,"Attempt to start while already started");
return;
}
if(hasScheduledKeepAlives()) {
stopKeepAlives();
}
connect();
registerReceiver(mConnectivityReceiver,new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
/**
* Attempts to stop the Mqtt client
* as well as halting all keep alive messages queued
* in the alarm manager
*/
private synchronized void stop() {
if(!mStarted) {
Log.i(DEBUG_TAG,"Attemtpign to stop connection that isn't running");
return;
}
if(mClient != null) {
mConnHandler.post(new Runnable() {
@Override
public void run() {
try {
mClient.disconnect();
} catch(MqttException ex) {
ex.printStackTrace();
}
mClient = null;
mStarted = false;
stopKeepAlives();
}
});
}
unregisterReceiver(mConnectivityReceiver);
}
/**
* Connects to the broker with the appropriate datastore
*/
private synchronized void connect() {
String url = String.format(Locale.US, MQTT_URL_FORMAT, MQTT_BROKER, MQTT_PORT);
Log.i(DEBUG_TAG,"Connecting with URL: " + url);
try {
if(mDataStore != null) {
Log.i(DEBUG_TAG,"Connecting with DataStore");
mClient = new MqttClient(url,mDeviceId,mDataStore);
} else {
Log.i(DEBUG_TAG,"Connecting with MemStore");
mClient = new MqttClient(url,mDeviceId,mMemStore);
}
} catch(MqttException e) {
e.printStackTrace();
}
mConnHandler.post(new Runnable() {
@Override
public void run() {
try {
mClient.connect(mOpts);
mClient.subscribe("hello", 0);
mClient.setCallback(MqttService.this);
mStarted = true; // Service is now connected
Log.i(DEBUG_TAG,"Successfully connected and subscribed starting keep alives");
startKeepAlives();
} catch(MqttException e) {
e.printStackTrace();
}
}
});
}
/**
* Schedules keep alives via a PendingIntent
* in the Alarm Manager
*/
private void startKeepAlives() {
Intent i = new Intent();
i.setClass(this, MqttService.class);
i.setAction(ACTION_KEEPALIVE);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + MQTT_KEEP_ALIVE,
MQTT_KEEP_ALIVE, pi);
}
/**
* Cancels the Pending Intent
* in the alarm manager
*/
private void stopKeepAlives() {
Intent i = new Intent();
i.setClass(this, MqttService.class);
i.setAction(ACTION_KEEPALIVE);
PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
mAlarmManager.cancel(pi);
}
/**
* Publishes a KeepALive to the topic
* in the broker
*/
private synchronized void keepAlive() {
if(isConnected()) {
try {
sendKeepAlive();
return;
} catch(MqttConnectivityException ex) {
ex.printStackTrace();
reconnectIfNecessary();
} catch(MqttPersistenceException ex) {
ex.printStackTrace();
stop();
} catch(MqttException ex) {
ex.printStackTrace();
stop();
}
}
}
/**
* Checkes the current connectivity
* and reconnects if it is required.
*/
private synchronized void reconnectIfNecessary() {
if(mStarted && mClient == null) {
connect();
}
}
/**
* Query's the NetworkInfo via ConnectivityManager
* to return the current connected state
* @return boolean true if we are connected false otherwise
*/
private boolean isNetworkAvailable() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
return (info == null) ? false : info.isConnected();
}
/**
* Verifies the client State with our local connected state
* @return true if its a match we are connected false if we aren't connected
*/
private boolean isConnected() {
if(mStarted && mClient != null && !mClient.isConnected()) {
Log.i(DEBUG_TAG,"Mismatch between what we think is connected and what is connected");
}
if(mClient != null) {
return (mStarted && mClient.isConnected()) ? true : false;
}
return false;
}
/**
* Receiver that listens for connectivity chanes
* via ConnectivityManager
*/
private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(DEBUG_TAG,"Connectivity Changed...");
}
};
/**
* Sends a Keep Alive message to the specified topic
* @return MqttDeliveryToken specified token you can choose to wait for completion
*/
private synchronized MqttDeliveryToken sendKeepAlive()
throws MqttConnectivityException, MqttPersistenceException, MqttException {
if(!isConnected())
throw new MqttConnectivityException();
if(mKeepAliveTopic == null) {
mKeepAliveTopic = mClient.getTopic(
String.format(Locale.US, MQTT_KEEP_ALIVE_TOPIC_FORAMT,mDeviceId));
}
Log.i(DEBUG_TAG,"Sending Keepalive to " + MQTT_BROKER);
MqttMessage message = new MqttMessage(MQTT_KEEP_ALIVE_MESSAGE);
message.setQos(MQTT_KEEP_ALIVE_QOS);
return mKeepAliveTopic.publish(message);
}
/**
* Query's the AlarmManager to check if there is
* a keep alive currently scheduled
* @return true if there is currently one scheduled false otherwise
*/
private synchronized boolean hasScheduledKeepAlives() {
Intent i = new Intent();
i.setClass(this, MqttService.class);
i.setAction(ACTION_KEEPALIVE);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, PendingIntent.FLAG_NO_CREATE);
return (pi != null) ? true : false;
}
@Override
public IBinder onBind(Intent arg0) {
return iMqttService;
}
/**
* Connectivity Lost from broker
*/
@Override
public void connectionLost(Throwable arg0) {
stopKeepAlives();
mClient = null;
if(isNetworkAvailable()) {
reconnectIfNecessary();
}
}
/**
* Publish Message Completion
*/
@Override
public void deliveryComplete(MqttDeliveryToken arg0) {
}
/**
* Received Message from broker
*/
@Override
public void messageArrived(MqttTopic topic, MqttMessage message)
throws Exception {
Intent intent = new Intent(MQTT_RECE_MESSAGE_ACTION);
intent.putExtra("Topic",topic.getName());
intent.putExtra("Message",message.getPayload());
localBroadcastManager.sendBroadcast(intent);
Log.i(DEBUG_TAG," Topic:\t" + topic.getName() +
" Message:\t" + new String(message.getPayload()) +
" QoS:\t" + message.getQos());
}
/**
* MqttConnectivityException Exception class
*/
private class MqttConnectivityException extends Exception {
private static final long serialVersionUID = -7385866796799469420L;
}
private IMqttService.Stub iMqttService = new IMqttService.Stub(){
@Override
public boolean mqttSubscribe(String topic,int mqttQOS)
{
if(isConnected()) {
try {
mClient.subscribe(topic, mqttQOS);
return true;
} catch (MqttException e) {
e.printStackTrace();
return false;
}
}
else
{
return false;
}
}
@Override
public boolean mqttPubMessage(String topic,String Message,int mqttQOS)
{
if(isConnected())
{
MqttTopic mqttTopic = mClient.getTopic(topic);
MqttMessage message = new MqttMessage(Message.getBytes());
message.setQos(mqttQOS);
try{
mqttTopic.publish(message);
return true;
}
catch (MqttException e)
{
e.printStackTrace();
return false;
}
}
else {
return false;
}
}
};
}
2.MqttBaseActivity.class,里面包含iMqtt_Service接口绑定,广播接收器消息注册,调用基本思路是如果继承子类中有receiver的实现,则会调用接口绑定与广播消息接收器注册,如果没有receiver实现则认为在该activity中不需要实现mqtt的订阅、发布、接收任务。下面是具体实现代码:
package com.splxtech.powermanagor.Base;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import com.splxtech.powermanagor.IMqttService;
import com.splxtech.powermanagor.engine.MqttService;
import com.splxtech.splxapplib.activity.BaseActivity;
/**
* Created by li300 on 2016/10/19 0019.
*/
public abstract class MqttBaseActivity extends BaseActivity {
//全局消息接收器
public MessageMqttReciver mReciver;
private IntentFilter mIntentFilter;
private Intent mServiceIntent;
private LocalBroadcastManager localBroadcastManager;
//调用该接口中方法来实现数据发送与主题订阅
public IMqttService iMqttService;
//标记是否已经进行了服务绑定与全局消息注册
private boolean flag;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iMqttService = IMqttService.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iMqttService = null;
}
};
@Override
public void onStart()
{
flag = false;
if(mReciver!=null)
{
flag = true;
initMqtt();
localBroadcastManager.registerReceiver(mReciver,mIntentFilter);
bindService(mServiceIntent,conn,BIND_ABOVE_CLIENT);
}
super.onStart();
}
@Override
public void onDestroy()
{
if(flag==true)
{
unbindService(conn);
localBroadcastManager.unregisterReceiver(mReciver);
}
super.onDestroy();
}
public void initMqtt()
{
localBroadcastManager = LocalBroadcastManager.getInstance(this);
mServiceIntent = new Intent(this, MqttService.class);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(MqttService.MQTT_RECE_MESSAGE_ACTION);
}
public abstract class MessageMqttReciver extends BroadcastReceiver
{
@Override
public abstract void onReceive(Context context, Intent intent);
}
}