남궁원
남궁원

Reputation: 13

Android MQTT mosquitto how to operate in background

I am now trying to use MQTT library and it's able that two other local hosts can communicate with one another (this sample code: https://github.com/bytehala/android-mqtt-quickstart)

But the only thing I have to resolve is that

While turned off, receiving messages is not available

Please, let me know how to operate in the background

my code mqttcallbackhandler.java

     public class MqttCallbackHandler implements MqttCallback {

  /** {@link Context} for the application used to format and import external strings**/
      private Context context;
  /** Client handle to reference the connection that this handler is attached to**/
  private String clientHandle;

          MainActivity main;

  /**
   * Creates an <code>MqttCallbackHandler</code> object
   * @param context The application's context
   * @param clientHandle The handle to a {@link Connection} object
   */
      public MqttCallbackHandler(Context context, String clientHandle)
      {
        this.context = context;
        this.clientHandle = clientHandle;
      }

  /**
   * @see org.eclipse.paho.client.mqttv3.MqttCallback#connectionLost(java.lang.Throwable)
   */
  @Override
  public void connectionLost(Throwable cause) {
//    cause.printStackTrace();
    if (cause != null) {
      Connection c = Connections.getInstance(context).getConnection(clientHandle);
      c.addAction("Connection Lost");
      c.changeConnectionStatus(ConnectionStatus.DISCONNECTED);

      //format string to use a notification text
      Object[] args = new Object[2];
      args[0] = c.getId();
      args[1] = c.getHostName();

      String message = context.getString(R.string.connection_lost, args);

      //build intent
      Intent intent = new Intent();
      intent.setClassName(context, "org.eclipse.paho.android.service.sample.MainActivity");
      intent.putExtra("handle", clientHandle);

      //notify the user
      Notify.notifcation(context, message, intent, R.string.notifyTitle_connectionLost);
    }
  }

  /**
   * @see org.eclipse.paho.client.mqttv3.MqttCallback#messageArrived(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage)
   */
  @Override
  public void messageArrived(String topic, MqttMessage message) throws Exception {

    //Get connection object associated with this object
    Connection c = Connections.getInstance(context).getConnection(clientHandle);

    //create arguments to format message arrived notifcation string
    String[] args = new String[2];
    args[0] = new String(message.getPayload());
    args[1] = topic+";qos:"+message.getQos()+";retained:"+message.isRetained();

    //get the string from strings.xml and format
    String messageString = context.getString(R.string.messageRecieved, (Object[]) args);

    //create intent to start activity
    Intent intent = new Intent();
    intent.setClassName(context, "org.eclipse.paho.android.service.sample.ConnectionDetails");
    intent.putExtra("handle", clientHandle);

    //format string args
    Object[] notifyArgs = new String[3];
    notifyArgs[0] = c.getId();
    notifyArgs[1] = new String(message.getPayload());
    notifyArgs[2] = topic;

    Log.d("won", "msg2=" + notifyArgs[1] + "");

    MainActivity.MessageReceive(notifyArgs[1] + "");

    //notify the user
//    Notify.notifcation(context, context.getString(R.string.notification, notifyArgs), intent, R.string.notifyTitle);
    //update client history
    c.addAction(messageString);

  }

  /**
   * @see org.eclipse.paho.client.mqttv3.MqttCallback#deliveryComplete(org.eclipse.paho.client.mqttv3.IMqttDeliveryToken)
   */
  @Override
  public void deliveryComplete(IMqttDeliveryToken token) {
    // Do nothing
  }

}

Upvotes: 1

Views: 7514

Answers (2)

k8C
k8C

Reputation: 424

From android Oreo, because of features like Doze, App Stanby, Battery Optimization, App Bucket and Battery Saver, normal service and Job Scheduler are not guaranteed to have network access and continuously running in background. To use MQTT, we need to use a foreground service together with a wakelock to always keep connection to server to check for messages even when the phone screen is off. https://developer.android.com/guide/components/services#Foreground

Upvotes: 2

Panos Nikolos
Panos Nikolos

Reputation: 403

well you could use a service for that that is executed when your application begins

In your manifest add this so that you declare your service

<service
        android:enabled="true"
        android:name=".Mqttservice"
/>

Mqttservice.java

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
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.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;


public class Mqttservice extends Service {
    private String ip="brokerip",port="1883";
    private final IBinder mBinder = new LocalBinder();
    private Handler mHandler;

private class ToastRunnable implements Runnable {//to toast to your main activity for some time
    String mText;
    int mtime;

    public ToastRunnable(String text, int time) {
        mText = text;
        mtime = time;
    }

    @Override
    public void run() {

        final Toast mytoast = Toast.makeText(getApplicationContext(), mText, Toast.LENGTH_LONG);
        mytoast.show();
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mytoast.cancel();
            }
        }, mtime);
    }
}

private static final String TAG = "mqttservice";
private static boolean hasWifi = false;
private static boolean hasMmobile = false;
private ConnectivityManager mConnMan;
    private volatile IMqttAsyncClient mqttClient;
private String uniqueID;


class MQTTBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        IMqttToken token;
        boolean hasConnectivity = false;
        boolean hasChanged = false;
        NetworkInfo infos[] = mConnMan.getAllNetworkInfo();
        for (int i = 0; i < infos.length; i++) {
            if (infos[i].getTypeName().equalsIgnoreCase("MOBILE")) {
                if ((infos[i].isConnected() != hasMmobile)) {
                    hasChanged = true;
                    hasMmobile = infos[i].isConnected();
                }
                Log.d(TAG, infos[i].getTypeName() + " is " + infos[i].isConnected());
            } else if (infos[i].getTypeName().equalsIgnoreCase("WIFI")) {
                if ((infos[i].isConnected() != hasWifi)) {
                    hasChanged = true;
                    hasWifi = infos[i].isConnected();
                }
                Log.d(TAG, infos[i].getTypeName() + " is " + infos[i].isConnected());
            }
        }
        hasConnectivity = hasMmobile || hasWifi;
        Log.v(TAG, "hasConn: " + hasConnectivity + " hasChange: " + hasChanged + " - " + (mqttClient == null || !mqttClient.isConnected()));
        if (hasConnectivity && hasChanged && (mqttClient == null || !mqttClient.isConnected())) {
                doConnect();

        }



    }
}


public class LocalBinder extends Binder {
    public Mqttservice getService() {
        // Return this instance of LocalService so clients can call public methods
        return Mqttservice.this;
    }
}

@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

public void publish(String topic, MqttMessage message) {
    SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);// we create a 'shared" memory where we will share our preferences for the limits and the values that we get from onsensorchanged
    try {

        mqttClient.publish(topic, message);

    } catch (MqttException e) {
        e.printStackTrace();
    }

}


@Override
public void onCreate() {

    mHandler = new Handler();//for toasts
    IntentFilter intentf = new IntentFilter();
    setClientID();
    intentf.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    registerReceiver(new MQTTBroadcastReceiver(), new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    mConnMan = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    Log.d(TAG, "onConfigurationChanged()");
    android.os.Debug.waitForDebugger();
    super.onConfigurationChanged(newConfig);

}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.d("Service", "onDestroy");

}


private void setClientID() {
    uniqueID = android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
    Log.d(TAG, "uniqueID=" + uniqueID);

}


private void doConnect() {
    String broker = "tcp://" + ip + ":" + port;
    Log.d(TAG, "mqtt_doConnect()");
    IMqttToken token;
    MqttConnectOptions options = new MqttConnectOptions();
    options.setCleanSession(true);
    options.setMaxInflight(100);//handle more messages!!so as not to disconnect
    options.setAutomaticReconnect(true);
    options.setConnectionTimeout(1000);
    try {

        mqttClient = new MqttAsyncClient(broker, uniqueID, new MemoryPersistence());
        token = mqttClient.connect(options);
        token.waitForCompletion(3500);

        mqttClient.setCallback(new MqttCallback() {
            @Override
            public void connectionLost(Throwable throwable) {
                try {
                    mqttClient.disconnectForcibly();
                    mqttClient.connect();
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void messageArrived(String topic, MqttMessage msg) throws Exception {
                Log.i(TAG, "Message arrived from topic " + topic);

                if (topic.equals("Sensors/message")) {


                } else if (topic.equals("Sensors/" + uniqueID)) {
                }
                else{

                }

            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
                System.out.println("published");
            }
        });

        mqttClient.subscribe("Sensors/" + uniqueID, 0);
        mqttClient.subscribe("Sensors/message", 0);

    } catch (MqttSecurityException e) {
        e.printStackTrace();
    } catch (MqttException e) {
        switch (e.getReasonCode()) {
            case MqttException.REASON_CODE_BROKER_UNAVAILABLE:
                mHandler.post(new ToastRunnable("WE ARE OFFLINE BROKER_UNAVAILABLE!", 1500));
                break;
            case MqttException.REASON_CODE_CLIENT_TIMEOUT:
                mHandler.post(new ToastRunnable("WE ARE OFFLINE CLIENT_TIMEOUT!", 1500));
                break;
            case MqttException.REASON_CODE_CONNECTION_LOST:
                mHandler.post(new ToastRunnable("WE ARE OFFLINE CONNECTION_LOST!", 1500));
                break;
            case MqttException.REASON_CODE_SERVER_CONNECT_ERROR:
                Log.v(TAG, "c" + e.getMessage());
                e.printStackTrace();
                break;
            case MqttException.REASON_CODE_FAILED_AUTHENTICATION:
                Intent i = new Intent("RAISEALLARM");
                i.putExtra("ALLARM", e);
                Log.e(TAG, "b" + e.getMessage());
                break;
            default:
                Log.e(TAG, "a" + e.getMessage());
        }
    }
    mHandler.post(new ToastRunnable("WE ARE ONLINE!", 500));

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.v(TAG, "onStartCommand()");
    return START_STICKY;
}

}

in your main in oncreate add this

Intent mymqttservice_intent = new Intent(this, Mqttservice.class);
        startService(mymqttservice_intent);

Upvotes: 3

Related Questions