Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Paho MQTT Android Service Issue

I am implementing the Paho MQTT Android Service within an application I am developing. After testing the sample application provided by Paho, I have found that there are a few things that I would like to change.

https://eclipse.org/paho/clients/android/

The applications service appears to shut off once the application is fully closed. I would like to keep the service running even after the application closes in the event more messages come in. I also am looking for a way to open the application to a specific activity once a new message is received.

Here is one of the callbacks that is called when a message arrives, I have tried to implement a simple startActivity to open a specific activity but it does not work if the app is closed/no longer running.

If anyone has worked with the PAHO MQTT Android Service, Is there a specific way to keep the service from stopping when the application is closed, and how can I re-open the application when a message arrives?

    /**
   * @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;

    // notify the user
    Notify.notifcation(context, context.getString(R.string.notification, notifyArgs), intent,
        R.string.notifyTitle);

    // update client history
    c.addAction(messageString);

    Log.e("Message Arrived", "MESSAGE ARRIVED CALLBACK");

    // used to open the application if it is currently not active
    Intent i = new Intent(context, ConnectionDetails.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    i.putExtra("handle", clientHandle);
    context.startActivity(i);


  }
like image 673
Coova Avatar asked Feb 16 '15 14:02

Coova


People also ask

What is Paho Android service?

The Paho Android Service is an MQTT client library written in Java for developing applications on Android. To get started, download Android Studio. You will also need to download the Android SDK. Currently you will need the SDK for 19,21 and 22, This will hopefully be simplified soon.

What is PAHO MQTT library?

This code provides a client class which enable applications to connect to an MQTT broker to publish messages, and to subscribe to topics and receive published messages. It also provides some helper functions to make publishing one off messages to an MQTT server very straightforward. It supports Python 2.7.

How do I install PAHO?

Install the newest python from their website, install it on PATH. Verify that your installation was successful with cmd -> python (should show your version, i.e Python 3.8. 3) Install your package with cmd -> pip install paho-mqtt.


2 Answers

While this doesn't seem like a complete solution to the problem I will post my workaround in case it helps someone.

For me the problem begins when the user swipes the app out of the recent apps list. As mentioned here such an action not only kills the activity, instead it kills the whole process, including the MqttService. Then as mentioned in the thread Android recognizes that your service should be restarted and schedules a restart of the killed service. However, this doesn't imply connection recovery as all connections were bound to the activity.

So unless you find out a way to eliminate the service stopping issue you are guaranteed to lose connection to the broker when the user decides to swipe out the app.

This is not the end of the world however, as we can simply reconnect after we've lost connection. The problem is, this time we don't have an activity to do the desired action. You have to either amend the source code of the Paho Android service library, or in a much simpler fashion what I did was to create another service.

All the connections will take place in this new service, and any activities wishing to connect should communicate with this service. The advantage of this is we can make the service sticky and even if the user swipes our app and kills it, it will immediately restart and we can recover by simply reconnecting.

So as a demonstration with this very simple service:

public class MessagingService extends Service {
    private static final String TAG = "MessagingService";
    private MqttAndroidClient mqttClient;
    String deviceId;



    @Override
    public void onCreate() {
    }
    private void setClientID() {
        WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        WifiInfo wInfo = wifiManager.getConnectionInfo();
        deviceId = wInfo.getMacAddress();
        if (deviceId == null) {
            deviceId = MqttAsyncClient.generateClientId();
        }
    }

    public class MsgBinder extends Binder {
        public MsgServ getService() {
            return MsgServ.this;
        }
    }

    public void doConnect(){
        // Using some of the Paho sample app classes
        String server = ConfigClass.BROKER_URI;
        MemoryPersistence mem = new MemoryPersistence();
        mqttClient = new MqttAndroidClient(this,ConfigClass.BROKER_URI,deviceId,mem);
        MqttConnectOptions conOpt = new MqttConnectOptions();
        String clientHandle = server + deviceId;
        Connection con = new Connection(clientHandle, deviceId, ConfigClass.BROKER_ADDRESS,
                                        ConfigClass.BROKER_PORT, this, mqttClient, false);
        conOpt.setCleanSession(false);
        conOpt.setConnectionTimeout(ConfigClass.CONN_TIMEOUT);
        conOpt.setKeepAliveInterval(ConfigClass.CONN_KEEPALIVE);
        conOpt.setUserName("testclient");
        conOpt.setPassword("password".toCharArray());
        String[] actionArgs = new String[1];
        actionArgs[0] = deviceId;
        final ActionListener callback =
                new ActionListener(this, ActionListener.Action.CONNECT, clientHandle,
                                   actionArgs);
        mqttClient.setCallback(new MqttCallbackHandler(this, clientHandle));
        mqttClient.setTraceCallback(new MqttTraceCallback());
        con.addConnectionOptions(conOpt);
        Connections.getInstance(this).addConnection(con);
        try {
            mqttClient.connect(conOpt, null, callback);
            Log.d("Con", "Connected");
        } catch (MqttException e) {
            Log.d("Con", "Connection failed");
            e.printStackTrace();
        }
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        doConnect();
        return START_STICKY;
    }

}

Log of server:

1433455371: New client connected from 192.168.2.5 as ed:0a:2b:56:b5:45 (c0, k30, u'testclient').
1433455371: Sending CONNACK to ed:0a:2b:56:b5:45 (1, 0)
1433455375: Socket error on client ed:0a:2b:56:b5:45, disconnecting.
1433455377: New connection from 192.168.2.5 on port 1883.
1433455377: Client ed:0a:2b:56:b5:45 disconnected.
1433455377: New client connected from 192.168.2.5 as ed:0a:2b:56:b5:45 (c0, k30, u'testclient').
1433455377: Sending CONNACK to ed:0a:2b:56:b5:45 (1, 0)

As you can see as soon as I close the app and the service gets killed, it restarts reconnects and gets kept-alive just find afterwards. From here, you should be able to do the rest. Perhaps creating a notification with your newly arrived message which will open the app. Just remember to do everything in the newly created service which is guaranteed to maintain a connection.

like image 121
ᴘᴀɴᴀʏɪᴏᴛɪs Avatar answered Oct 03 '22 20:10

ᴘᴀɴᴀʏɪᴏᴛɪs


If you close your app using the task manager I don't think this is possible since "fully closing" the app will also stop any services it contains. Even though the service is started "sticky" it does not re-launch on my device. If you close the app by swiping it away on the recent tasks the service does remain running. See here for more info: Killing android application from task manager kills services started by app

However, I think the other problem is even if the service is still running the application contains the callback objects that get invoked by the service. If the application is no longer running the callback no longer exists and thus never gets called.

Here is a high level view of how I implemented this. This has been running in production for a few months but unfortunately I don't own the code and can't post it.

  • I created a singleton object that hosts the MQTTService/mqttAndroidClient. This exposes public methods to connect/disconnect, and contains the MqttCallback object used to receive the messages. It also handles the connection lost and retry mechanisms needed. This is the trickiest part but I can't post it here.
  • I created an Application object, I connect in onCreate() and close the connection in onTerminate()
  • I registered a BroadcastReceiver that gets the BOOT_COMPLETED action that resides in the Application object, it has an empty implementation but it spins up the application so the mqtt service connects on boot.

This eliminates the need for any given activity to be running to receive a message. It also seems resilient against closing the application, the exception being if you 'force close' it in the application settings. That makes since though since the user explicitly chose to close it.

like image 32
Hugh Jeffner Avatar answered Oct 03 '22 20:10

Hugh Jeffner