3

We prepared an Android application with a service that maintains MQTT connection with our server. The service returns a *START_STICKY* from its onStartCommand in order for Android to restart the service in case it kills the service for resource shortages. But the problem is, the service is killed very frequently by Android OS. It sometimes kills the service once in few seconds, even if no other process works on the device(with 2GB of ram). Why Android is killing my service so frequently? How can I lessen the number of restarts? My service should be killed as less as possible, because it disconnects my tcp connection and client have to reconnect again, causing quite a big load on our server. What can be wrong with this code? Thanks

public class GTAndroidMQTTService extends Service implements MqttCallback {

    private void init() {
        this.clientId = Settings.System.getString(getContentResolver(), Secure.ANDROID_ID);
    }

    @Override
    @Deprecated
    public void onStart(Intent intent, int startId) {
        logger("onStart() called");
        super.onStart(intent, startId);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        logger("onStartCommand() called");
        if (client == null) {
            try {

                init();

                conOpt = new MqttConnectOptions();
                conOpt.setCleanSession(false);
                conOpt.setUserName("...");
                conOpt.setPassword("...");

                try {
                    char[] keystorePass = getString(R.string.keystorepass).toCharArray();

                    KeyStore keyStore = KeyStore.getInstance("BKS");
                    keyStore.load(getApplicationContext().getResources().openRawResource(R.raw.prdkey),
                            keystorePass);

                    TrustManagerFactory trustManagerFactory = TrustManagerFactory
                            .getInstance(KeyManagerFactory.getDefaultAlgorithm());

                    trustManagerFactory.init(keyStore);

                    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
                            .getDefaultAlgorithm());
                    kmf.init(keyStore, keystorePass);

                    SSLContext sslContext = SSLContext.getInstance("TLS");

                    sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

                    conOpt.setSocketFactory(sslContext.getSocketFactory());
                } catch (Exception ea) {
                }

                client = new MqttClient(this.mqttURL, clientId, new MqttDefaultFilePersistence(folder));
                client.setCallback(this);

                conOpt.setKeepAliveInterval(this.keepAliveSeconds);

            } catch (MqttException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        if (intent == null) {
            Log.i("TAG", "Android restarted the service[START_STICKY]");
            if (client != null) {
                tryToEstablishConnection();
            }
        }
        return START_STICKY;
    }

    public void unsubscribe(String topicName) throws MqttException {
        try {
            client.unsubscribe(topicName);
        } catch (Exception e) {
            Log.i("TAG", "Unsubscribing from topic \"" + topicName + "has failed: " + e.toString());
        }
    }

    private void retry() {
        try {
            notifyUserWithServiceStatus("Status Changed", "Status", "Connecting");
            client.connect(conOpt);
            notifyUserWithServiceStatus("Status Changed", "Status", "User Connected #" + (++retrycnt));
        } catch (Exception e) {
            notifyUserWithServiceStatus("Status Changed", "Status", "Cannot Connect");
            e.printStackTrace();
        }
    }

    public void subscribe(String topicName, int qos) throws MqttException {
        try {
            client.subscribe(topicName, qos);
        } catch (Exception e) {
        }
    }

    public void disconnect() {
        try {
            client.disconnect();
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

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

    @Override
    public void onCreate() {
        logger("onCreate() called");
        super.onCreate();
    }

    @Override
    public void connectionLost(Throwable arg0) { // Connection lost
        notifyUserWithServiceStatus("Status Changed", "Status", "Connection Lost!");
        tryToEstablishConnection();
    }

    private void tryToEstablishConnection() {
        if (!retrying) {
            retrying = true;
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (;;) {
                        try {
                            if (isOnline() && !isConnected()) {
                                retry();
                                Thread.sleep(RETRY_INTERVAL);
                            } else if (isConnected()) {
                                retrying = false;
                                break;
                            } else if (!isOnline()) {
                                retrying = false;
                                break;
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }

    private class NetworkConnectionIntentReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context ctx, Intent intent) {
            PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
            WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MQTT");
            wl.acquire();

            if (isOnline() && !isConnected())
                notifyUserWithServiceStatus("Status Changed", "Status", "Online but not connected");
            else if (!isOnline())
                notifyUserWithServiceStatus("Status Changed", "Status", "Connection Lost!");

            tryToEstablishConnection();
            wl.release();
        }
    }

    private boolean isConnected() {
        try {
            return client.isConnected();
        } catch (Exception e) {
            return false;
        }
    }

    private boolean isOnline() {
        ConnectivityManager conMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo i = conMgr.getActiveNetworkInfo();
        if (i == null)
            return false;
        if (!i.isConnected())
            return false;
        if (!i.isAvailable())
            return false;
        return true;
    }

    @Override
    public void onDestroy() {
        logger("onDestroy() called");
        try {
            client.disconnect();
            Log.i("TAG", "Service stopped");
        } catch (MqttException e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken arg0) {
        // TODO Auto-generated method stub
    }
}
Alpay
  • 1,350
  • 2
  • 24
  • 56
  • Have you tried to add in the manifest `android:process=":different"`? It might be killed because your activity is being killed? I am not making sense right now! – Sherif elKhatib Oct 09 '13 at 14:22
  • I think a background service is kept running even if activity is killed. What does `android:process=":different"` do exactly? – Alpay Oct 09 '13 at 14:36
  • Runs the service on a different process. Still, I am very unsure. Another way of preventing the system from killing it (often) is to use `startForeground`. Check http://developer.android.com/reference/android/app/Service.html#startForeground(int, android.app.Notification) – Sherif elKhatib Oct 09 '13 at 14:44
  • I tried to make it a foreground service, but it is unacceptable for us to have a notification saying, "... is running" and we couldn' t prevent this from appearing on Android 4.3 devices (Nexus for example) – Alpay Oct 09 '13 at 14:58
  • Ok please add `android:process=":different"` to the manifest. For example `` – Sherif elKhatib Oct 09 '13 at 15:01
  • Ok, I' ll give it a try and tell you the result. Thank you – Alpay Oct 09 '13 at 15:06
  • Make sure not to run any long running task in `onStartCommand()` as per doc `Note that the system calls this on your service's main thread. A service's main thread is the same thread where UI operations take place for Activities running in the same process. You should always avoid stalling the main thread's event loop. When doing long-running operations, network calls, or heavy disk I/O, you should kick off a new thread, or use AsyncTask.` – M-Wajeeh Oct 09 '13 at 15:55
  • Can you add the code you are using to start the service? The service being killed could be related to how you are starting it. – rhyshort Oct 11 '13 at 12:34
  • `startService(new Intent(this, GTAndroidMQTTService.class));` is how I fire off the service. `this` here refers to the activity that starts the service – Alpay Oct 11 '13 at 13:47
  • does writing this inside the application class make a difference ?? startService(new Intent(this, GTAndroidMQTTService.class)); – Rat-a-tat-a-tat Ratatouille Oct 14 '13 at 06:14
  • @DharaShah can you please elaborate? What do you mean by application class? – Alpay Oct 14 '13 at 07:05
  • @Alpay an application class is one that extends The Application Class – Rat-a-tat-a-tat Ratatouille Oct 14 '13 at 07:54
  • any updates on this, Alpay? – Wayne Oct 22 '15 at 16:34

3 Answers3

0

It sounds as though your service is running in the Application process; is it directly tied to your Activity?

You'll want to run it a different process entirely; you can do this by adding the following declaration in your manifest:

<service
    android:name=".ServiceClassName"
    android:process=":yourappname_background" >

And then use the same android:process attribute for any receiver declarations as well.

Mike P.
  • 1,920
  • 1
  • 18
  • 20
0

Does binding to your service keep it alive?

Buddy
  • 10,874
  • 5
  • 41
  • 58
0

Some Background:

When you create a service you have to make sure your work is started in a background thread. IntentService runs on a background thread while a Service runs on the Main Thread.

A service runs in the main thread of the application that hosts it, by default

Source: http://developer.android.com/guide/components/services.html

Take a look at http://developer.android.com/guide/components/services.html#ExtendingIntentService

Read below for similar issues.

Similar answer in: Service vs IntentService

The Service can be used in tasks with no UI, but shouldn't be too long. If you need to perform long tasks, you must use threads within Service.

Also I would suggest reading CommonsWare's answer to How to always run a service in the background?

My Suggestion:

I would move to an IntentService or WakefulIntentService and consider updating information with an fixed interval instead of using a constant tcp connection. A HTTP based API could provide same information over SSL.

Community
  • 1
  • 1
ejohansson
  • 2,832
  • 1
  • 23
  • 30
  • So, how does Whatsapp and Whatsapp like applications handle these kind of situations? As far as I know, they use a modified XMPP variant and it indeed establish a TCP connection beneath. – Alpay Oct 21 '13 at 07:24