Android Bind Service to the Application cross Activities

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

Paul Sterl has written 21 articles

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>