Question

I'm developing an Android chat app in Java. Now I finally got my service to work but as soon as I fully kill the app the connection in my service dies.

I am using asmack as library for the XMPP connection. The goal is to receive messages even if the app is killed by the user (so it's not in the background).

It does work when I use a foreground service, but I don't want to use a foreground service because of high memory useage and because I don't want the foreground message in the notification center.

My service class

public class MessagingService extends Service {

private final String TAG = "MessagingService";
private final IBinder mBinder = new MessagingBinder();
public Context context;
public XMPPConnection Connection;
public static Handler mHandler = new Handler();

private final int ONGOING_NOTIFICATION_ID = 2344;

@Override
public void onCreate() {
}

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

@Override
public IBinder onBind(Intent intent) {
    Log.d(TAG, "onBind");
    return mBinder;
}

@Override
public boolean onUnbind(Intent intent) {
    Log.d(TAG, "onUnbind");
    return true;
}

@Override
public void onRebind(Intent intent) {
    super.onRebind(intent);
    Log.d(TAG, "onRebind");
}

@Override
public void onDestroy() {
}


public class MessagingBinder extends Binder {

    MessagingService getService() {
        Log.d(TAG + " - MessagingBinder", "getService");
        return MessagingService.this;
    }

}

public Boolean isConnected() {
    return (Connection != null);
}

public void Connect(final AuthorizeActivity authorize, final String username, final String password) {
    Thread XMPPConnect = new Thread(new Runnable() {

        public final String TAG = "XMPPConnect Thread";

        @Override
        public void run() {

            AndroidConnectionConfiguration connConfig = new AndroidConnectionConfiguration(Configuration.HOST, Configuration.PORT, Configuration.SERVICE);

            SmackConfiguration.setDefaultPingInterval(100);
            connConfig.setReconnectionAllowed(true);
            connConfig.setSASLAuthenticationEnabled(true);
            connConfig.setRosterLoadedAtLogin(true);

            Connection = new XMPPConnection(connConfig);

            try {
                Connection.connect();
                Log.i(TAG, "Connected to " + Connection.getHost());
            } catch (XMPPException ex) {
                Log.e(TAG, "Failed to connect to " + Connection.getHost());
                Log.e(TAG, ex.toString());
                Connection = null;
            }

            if(authorize != null)
                authorize.mServiceConnectCallback();

            if(username != null && password != null)
                Login(username, password, null);

        }

    });
    XMPPConnect.start();
}

public void Login(final String username, final String password, final AuthorizeActivity authorize) {
    Thread XMPPLogin = new Thread(new Runnable() {

        public final String TAG = "XMPPConnect Thread";

        @Override
        public void run() {

            try {
                Connection.login(username, password);
                Log.i(TAG, "Logged in as " + Connection.getUser());

                Presence presence = new Presence(Presence.Type.available);
                Connection.sendPacket(presence);

                PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
                Connection.addPacketListener(new PacketListener() {
                    @Override
                    public void processPacket(Packet packet) {
                        final Message message = (Message) packet;
                        if (message.getBody() != null) {
                            final String fromName = StringUtils.parseName(message.getFrom());
                            Log.i(TAG, "Text Recieved " + message.getBody() + " from " + fromName );

                            mHandler.post(new Runnable() {
                                public void run() {
                                    Receiver.recieveMessage(fromName, message.getBody());

                                    if(!VisibilityHelper.IsVisible()) {
                                        showNotification(fromName, message.getBody());
                                    }

                                }
                            });
                        }
                    }

                }, filter);

            } catch (XMPPException ex) {
                Log.e(TAG, "Failed to log in as " + "test");
                Log.e(TAG, ex.toString());
                Connection = null;
            }

            if(authorize != null)
                authorize.mServiceLoginCallback();

        }

    });
    XMPPLogin.start();
}

public void showNotification(String from, String message) {
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

    CharSequence notiText = message;
    long meow = System.currentTimeMillis();

    Notification notification = new Notification(R.drawable.ic_launcher, notiText, meow);

    Context context = getApplicationContext();
    CharSequence contentTitle = from;
    CharSequence contentText = message;
    Intent notificationIntent = new Intent(context, MainActivity.class);

    PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
    notification.flags = Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL;

    int SERVER_DATA_RECEIVED = 1;
    notificationManager.notify(SERVER_DATA_RECEIVED, notification);
}

public void Logout() {
    if(Connection.isConnected()) {
        Log.i(TAG, "Logout");
        Connection.disconnect();
    }
}

public HashMap<String, String> getVCard(String user) {
    Log.d(TAG, "getVCard");

    //String email = user + "@" + Configuration.HOST;
    String email = user;

    VCard card = new VCard();

    ProviderManager.getInstance().addIQProvider("vCard", "vcard-temp", new VCardProvider());

    try {
        card.load(MainActivity.mService.Connection, email);

        String jabber_id = card.getJabberId();
        String firstname = card.getFirstName();
        String middlename = card.getMiddleName();
        String lastname = card.getLastName();

        HashMap<String, String> vcard = new HashMap<String, String>();

        vcard.put("jabber_id", jabber_id);
        vcard.put("firstname", firstname);
        vcard.put("middlename", middlename);
        vcard.put("lastname", lastname);

        return vcard;
    } catch (XMPPException e) {
        e.printStackTrace();
    }

    return null;
}

public void retrieveContactsFromList() {
    if(this.isConnected()) {
        Roster roster = Connection.getRoster();
        Collection<RosterEntry> entries = roster.getEntries();

        for(RosterEntry entry : entries) {
            Receiver.onRetrieveContactFromList(entry);
        }
    }
}

}

My activity to start the service

public class ConnectionBinder extends FragmentActivity {

private final String TAG = "ConnectionBinder";
public static MessagingService mService;
public boolean mBound = false;

public Database DB;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if(!this.messagingServiceIsRunning())
    {
        startService(new Intent(this, MessagingService.class));
    }
}

private boolean messagingServiceIsRunning() {
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

    for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
        if (MessagingService.class.getName().equals( service.service.getClassName())) {
            return true;
        }
    }

    return false;
}

@Override
protected void onResume() {
    super.onResume();
    doBindService();
}

@Override
protected void onPause() {
    super.onPause();
    doUnbindService();
}

private void doBindService() {
    Intent intent = new Intent(this, MessagingService.class);
    bindService(intent, mMessagingService, Context.BIND_AUTO_CREATE);
}

private void doUnbindService() {
    if (mBound) {
        unbindService(mMessagingService);
    }
}

private void doXMPPLogin() {
    HashMap<String, String> user = DB.getUser();

    mService.Connect(null, user.get("username"), user.get("password"));
}

private ServiceConnection mMessagingService = new ServiceConnection() {

    public void onServiceConnected(ComponentName className, IBinder service) {
        Log.d(TAG, "mMessagingService.onServiceConnected()");
        MessagingBinder binder = (MessagingBinder) service;
        mService = binder.getService();
        mBound = true;

        if(!mService.isConnected()) {
            doXMPPLogin();
        }

        mService.retrieveContactsFromList();
    }

    public void onServiceDisconnected(ComponentName arg0) {
        Log.d(TAG, "mMessagingService.onServiceDisconnected()");
        mBound = false;
    }

};

}
Was it helpful?

Solution

Traditional XMPP implementations (and XMPP RFCs) does not define a way to maintain persistent user "sessions" when client disconnects - they all closing user session when underlying TCP/IP or HTTP connection is lost. On the other hand, typical Android enviroment have "always-connected" Google Cloud Services, which can deliver messages for your application even if it is not connected. In fact, most chat and social networking applications are using GCM to inform user about new messages. So, depend on your needs, you need to make some changes on the server side of your chat application:

  1. Most XMPP server implementations are able to store messages which was received when user was "offline" and delivers it when user connects again. You can "hook" offline message receiving and inform user via Google Cloud Messaging about availability of new messages, user will receive it when open your application again and your XMPPConnection will established.
  2. Use XMPP Stream Management extension - if you need to share same session across multiple user reconnections - and "resume" previous session when user open your app again. And you still should inform user about new events in his "session" via GCM.
  3. Your server-side XMPP software should keep GCM registration ids for every user device, so when user device is registered in GCM - you need to inform your server about newly registered id - it can be achieved by sending custom <iq> packet to server with your GCM id.

Some commercial XMPP products already implement steps above and will sell you "Push-enabled XMPP service" which is in fact XMPP server with GCM backend, as I describe.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top