1

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
  }

}
Chetan Gaikwad
  • 1,268
  • 1
  • 13
  • 26
남궁원
  • 13
  • 1
  • 8

2 Answers2

3

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);
Panos Nikolos
  • 333
  • 3
  • 11
2

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

k8C
  • 414
  • 4
  • 9
  • are you familiar with an Android MQTT library which uses the new service approach (startForegroundService instead of startService)? I'm using Paho and failing to start the service when Android Boot is done. – Tomer Petel Aug 19 '19 at 18:25
  • @TomerPetel I use Paho mqtt java library, I don't know any other library. Start apps at BOOT is simple, read this https://codinginflow.com/tutorials/android/start-app-on-boot. On some Chinese devices you may need to allow apps to start at boot in settings. I have a mqtt app which also start a foreground service at BOOT to process incoming messages – k8C Aug 20 '19 at 16:36
  • I already managed to run at BOOT but when I try to connect it'll start a service and will cause the following error (Starting Android 8.0): `Not allowed to start service Intent`. [this](https://stackoverflow.com/questions/46445265/android-8-0-java-lang-illegalstateexception-not-allowed-to-start-service-inten) describes it well. Didn't encounter it? – Tomer Petel Aug 20 '19 at 17:53
  • Maybe you didn't call _startForegroundService_ in Android 8 and above. I do it like this: `if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(new Intent(context, MqttService.class)); else context.startService(new Intent(context, MqttService.class));` Remember to call _startForeground_ in _onStartCommand_ or _onCreate_ of the service – k8C Aug 21 '19 at 04:37
  • maybe I'm missing something here but AFAIK the `connect` method starts a service (which I can't change unless I'll fork and change the library itself) and not foregroundService. I tried to connect once BOOT is done and got the error. – Tomer Petel Aug 21 '19 at 07:25
  • 1
    @TomerPetel oh I understand why, you are using the Paho Android library, it's very old and not support newer Android versions. Use the Java one, the API is almost the same but you have full control over services and threading https://www.eclipse.org/paho/clients/java/ – k8C Aug 21 '19 at 07:42
  • I switched to the Java version - thx for that! I'm having another issue... I want to connect the the MQTT broker at BOOT and stay connected but when working on Oreo and up when calling `startForegroundService` you need to call `startForeground` within 5 seconds . What to do if I just want to keep connected without bringing the app to the foreground or present a notification? – Tomer Petel Aug 27 '19 at 10:25
  • AFAIK, no way to do that after Oreo. But you can try WorkManager or AlarmManager, I've read that they are better than Service in some cases. – k8C Aug 27 '19 at 16:13