Communicating with running services

Sometimes, we have a long-running task in a service, but it needs to communicate with our app. This can be needed to send data to the service or to be notified of an event from the service.

How to do it...

To communicate with a service, we need a connection. We use the connection to get a binder, which holds a reference to the service. Let's take a look at the following steps:

  1. Although not entirely necessary for very simple services, we will want an interface that defines the public interface of the service:
    public interface IXamarinService {
      event EventHandler SomeEvent;
      void SomeInstruction();
    }
  2. Next, we create an instance of IBinder, or rather Binder, which holds a reference to the running service:
    public class XamarinBinder : Binder {
      public IXamarinService Service { get; private set; }
    
      public XamarinBinder(IXamarinService service) {
        Service = service;
      }
    }
  3. Now that we have the binder instance, we can create the actual service implementing the service interface. To support connections, we return an instance of the binder in the OnBind() method:
    [Service]
    public class XamarinService : Service, IXamarinService {
      public event EventHandler SomeEvent;
      public void SomeInstruction() {
      }
    }
  4. To support incoming connections, we return an instance of the binder in the OnBind() method:
    public override IBinder OnBind(Intent intent) {
      return new XamarinBinder(this);
    }
  5. Now, we can implement the service logic in the OnStartCommand() method, which will start the task on a new thread, returning immediately with NotSticky so that the service will be stopped automatically when the task is done:
    public override StartCommandResult OnStartCommand(
      Intent intent, StartCommandFlags flags, int startId) {
      // do some work on a new thread
      // ...
      return StartCommandResult.NotSticky;
    }
  6. Once we have the service that supports binding, we need a connection that implements the IServiceConnection interface:
    public class XamarinConnection
      : Java.Lang.Object, IServiceConnection {
      private XamarinBinder binder;
      public void OnServiceConnected(
      ComponentName name, IBinder service) {
        binder = service as XamarinBinder;
        var handler = Connected;
        if (handler != null)
          handler(this, EventArgs.Empty);
      }
      public void OnServiceDisconnected(ComponentName name) {
        var handler = Disconnected;
        if (handler != null)
          handler(this, EventArgs.Empty);
        binder = null;
      }
      public event EventHandler Connected;
      public event EventHandler Disconnected;
      public IXamarinService Service {
        get {
          if (binder != null && binder.Service != null)
            return binder.Service;
          return null;
        }
      }
    }
  7. We start the service, although the service is not required, as follows:
    var intent = new Intent(this, typeof(XamarinService));
    StartService(intent);
  8. Whether or not the service is started, we can bind to it as follows:
    var connection = new XamarinConnection();
    BindService(intent, connection, Bind.AutoCreate);
  9. We should ensure that we unbind from the service if the activity stops:
    UnbindService(connection);
  10. If we want to attach events to the service, we can subscribe to them when the connection is made:
    connection.Connected += delegate {
      connection.Service.SomeEvent += OnSomeEvent;
    };
    connection.Disconnected += delegate {
      connection.Service.SomeEvent -= OnSomeEvent;
    };
  11. And, we can also invoke methods on the service:
    var service = connection.Service;
    if (service != null) {
      service.SomeInstruction();
    }

How it works...

Once we have started a service, we may wish to set up communication with it. This may be to receive progress notifications or request a specific operation while it is performing the tasks. To do this, we can connect to the service using an IBinder instance and an IServiceConnection instance.

Usually, we would inherit our service from the base Service type and provide a public interface for the service. We can use the service object directly instead of an interface, but we would not be able to control how the service is used.

Tip

An interface can be used to define the public interface of the service. This will prevent accidental or intentional misuse of the members on the service.

After we have, optionally, created an interface that defines the public aspects of the service, we create a binder. We can use the IBinder interface, but then we would have to implement all the methods on that interface. Thus, we use the Binder type, which provides a standard implementation of all the methods. For most local services, we do not even use the methods, but rather just provide our own means of obtaining a reference to the running service.

From the service, we override the OnBind() method and return an instance of our binder. We can create a new instance each time or return the same instance each time.

We use an implementation of the IServiceConnection interface when subscribing to a service, there are two methods we need to implement. The first is the OnServiceConnected() method, which is invoked when a connection to the running service is established. In this method of the connection, we are provided the binder that we returned from the OnBind() method of the service. We can then use this binder and get hold of the actual service.

The other method is the OnServiceDisconnected() method. This is invoked when a connection to the service is lost, typically when the hosting process has crashed or is terminated. The connection object is not destroyed and will reconnect when the service becomes available again. If we subscribe to any events on the service when we connect, we need to unsubscribe from them when the service is destroyed so that resources can be disposed of.

Note

When a service is destroyed, the OnServiceDisconnected() method is invoked on any open connections. The connection objects are not destroyed, and as soon as the service is re-created, the OnServiceConnected() method is invoked on those connections.

We can connect, or bind, to a service whether it has started or not using the BindService instance on the Context type. If we have not started the service, it will be created and started. However, the OnStartCommand() method will not be invoked as no intent would have been received. But, as the service has started, we can still invoke methods on that service and subscribe to events.

Tip

A service does not have to be started before it can be bound. If it is not started, it will be.

Once we have a connection to the service, we can obtain the service from the binder that was provided to the connection. Once we have the service, we can directly invoke methods or subscribe to events as we would do for any object.

To unbind from a service, we pass the open connection to the UnbindService() method on the Context type. This disconnects the connection from the service and we will no longer receive a notification if the service is started or stopped. If the service wasn't started with StartService, it is now available to be terminated at any time by the Android OS. However, this does not mean that it will be terminated immediately.

Note

Disconnecting from a bound service does not mean that it will be destroyed.

There's more...

Using a separate connection object is not required. Instead of a separate object, the activity can be used instead. Thus, when connecting to a service, we pass the activity to the BindService() and UnbindService() methods.

See also

  • The Starting services recipe
  • The Handling simultaneous service requests recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.108.9