It is somewhat of an old question but I have personally struggled with maintaining a persistent connection using Smack in the past and had to figure out the answers based on multiple resources. I will try to address my findings in this answer.
Notice - do not use isConnected()
method of your AbstractXMPPConnection
to determine whether the connection is active. This is a mere internal state of the object. It has no real way of knowing whether a connection is actually active.
The only way to be sure is by pinging, either by the server or by the client.
The persistent connection route - NOT recommended
First thing to understand - if you indeed want a persistent connection with the server (and I've been this route and do not recommend it - more about it below), you have to do things manually.
On the server side - make sure you disconnect clients that are idle after a reasonable amount of time (too long and you will lose messages, as the connection will be terminated but the server will still consider it active. too short and you will have repeating broken connections if your users have poor internet connection. I personally user 30 seconds. I guess anything between 30 and 60 seconds is fine). This will allow you to detect on the client side when connection with the server is lost.
Second thing to do is implement the ConnectionListener
Smack interface. It contains callbacks to various connection events such as connectionClosedOnError
that allow you to resume your connection with the server.
Then you should listen to network connectivity events - if the user has changed networks you should probably disconnect and reconnect again.
This is arguable, but I personally had problems with keeping the connection alive this way, relying on server pings and callbacks. If you have too - you should ping the server from the client to be certain.
Smack has the ServerPingWithAlarmManager
class precisely for that - it pings the server every 30 minutes. Alas, for me this did not work, as some OEMs (Xiaomi I am looking at you) have restricted background tasks in their custom Android skins. The way that worked for me was to ping the server using a custom PingManager. Get it using PingManager.getInstanceFor(connection)
and schedule it to run every X minutes (7 minutes worked for me) using an AlarmManager
.
For this ping manager you should register a PingFailedListener
that will reconnect on its pingFailed()
method.
All this sounds bad? here is the worst part - the whole things falles apart on api >= 23. This all works fine until Doze and App Standby come into play. This is why if you go the persisted connection route - you have to ask for the dreaded ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
permission which allows your app to bypass Doze and App Standby.
Congratulations! assuming Google did not ban your app from the Play Store for asking for the whitelist battery optimizations permission recklessly - you now have a reliable and persistent xmpp connection with your server inside an app that drains battery like crazy even when the user does nothing to engage with it for a long period of time.
Not an ideal solution, as I have previously stated.
The better solution - FCM high-priority messages
After understanding the problems with persisting a connection with the server - this is the method I recommend using.
Do not try to fight the network connection and the battery optimizations that get more restrictive with each Android version.
Integrating FCM messages into your server will allow your users to receive the messages in real time even when the device is in Doze mode.
This is the approach I use nowadays and it works pretty well with the benefit of much much lower battery drainage.
I do not ping the server, and the server does not ping my app. when the device changes networks or the connection is closed due to am error - I just disconnect the connection.
While connected, messages are received by a Message
type StanzaListener
.
While disconnected, messages are received by the FirebaseMessagingService()
implementation.
No need to persist the connection. No need to fight the system. No huge battery drainage.
Hope this answer helps somebody in the future and saves them some time, as it took me a lot of time, effort and trial and error to get to a solution I am satisfied with.