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.
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:
public interface IXamarinService { event EventHandler SomeEvent; void SomeInstruction(); }
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; } }
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() { } }
OnBind()
method:public override IBinder OnBind(Intent intent) { return new XamarinBinder(this); }
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; }
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; } } }
var intent = new Intent(this, typeof(XamarinService)); StartService(intent);
var connection = new XamarinConnection(); BindService(intent, connection, Bind.AutoCreate);
UnbindService(connection);
connection.Connected += delegate { connection.Service.SomeEvent += OnSomeEvent; }; connection.Disconnected += delegate { connection.Service.SomeEvent -= OnSomeEvent; };
var service = connection.Service; if (service != null) { service.SomeInstruction(); }
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.
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.
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.
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.