Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activity with long-lasting service in the background that will not be killed

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?

like image 665
caw Avatar asked Dec 17 '14 05:12

caw


1 Answers

(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.
like image 121
matiash Avatar answered Oct 16 '22 19:10

matiash