Android - Paho Mqtt client does not receive messages once network connectivity changes (mobile data disabled and enabled again)
Asked Answered
B

6

6

I am using Mosquitto Mqtt along with paho API to receive push messages on android device. But as soon as the network connectivity changes it stops receiving messages. Here are the steps to reproduce the issue with simple test case:

1) Create a simple activity.

2) On Activity StartUp connects to mosquitto test server (test.mosquitto.org:1883) through paho API.

3) Subscribe to some topic.

4) Publish some message to the topic.

Result: Mqtt Client receives all the messages published to the topic. Now

5) Disable internet connection on the mobile (mobile data)

6) Publish some message to the topic.

7) Reconnect internet.

Result: Client Does not receive any message published after internet connection was disabled.

As KeepAliveInterval has been kept to high value (30 minutes), it should receive all messages after reconnecting to the internet.

The same use case (same code) is working for simple java project (non-android) where I disable internet on my laptop to run the use case.

Any idea why it is not working on android device??? Am I missing something?

Note:

1) Using mqtt-client-0.4.1

2) Android target API level 11

3) Not putting device to sleep mode during the test.

4) Does not get any call in connectionLost callback and all the 4 threads of mqtt callback are running throughout the test case i.e connection the the mosquitto server is intact.

Barre answered 7/8, 2014 at 7:32 Comment(0)
L
4

The Java client library is at the mercy of the underlying networking API to a certain extent. When publish is called, it will write an MQTT packet to the socket. If that write fails, then connection lost will be called, if that write works then the client library will carry on. The difference in behaviour you are seeing is because the networking libraries are behaving differently in these circumstances.

The MQTT keepalive interval is meant to help with this. Under certain circumstances a TCP connection may appear to be live when it is not. This is especially possible on mobile or satellite connected devices - you can't expect the networking APIs to work exactly the same in all circumstances. Keepalive sends a ping packet to the server and expects a response - if that response is not received, the session is assumed to be closed.

If you set the keepalive interval to say 10 seconds, then the connection should be recognised as broken within 15 to 20 seconds.

Landmass answered 13/8, 2014 at 8:35 Comment(1)
Wouldn't a 10 second keepalive cause a significant battery drain on android?Sang
H
2

You can attach MqttCallback listener to MqttAsyncclient . It has callback method connection lost which will get called when connection lost event occured or paho disconnects.

Himmler answered 27/9, 2014 at 10:57 Comment(5)
Thanks Anubhav but I am aware of connection lost callback. Check out my fourth point in notes. To fix the issue, I had to come up with a workaround. Find my answer below.Barre
Yeah right. Are you sending qos1 or qos 0 messages because if you are trying to send qos0 packets in disconnected state they will not retried when you get into connected state. Also you should use your own persistence to store qos1 messages to be retried later, because persistence in paho has a bug. It will not store messages to perisistence if you are in disconnected stateHimmler
I am using qos2 and getting all the message once reconnected, so that's not an issue :)Barre
@anubhavgupta you mentioned about the paho bug in persistance, do you have reference to it?. I am having the issue you descrived ( mesg not persisted in disconnect state)Teleology
@Teleology there is sendnowait function in ClientComms class in paho lib. It basically check if client is connected before sending . The persistence steps comes after this in internalsend function. So if client is not connected then internalsend is not called and messages are not persistedHimmler
B
2

To fix the issue, I had to make an explicit ping to the broker whenever internet connection is back on (along with a timer to wait for ping response). If ping fails or timer goes out, I forcefully terminate the existing connection (disconnectForcibly) and then explicitly call connectionLost method. (Then reconnect from connectionLost method only).

Barre answered 29/9, 2014 at 5:39 Comment(0)
R
1

In Your Service :-

 //Receiver that notifies the Service when the phone gets data connection
  private NetworkConnectionIntentReceiver netConnReceiver;

Create the Following Class:-

/*
* Called in response to a change in network connection - after losing a
*  connection to the server, this allows us to wait until we have a usable
*  data connection again
*/
class NetworkConnectionIntentReceiver extends BroadcastReceiver
{
  private static  String TAG ="NetworkConnectionIntentReceiver";
  @Override
  public void onReceive(Context ctx, Intent intent)
  {
    // we protect against the phone switching off while we're doing this
    //  by requesting a wake lock - we request the minimum possible wake
    //  lock - just enough to keep the CPU running until we've finished

    PowerManager pm = (PowerManager) ctx.getSystemService(ctx.POWER_SERVICE);
    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MQTT");
    wl.acquire();

    Connection c = Connections.getInstance(ctx).getConnection(clientHandle);
    final ActionListener callback = new ActionListener(ctx,
                ActionListener.Action.CONNECT, clientHandle,null);
    c.getClient().setCallback(new MqttCallbackHandler(ctx, clientHandle,messenger_where_incoming_messages_tobe_sent));
    c.getClient().connect(c.getConnectionOptions(), null, callback);

    /*    The Above Reconnect Logic can be put up in a Reconnect() function.
     *    OR WRITE Any Other LOGIC TO RECONNECT TO MQTT
     */       

    // we're finished - if the phone is switched off, it's okay for the CPU
    //  to sleep now
    wl.release();
}

Now Call the Following Method Somewhere Appropriate in OnResume() or onCreate to Register the BroadcastReceiver.

synchronized void handleNetworkChange()
{

    // changes to the phone's network - such as bouncing between WiFi
    //  and mobile data networks - can break the MQTT connection
    // the MQTT connectionLost can be a bit slow to notice, so we use
    //  Android's inbuilt notification system to be informed of
    //  network changes - so we can reconnect immediately, without
    //  haing to wait for the MQTT timeout
    if (netConnReceiver == null)
    {
        netConnReceiver = new NetworkConnectionIntentReceiver();
        registerReceiver(netConnReceiver,
                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));

    }
}
Routinize answered 11/12, 2015 at 5:39 Comment(0)
T
1

I fixed recconect bug as follows (using rxJava2, but not required):

    public void reconnect() {
        Completable.create(emitter -> {
            while (!mqttClient.isConnected()) {
                mqttClient.connect(options, null, new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken asyncActionToken) {
                        emitter.onComplete();
                    }

                    @Override
                    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                        LogHelper.d(TAG,"try to connect failed");
                    }
                });

                Thread.sleep(2000);
            }
            emitter.onComplete();
        })
        .subscribeOn(Schedulers.io())
        .subscribe();
    }

and an example call

private BroadcastReceiver changeNetworkStateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Objects.equals(intent.getAction(), NetworkStateReceiver.EVENT_CHANGE_NETWORK_STATE)) {
            if(Utils.isOnline(context)) {
                mqttClient.reconnect();
            }
        }
    }
};
Tremble answered 26/3, 2018 at 18:21 Comment(0)
C
1

I already had this problem and fix it with check MqttAndroidClient connection and using .isConnected() in time intervals.

Coeducation answered 19/12, 2018 at 7:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.