Services - Part 2 - Bound Services - 2013-03-13

9 Pages • 2,275 Words • PDF • 131.7 KB
Uploaded at 2021-09-24 13:55

This document was submitted by our user and they confirm that they have the consent to share it. Assuming that you are writer or own the copyright of this document, report to us by using this DMCA report button.


Part 2 - Bound Services

Xamarin Inc.

In addition to running indefinitely in the background, services can also provide a client-server interface that a client can interact with. Such services are termed Bound Services. Bound Services can be created either locally in a particular application process, or in a remote process that can service multiple applications. Local services would be used to provide background worker capability within an application process for particular method calls. Remote services, however, would be used across process boundaries to provide some system-wide capability.

Bound Service Lifecycle The lifecycle of a Bound Service is different from that of a Started Service. Unlike Started Services, Bound Services do not run indefinitely. Instead, they are created when a client connects to them and are destroyed after all bound clients have disconnected. However, a Bound Service can be implemented in the same Service subclass as a Started Service, so if it has been started with a StartService call, it will not be destroyed even after all bound clients have disconnected. Likewise a StartedService will not be destroyed after a call to StopService or StopSelf, if it still has bound clients connected. The following diagram illustrates the lifecycle of a Bound Service:



OnBind – This method is used to return an instance on an IBinder that the client uses to obtain a service instance that can, in turn, call methods on the service.



OnUnbind – This method is called when all bound clients have unbound. By returning true from this method, the service will later call OnRebind with the intent passed to OnUnbind when new clients bind to it. You would do this when a service continues running after it has been unbound. This would happen if the recently unbound service were also a started service, and StopService or StopSelf hadn’t been called. In such a scenario, OnRebind allows the intent to be retrieved. The default returns false, which does nothing.

Implementing a Local Bound Service Implementing a Local Bound Service requires the following: 1. A subclass of the Binder class that returns the service instance, where Binder is the default implementation of IBinder. 2. An implementation of the OnBind method that returns an instance of the Binder subclass. The Binder subclass is responsible for returning the service instance to clients so that they can make method calls against it. The OnBind method returns an instance of the Binder. OnBind is called by Android the first time any client attempts to connect to a service. For example, the following code shows an implementation of a Binder subclass named DemoServiceBinder: public class DemoServiceBinder : Binder { DemoService service; public DemoServiceBinder (DemoService service) { this.service = service; } public DemoService GetDemoService () { return service; } }

Clients can use the DemoServiceBinder class to obtain a reference to the DemoService itself, which the client can then use to call the service’s methods. The mechanism that the DemoService uses to return the instance of DemoServiceBinder is the OnBind lifecycle method. The following code shows the implementation of OnBind that returns a DemoServiceBinder: public override IBinder OnBind (Intent intent) { binder = new DemoServiceBinder (this); return binder; }

The binder variable is an instance variable for which the DemoService keeps a reference. Since the OnBind method is only called on the first connection to the service, additional calls will reuse the same binder instance.

Binding Clients to the Service A client binds to a service by calling BindService with an intent and an instance of a ServiceConnection. A ServiceConnection is a class that provides a calling interface between the client and the service. For example, the following code in the OnStart of an Activity binds a service that has an intent filter with the action com.xamarin.DemoService: protected override void OnStart () { base.OnStart (); var demoServiceIntent = new Intent ("com.xamarin.DemoService"); demoServiceConnection = new DemoServiceConnection (this); BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate); }

The above code uses the Bind.AutoCreate value in the BindService call to automatically create the service, if the binding exists. BindService itself is an asynchronous call that will either return false if there is no service to bind to, or cause a callback to be sent to the OnServiceConnected method of a ServiceConnection class when the connection is made. The code also creates an instance of DemoServiceConnection. This is a class that inherits from ServiceConnection and overrides OnServiceConnected to get a reference to the particular IBinder for the service. In this case, IBinder is an instance of a DemoServiceBinder. The DemoServiceBinder is used to get a reference to the DemoService itself, so that the client can call any methods that the service defines. The following code shows the DemoServiceConnection class: class DemoServiceConnection : Java.Lang.Object, IServiceConnection { DemoActivity activity; public DemoServiceConnection (DemoActivity activity) { this.activity = activity; } public void OnServiceConnected (ComponentName name, IBinder service) { var demoServiceBinder = service as DemoServiceBinder; if (demoServiceBinder != null) { activity.binder = demoServiceBinder; activity.isBound = true; } } public void OnServiceDisconnected (ComponentName name) { activity.isBound = false; } }

Notice that an instance of the Activity is passed into the constructor so that the binder obtained in the OnServiceConnected callback can be set on the Activity itself. This is the order of the process because it’s the Activity that wants to use the binder to get a reference to the service so that the Activity can call the service’s methods.

Calling Service Methods For example, say the service defines a method called GetText. Once OnServiceConnected has been called and the Activity has the binder, the Activity can use the binder to get the service reference and subsequently call GetText as follows: binder.GetDemoService ().GetText ();

Unbinding from the Service Clients such as the Activity shown here also must unbind from the service when they are finished using it. This allows the service to shut down when it is not in use. After the service disconnects, the OnServiceDisconnected method of ServiceConnection class will be called. To unbind from a service, a client calls UnbindService, passing the ServiceConnection instance it used in the binding. For example, the following code shows an Activity unbinding from a service in the OnDestroy method of the Activity: protected override void OnDestroy () { base.OnDestroy (); if (isBound) { UnbindService (demoServiceConnection); isBound = false; } }

If there are no outstanding calls to StartService, and no other clients are bound to the service, Android will shut down the service. However, since OnStop is called during a configuration change (such as a rotation change), the above code would also unbind from the service in that scenario. How to preserve the binding to the service in this scenario is discussed next.

Handling Configuration Changes When a configuration change such as a device rotation occurs, any binding and unbinding code that is bound to an Activity and placed in a lifecycle method (such as OnPause or OnStop) will be run. If binding to a particular service is expensive, you may want to preserve the ServiceConnection. For a client bound to a service to preserve the ServiceConnection across configuration changes: ● ● ●

The BindService method should be called from the ApplicationContext rather than from the Activity. The SerivceConnection instance should be returned from OnRetainNonConfigurationInstance. The OnRetainNonConfigurationInstance method should set a flag that will only be used to unbind the service when the service is not stopped due to a configuration change.

For the code above, the BindService call would become: ApplicationContext.BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate);

Then the DemoServiceConnection would keep a reference to the IBinder for retrieval after the configuration change: class DemoServiceConnection : Java.Lang.Object, IServiceConnection

{ DemoServiceBinder binder; public DemoServiceBinder Binder { get { return binder; } ⦠public void OnServiceConnected (ComponentName name, IBinder service) { var demoServiceBinder = service as DemoServiceBinder; if (demoServiceBinder != null) { ⦠// keep instance for preservation across configuration changes this.binder = (DemoServiceBinder)service; } }

Next, the OnRetainNonConfiguration method would set a flag, initialized to false when declared in the Activity. This method would then be used to prevent unbinding due to a configuration change, and would return the DemoServiceConnection instance: bool isConfigurationChange = false; ⦠// return the service connection if there is a configuration change public override Java.Lang.Object OnRetainNonConfigurationInstance () { base.OnRetainNonConfigurationInstance (); isConfigurationChange = true; return demoServiceConnection; } protected override void OnDestroy () { base.OnDestroy (); if (!isConfigurationChange) { if (isBound) { UnbindService (demoServiceConnection); isBound = false; } } }

Finally, in the OnCreate method of the Activity, the DemoServiceConnection instance and the DemoServiceBinder would be retrieved from the LastNonConfigurationInstance: // restore from connection there was a configuration change, such as a device rotation demoServiceConnection = LastNonConfigurationInstance as DemoServiceConnection; if(demoServiceConnection != null) binder = demoServiceConnection.Binder;

For more information about how to work with rotation in Activities, see Handling Rotation .

Inter-process Communication with Services In addition to local services, which run in the same process as the caller, services can also be created in their own processes. To perform inter-process communication (IPC) to a service on Android, a Messenger class can be used to send messages in a client-server fashion. A more advanced technique that can be employed in order to perform IPC with services is to use Android Interface Definition

Language (AIDL), which Xamarin.Android will support in a future release.

Using the Messenger Class Android provides the Messenger class to enable IPC for services without using AIDL. When using Messenger, messages sent from clients are queued in the service and processed one at a time. Also, the Messenger does not expose a service interface to clients. Instead, clients send Message objects, which the service handles in a Handler class.

Implementing a Messenger-Bound Service Implementing a service that uses a Messenger involves creating a Messenger in the service that returns a binder to the client. Two things need to be in place in the service for this: 1. A class that inherits from Handler. 2. A Messenger instance that is created with an instance of the Handler. For example, the following code shows a service that uses a Messenger instance named demoMessenger that, in turn, takes an instance of a class called DemoHandler: [Service] [IntentFilter(new String[]{"com.xamarin.DemoMessengerService"})] public class DemoMessengerService : Service { Messenger demoMessenger; public DemoMessengerService () { demoMessenger = new Messenger (new DemoHandler ()); } public override IBinder OnBind (Intent intent) { return demoMessenger.Binder; } class DemoHandler : Handler { public override void HandleMessage (Message msg) { ⦠} } }

The HandleMessage method is called when a client binds to the service and sends a message to the service that is contained in a Message object. For simple messages, the Message object can contain two integer properties, named Arg1 and Arg2 respectively, as well as a property called What that can be used to distinguish the particular message call. For instance, based upon the values of these two properties, the client could send different values of What and the HandleMessage implementation and so take different actions. To send more types of data than integers, Message includes a Data property that is an Andorid.OS.Bundle. This property can include other values that can be retrieved by key. For example,

if the client included a string with the key âInputTextâ in the Data, this string could be retrieved in the HandleMessage method in the service as shown below: class StockHandler : Handler { public override void HandleMessage (Message msg) { Log.Debug ("DemoMessengerService", "What = " + msg.What.ToString()); string text = msg.Data.GetString ("InputText"); Log.Debug ("DemoMessengerService", "InputText = " + text); } }

Calling a Messenger Service from the Client To use a Messenger to call a service, a client needs to create a Message object, and then call the Send method of the Messenger class. The client needs to: 1. Implement an IServiceConnection that creates a Messenger. 2. Create a Message object and add data to it. 3. Call the Send method of the Messenger.

Creating a Messenger in the Client The client creates the Messenger when it connects to the service. In the IServiceConnection implementation, this occurs after the client calls BindService. For example, the following code shows a client connecting to a service, and then creating a Messenger instance. protected override void OnStart () { base.OnStart (); var demoServiceIntent = new Intent ("com.xamarin.DemoMessengerService"); demoServiceConnection = new DemoServiceConnection (this); BindService (demoServiceIntent, demoServiceConnection, Bind.AutoCreate); } protected override void OnStop () { base.OnStop (); if (isBound) { UnbindService (demoServiceConnection); isBound = false; } } class DemoServiceConnection : Java.Lang.Object, IServiceConnection { DemoMessengerActivity activity; public DemoServiceConnection (Activity1 activity) { this.activity = activity; } public void OnServiceConnected (ComponentName name, IBinder service) { activity.demoMessenger = new Messenger (service); activity.isBound = true; }

public void OnServiceDisconnected (ComponentName name) { activity.demoMessenger.Dispose (); activity.demoMessenger = null; activity.isBound = false; } }

This procedure is very similar to the local bound service example shown earlier. The main difference is that the code here, in OnServiceConnected, creates a Messenger from the IBinder, which it can then use to call the service.

Creating a Message To create a Message, use the Message.Obtain method. The Message.Data property allows an Android.OS.Bundle to be sent in the Message, as mentioned earlier. The bundle can be used to include additional data in the Message. For example, the following code creates a Message and includes a string with the key âInputTextâ to a bundle: Message message = Message.Obtain (); Bundle b = new Bundle (); b.PutString ("InputText", "text from client"); message.Data = b;

Sending the Message to the Service To send the Message to the service, the Messenger instance created from the IBinder in OnServiceConnected is used. Simply call the Send method of the Messenger, as shown below: demoMessenger.Send (message);

The server will receive the Message in the Handler, where it can extract the data, as shown earlier.

Source URL: http://docs.xamarin.com/guides/android/application_fundamentals/services/part_2_-_bound_services
Services - Part 2 - Bound Services - 2013-03-13

Related documents

9 Pages • 2,275 Words • PDF • 131.7 KB

916 Pages • 234,818 Words • PDF • 47.3 MB

49 Pages • 6,329 Words • PDF • 4.3 MB

2 Pages • PDF • 296.1 KB

3 Pages • 452 Words • PDF • 65.1 KB

60 Pages • PDF • 1.4 MB

207 Pages • 75,569 Words • PDF • 2 MB

3 Pages • 29 Words • PDF • 2 MB

37 Pages • 912 Words • PDF • 5.3 MB

101 Pages • 24,521 Words • PDF • 9.3 MB

20 Pages • 9,035 Words • PDF • 923.1 KB