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 Activity
s is running, and it should not stop while the user navigates between the two Activity
s.
One of the Activity
s 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 Activity
s 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 Activity
s "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 Activity
s 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?
(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:
startService()
in FirstActivity.onCreate()
.stopService()
in FirstActivity.onDestroy()
.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:
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).onDestroy()
instead of onPause()
saves you from having to check for isUserMovingToSecondActivity
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With