12

On Android, I have an Activity called FirstActivity which starts a Service named MyService to do networking stuff in the background. The Activity and the Service communicate with each other all the time by calling methods.

Now when the user navigates from FirstActivity to SecondActivity, the background service should not be killed or re-created, but kept alive and passed to SecondActivity which will now be the one communicating with the service.

In other words, the Service shall be running as long as one of the two Activitys is running, and it should not stop while the user navigates between the two Activitys.

One of the Activitys will always be in the foreground and during this time, the service should (optimally) never get killed. I think this should not be a problem because one of those two Activitys is always active and thus Android knows the service is important and not something that must be killed.

(If there was no way to prevent Android from killing and re-creating the service from time to time, I would need a way to restore the full state of the service gracefully.)

To sum up, the Service should have the same lifespan as the two Activitys "combined". It should start with the first of them and stop not before both of them have been destroyed.

So is the following code correct for that setup and goals?

public class MyService extends Service {

    public class LocalBinder extends Binder {
        public MyService getService() {
            return MyService.this;
        }
    }

    ...

}

public class FirstActivity extends Activity {

    private MyService mMyService;

    private ServiceConnection mMainServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            MyService mainService = ((LocalBinder) service).getService();
            mMyService = mainService;
            mMyService.setCallback(FirstActivity.this);
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            mMyService = null;
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        startService(new Intent(FirstActivity.this, MyService.class));
    }

    @Override
    protected void onResume() {
        super.onResume();
        bindService(new Intent(FirstActivity.this, MyService.class), mMainServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mMainServiceConnection != null) {
            unbindService(mMainServiceConnection);
        }

        if (mMyService != null) {
            mMyService.setCallback(null);
        }

        if (!isUserMovingToSecondActivity) {
            stopService(new Intent(FirstActivity.this, MyService.class));
        }
    }

    @Override
    public void onBackPressed() {
        stopService(new Intent(FirstActivity.this, MyService.class));
        super.onBackPressed();
    }

    ...

}

public class SecondActivity extends Activity {

    private MyService mMyService;

    private ServiceConnection mMainServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            MyService mainService = ((LocalBinder) service).getService();
            mMyService = mainService;
            mMyService.setCallback(SecondActivity.this);
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            mMyService = null;
        }
    };

    @Override
    protected void onResume() {
        super.onResume();
        bindService(new Intent(SecondActivity.this, MyService.class), mMainServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mMainServiceConnection != null) {
            unbindService(mMainServiceConnection);
        }
    }

    @Override
    protected void onDestroy() {
        ...
        stopService(new Intent(SecondActivity.this, MyService.class));
    }

    ...

}

Is this the best way to guarantee a long-lasting service in the background of the Activitys that will not be killed or re-created?

What about Context.BIND_AUTO_CREATE? Is it correct to have this flag set here? What about Context.BIND_ADJUST_WITH_ACTIVITY and Context.BIND_WAIVE_PRIORITY -- do I need these?

caw
  • 30,999
  • 61
  • 181
  • 291
  • Have you tried that code or are you asking before trying? If you did try, did you get any errors? Did it not behave as you expected? We're gonna have a much more productive discussion and a more useful answer for the community and yourself if you actually try it and then ask us for the solution a concrete problem you have – DallaRosa Dec 19 '14 at 07:05
  • @DallaRosa Yes, of course, I have tried this. This is a general question as to how to design a service class and the corresponding `Activity` classes so that the service lasts as long as possible and is not killed. Users have reported that features provided by this service stop being available in one of the `Activity` classes from time to time. And I (a) find it hard to debug when the syste might kill this service and (b) would like to know about some best practices or improvements that should be applied to this service for the well-defined goals and requirements. – caw Dec 19 '14 at 07:21
  • I can see lots of issues in your management of the `Service`. You need to stop it only when the first `Activity` is destroyed, or depend on the binding and manage that properly. There are some general instructions on this in the Beginning Android books: https://books.google.com.pk/books?id=mRGrCQbqHkoC&pg=PA399 – corsair992 Dec 20 '14 at 18:32
  • @corsair992 Thanks. If you see "lots of issues", can you name some? Regarding your second sentence: It doesn't seem to make a difference if I call `stopService(...)` in the second `Activity`'s `onDestroy(...)` as well or not, does it? – caw Dec 23 '14 at 18:39
  • **Major issues in your management of the `Service`:** You are stopping the `Service` in both the `onBackPressed()` and `onPause()` callbacks of the first `Activity`. The latter will cause the `Service` to be destroyed and recreated whenever the `Activity` is recreated due to configuration changes. You also stop the `Service` in the `onDestroy()` callback of the second `Activity`, which will cause the `Service` to be destroyed whenever you return to the first `Activity`. There are some other minor semantic issues, but they aren't critical. Most of the issues are addressed in matiash's answer. – corsair992 Dec 23 '14 at 19:06

1 Answers1

7

(Many thanks to @corsair992 for his useful pointers!)


If the activities are always called in that order (i.e. FirstActivity starts SecondActivity, and never the other way around, then you should, basically, attempt to "tie" the Service's life-cycle to FirstActivity's lifecycle.

In general (see caveats later), this means:

  • Call startService() in FirstActivity.onCreate().
  • Call stopService() in FirstActivity.onDestroy().
  • Call bindService()/unbindService() in the onStart()/onStop() methods of both Activities (to get access to the Binder object, and be able to call methods on it).

A service started this way will be alive until stopService() is called and every client unbinds, see Managing the Lifecycle of a Service:

These two paths are not entirely separate. That is, you can bind to a service that was already started with startService(). (...) In cases like this, stopService() or stopSelf() does not actually stop the service until all clients unbind.

and:

When the last client unbinds from the service, the system destroys the service (unless the service was also started by startService()).

With this basic strategy, the Service will live as long as FirstActivity is around (i.e. it is not destroyed). However, an important point still remains: in the event of a configuration change (e.g. a screen rotation) that is not handled explicitly will cause the activity to restart itself, and the service will be destroyed (since we're calling stopService() in onDestroy()).

To prevent this, you can check isChangingConfigurations() before actually stopping the service (since an onDestroy() callback occurring due to this reason means that although this particular instance of the Activity is being destroyed, it will be recreated afterwards.

Hence, the full solution would be something like:

public class FirstActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService(new Intent(this, MyService.class));
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() { ... }

    @Override
    protected void onStart() {
        super.onStart();
        bindService(new Intent(this, MyService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        unbindService(mServiceConnection);
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        if (!isChangingConfigurations())
            stopService(new Intent(this, MyService.class));

        super.onDestroy();
    }

While SecondActivity would only implement the onStart()/onStop() methods (in the same way).


A couple of notes about your particular implementation:

  • It's not necessary to override onBackPressed(), since if the activity is destroyed the necessary lifecycle methods will be called (plus, it could be finished without pressing the back button, for example if calling finish() on it).
  • Stopping the service in onDestroy() instead of onPause() saves you from having to check for isUserMovingToSecondActivity.
matiash
  • 54,791
  • 16
  • 125
  • 154
  • Thanks! (1) For our purpose, you can use `onStart()`/`onStop()` and `onResume()`/`onPause()` interchangeably, can't you? Otherwise, the second pair of methods seems superior because it is *guaranteed* to be called, in contrast to the first pair, right? (2) Does the check for `isChangingConfigurations()` really make a difference? If `FirstActivity` is destroyed, we lose the `mMyService` member variable anyway, and `onCreate(...)` and `onStart()` are called again (which also means you could bind twice). And in `SecondActivity`, we lose the connection in any case. – caw Dec 23 '14 at 18:56
  • 1
    @MarcoW. (1) `onStop()` may not arrive, but AFAIK that is only if the app is being killed, in which case the service will too. (2) You will lose the local binding, but the service isn't actually destroyed and then recreated when you bind again (so, if you have some ongoing process, it will still "be there"). However, it will be if you call `stopService()`. I assumed you wanted to keep the service state on rotation. – matiash Dec 23 '14 at 19:09
  • Regarding (2), when `onStop()` is called in `SecondActivity`, the `Service` is probably killed (because no `Activity` binds to it anymore and all `Activity`s who called `startService(...)` have called `stopService(...)`) before you can re-bind in `onStart()` after the configuration change. Am I wrong? – caw Dec 23 '14 at 20:02
  • @MarcoW. Well, I assumed `FirstActivity` starts `SecondActivity`. Is that not the case? If so, then `FirstActivity.onDestroy()` (and hence `stopService()`) isn't called until you exit both. – matiash Dec 23 '14 at 20:06
  • Yes, your assumption is correct. But there's no such guarantee on Android. The operating system may kill `FirstActivity` (and thus run its `onDestroy()`) at any time when you're in `SecondActivity`. More specifically, as soon as you leave `FirstActivity` and `onStop()` is executed, Android may kill that first `Activity` at any time when it needs more memory -- and will regularly do so. I have verified this with a small example application. – caw Dec 24 '14 at 02:29
  • @MarcoW. You're right. Even the second activity could suffer this fate, if low on resources. To be frank, I only see two other options: (1) binding on the application context (and preserving this binding through configuration changes, via `onSaveCustomInstanceState()`), or (2) just call `stopSelf()` in the service itself when it is done, instead of stopping it from the Activity. Depending on the scenario and what the service does it might even be more logical to do this. – matiash Dec 24 '14 at 03:04
  • @MarcoW. I wouldn't recommend (1) though. So I guess you know which one I'd prefer. :) – matiash Dec 24 '14 at 03:05
  • Thanks. I can't use (2) because I want to tie the `Service`'s lifecycle to the two `Activity`s' lifecycle. This is why I posted the question here. If the `Service` knew itself when it's ready and could just call `stopSelf()`, we wouldn't need any binding and it would be easy. So (1) may be a valid option. Apart from that, one might call `startService()` in *both* `Activity`s so that we can safely lose the first one when we're in the second one. – caw Dec 24 '14 at 03:09
  • @MarcoW. If you do that, then either `SecondActivity` calls `stopService()` in `onDestroy()` or it doesn't. If it does, then the service will be destroyed when returning to `FirstActivity`. And if it doesn't, then it will keep going if `FirstActivity` is destroyed before you exit `SecondActivity`. So you still have the same problem, AFAICS. – matiash Dec 24 '14 at 03:12
  • @MarcoW.: As matiash stated, no individual activity is killed by the system to clear memory; instead the whole process is killed (which contains all activities, services, and other components). I don't think there is any callback performed when this happens, but if there is then you can check for it by querying the `isFinishing()` method instead of `isChangingConfigurations()`. In any case, unless you are running the service in a separate process, it would also be invariably destroyed along with the activities when the process is killed. – corsair992 Dec 24 '14 at 05:32