The Problem
Sometimes we want to bind an Android Service in our activities and wrap inside e.g. our communication to our backend. Now the problem arises when we unbind it again. As usually if we change activities we want this service to survive and be in the very next activity. As soon as we close the last activity we usually want to have this service destroyed.
Just call bindService
in onResume
and unbindService
in onPause
would lead to a restart of the service on each activity change. If we move the calls into onStart
and onStop
then the Service will just live as long as our application lives. Even if it is hidden (e.g. home button press.)
Our Simple Service for this example
public class MyService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } public class MyServiceBinder extends Binder { public MyService getService() { return MyService.this; } } private final IBinder mBinder = new MyServiceBinder(); }
Of course don’t forget the XML Service tag in the AndroidManifest.xml.
Solution 1
A straight forward solution would be to register such Activities in an Android Application class, which is a singleton and let it handle the life cycle.
private volatile MyService mService; public interface ServiceUser { void attach(MyService s); void detach(); } private ConcurrentHashMap<Class<? extends Activity>, ServiceUser> serviceUsers = new ConcurrentHashMap(); private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = ((MyService.MyServiceBinder)service).getService(); for (ServiceUser us : serviceUsers.values()) { us.attach(mService); } } @Override public void onServiceDisconnected(ComponentName name) { if (mService != null) { for (ServiceUser us : serviceUsers.values()) { us.detach(); } } } }; public <T extends Activity> void useService(Class<T> listener, ServiceUser handler) { serviceUsers.put(listener, handler); if (mService == null) bindService(new Intent(this, MyService.class), mServiceConnection, BIND_AUTO_CREATE); else handler.attach(mService); } public <T extends Activity> void detach(Class<T> listener) { this.serviceUsers.remove(listener); if (serviceUsers.isEmpty()) unbindService(mServiceConnection); }
Now we can just create an abstract service activity and call onResume
and onPause
the useService
and detach
methods. So far we can still run into odd cases in which the service gets destroyed. Far more concerning is that we have all our Activities registered with our Application class, which could lead to memory leaks.
Solution 2
What if we just delay the unbind
method in our abstract base activity? An again cancel this delayed unbind?
public abstract class AbstractMyServiceActivity extends AppCompatActivity { private volatile MyService mService; private volatile boolean mPause = false; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = ((MyService.MyServiceBinder)service).getService(); onBackendServiceBind(mService); } @Override public void onServiceDisconnected(ComponentName name) { mService = null; } }; /** called as soon the service was bound */ protected abstract void onBackendServiceBind(MyService mService); @Override protected void onResume() { super.onResume(); mPause = false; if (mService == null) { bindService(new Intent(this, MyService.class), mServiceConnection, BIND_AUTO_CREATE); } else { onBackendServiceBind(mService); } } @Override protected void onPause() { super.onPause(); mPause = true; // we delay the disconnect from the backend service ... BackgroundExecutor.execute(new BackgroundExecutor.Task("", 10000L, "") { @Override public void execute() { if (mPause) unbindService(mServiceConnection); } } ); } // we could expose mPause if we like }
Again not a lot of less code. But this solution comes with some advantages:
- We have no single instance where we register all activities
- We can keep the service open / running for a while, if the user just leaves our app for a short time