Passing Complex Types to Services

Passing complex types to and from services requires more work than passing Java primitive types. Before embarking on this work, you should get an idea of AIDL's support for nonprimitive types:

• AIDL supports String and CharSequence.

• AIDL allows you to pass other AIDL interfaces, but you need to have an import statement for each AIDL interface you reference (even if the referenced AIDL interface is in the same package).

• AIDL allows you to pass complex types that implement the android.os.Parcelable interface. You need to have an import statement in your AIDL file for these types.

• AIDL supports java.util.List and java.util.Map, with a few restrictions. The allowable data types for the items in the collection include Java primitive, String, CharSequence, or android.os.Parcelable. You do not need import statements for List or Map, but you do need them for the Parcelables.

• Nonprimitive types, other than String, require a directional indicator. Directional indicators include in, out, and inout. in means the value is set by the client, out means the value is set by the service, and inout means both the client and service set the value.

The Parcelable interface tells the Android runtime how to serialize and deserialize objects during the marshalling and unmarshalling process. Listing 8-15 shows a Person class that implements the Parcelable interface.

Listing 8-15. Implementing the Parcelable Interface package com.syh;

import android.os.Parcel;

import android.os.Parcelable;

public class Person implements Parcelable { private int age; private String name;

public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {

public Person createFromParcel(Parcel in) { return new Person(in);

public Person[] newArray(int size) { return new Person[size];

private Person(Parcel in) { readFromParcel(in);

^Override public int describeContents() { return 0;

^Override public void writeToParcel(Parcel out, int flags) { out.writelnt(age); out.writeString(name);

public void readFromParcel(Parcel in) { age = in.readInt(); name = in.readString();

public int getAge() { return age;

public void setAge(int age) { this.age = age;

public String getName() { return name;

public void setName(String name) { this.name = name;

The Parcelable interface defines the contract for hydration and dehydration of objects during the marshalling/unmarshalling process. Underlying the Parcelable interface is the Parcel container object. The Parcel class is a fast serialization/deserialization mechanism specially designed for interprocess communication within Android. The class provides methods that you use to flatten your members to the container and to expand the members back from the container. To properly implement an object for interprocess communication, we have to do the following:

1. Implement the Parcelable interface. This means that you implement writeToParcel() and readFromParcel(). The write method will write the object to the parcel and the read method will read the object from the parcel. Note that the order in which you write properties must be the same as the order in which you read them.

2. Add a static final property to the class with the name CREATOR. The property needs to implement the android.os.Parcelable.Creator<T> interface.

3. Provide a constructor for the Parcelable that knows how to create the object from the Parcel.

4. Define Parcelable classes in a file called project.aidl in your project's root directory. The AIDL compiler will look for this file when compiling your AIDL files. The Android Eclipse plug-in provides a tool that you can invoke to generate the project.aidl file. To invoke the tool, right-click your project in Eclipse and select Android Tools > Create Aidl preprocess file for parcelable classes. An example of a project.aidl file is shown in Listing 8-16.

Note Seeing Parcelable might have triggered the question, why is Android not using the built-in Java serialization mechanism? It turns out that the Android team came to the conclusion that the serialization in Java is far too slow to satisfy Android's interprocess-communication requirements. So the team built the Parcelable solution. The Parcelable approach requires that you explicitly serialize the members of your class, but in the end, you get a much faster serialization of your objects.

Also realize that Android provides two mechanisms that allow you to pass data to another process. The first is to pass a bundle to an activity using an intent, and the second is to pass a Parcelable to a service. These two mechanisms should not be confused and are not interchangeable. That is, the Parcelable is not meant to be passed to an activity. If you want to start an activity and pass it some data, use a bundle. Parcelable is meant to be used only as part of an AIDL definition.

Listing 8-16. An Example of a project.aidl File parcelable com.syh.Person

As shown, the project.aidl file will contain an entry for each Parcelable in your project. In this case, we have just one Parcelable: Person. Note that the tool that generates the project. aidl file, which ships with the 1.0 version of the SDK, emits comments in the project.aidl file. If you add a Parcelable to your project and the project fails to compile, you will have to remove the comments from the file (leaving only the Parcelable entries). After you remove the comments, you will have to clean the project and rebuild it in Eclipse.

Now let's use the Person class in a remote service. To keep things simple, we will modify our IStockOuoteService to take an input parameter of type Person. The idea is that clients will pass a Person to the service to tell the service who is requesting the quote. The new IStockOuoteService.aidl looks like Listing 8-17.

Listing 8-17. Passing Parcelables to Services package com.syh; import com.syh.Person;

interface IStockOuoteService {

String getOuote(in String ticker,in Person requester);

The getOuote() method now accepts two parameters: the stock's ticker symbol, and a Person object to specify who is making the request. Note that we have directional indicators on the parameters because the parameters are nonprimitive types, and that we have an import statement for the Person class. Realize that the Person class is also in the same package as the service definition (com.syh).

The service implementation now looks like Listing 8-18.

Listing 8-18. The New StockQuoteService Implementation import android.app.Notification; import android.app.NotificationManager; import android.app.Pendinglntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException;

public class StockOuoteService extends Service {

private NotificationManager notificationMgr;

public class StockOuoteServiceImpl extends IStockOuoteService.Stub {

^Override public String getOuote(String ticker, Person requester) throws RemoteException { return "Hello "+requester.getName()+"! Ouote for "+ticker+" is 20.0";

^Override public void onCreate() { super.onCreate();

notificationMgr =

(NotificationManager)getSystemService(NOTIFKATION_SERVKE);

displayNotificationMessage("onCreate() called in StockOuoteService");

^Override public void onDestroy()

displayNotificationMessage("onDestroy() called in StockOuoteService");

super.onDestroy();

^Override public void onStart(Intent intent, int startId) {

super.onStart(intent, startId);

^Override public IBinder onBind(Intent intent)

displayNotificationMessage("onBind() called in StockOuoteService");

return new StockOuoteServiceImpl();

private void displayNotificationMessage(String message)

Notification notification = new Notification(R.drawable.note, message,System.currentTimeMillis());

Pendinglntent contentlntent = PendingIntent.getActivity(this, 0,new Intent(this, MainActivity.class), 0);

notification.setLatestEventInfo(this, "StockOuoteService",message, contentIntent);

notificationMgr.notify(R.string.app_notification_id, notification);

The only difference between this implementation and the previous one is that now we return the stock value as a string and not a double. The string returned to the user contains the name of the requester from the Person object, which demonstrates that we read the value sent from the client and that the Person object was passed correctly to the service.

To implement a client that passes the Person object to the service, we need to copy everything that the client needs to the client project. In our previous example, all we needed was the IStockOuoteService.aidl file. Now we also need to copy the Person.java file because the Person object is now part of the interface. After you copy the two files to the client project, you need to re-create the project.aidl file and remove the comments from it because of the bug we discussed earlier. Also note that after you remove the comments, you will need to do a clean and rebuild. Listing 8-19 shows the client code that calls the service.

Listing 8-19. Calling the Service with a Parcelable package com.sayed;

import com.syh.IStockOuoteService; import com.syh.Person;

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.os.Bundle;

import android.os.IBinder;

import android.os.RemoteException;

import android.util.Log;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.Toast;

public class MainActivity extends Activity {

private IStockOuoteService stockService = null; /** Called when the activity is first created. */ ^Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);

Button bindBtn = (Button)findViewById(R.id.bindBtn); bindBtn.setOnClickListener(new OnClickListener(){

^Override public void onClick(View view) {

bindService(new Intent(IStockOuoteService.class .getName()), serConn, Context.BIND_AUTO_CREATE);

Button unbindBtn = (Button)findViewById(R.id.unbindBtn); unbindBtn.setOnClickListener(new OnClickListener(){

^Override public void onClick(View view) { unbindService(serConn);

private ServiceConnection serConn = new ServiceConnection() { ^Override public void onServiceConnected(ComponentName name, IBinder service) {

stockService = IStockOuoteService.Stub.aslnterface(service); String val; try {

Person person = new Person();

person.setAge(33);

person.setName("Sayed");

val = stockService.getOuote("GOOG",person);

Toast.makeText(MainActivity.this, "Value from service is: "+val+"", Toast.LENGTH_SHORT).show();

} catch (RemoteException ee) {

Log.e("MainActivity", ee.getMessage(), ee);

^Override public void onServiceDisconnected(ComponentName name) { }

The interesting method in the client is the onServiceConnected() method. As shown, we create a new Person object and set its Age and Name properties. We then execute the service and display the result from the service call. The result looks like Figure 8-1.

Value from service Is: Hello Sayed! Quote for GOOG Is 20.0

Figure 8-1. Result from calling the service with a Parcelable

It is also useful to see the artifacts of the service project and the client that calls it (see Figure 8-2).

- AndroidServices2Demo S src

+ [JJ EiackgroundService.java + |JJ IStockQuoteService.java + LL MainActivity.java + LL Person.java + [J] R.java

+ LL StockQuoteService.java + |JJ TestServicel.java

IStockQuoteService. aidl & Android Library ■■■■& assets H£> res CI AndroidManifest.xml project.aidl src

+ |i] IStockQuoteService.java + Ll] Person.java

IStockQuoteService.aidl & Android Library ■■■■&■ assets I-& res tl AndroidManifest.xml project.aidl

Figure 8-2. The artifacts of the client and service

Figure 8-2 shows the Eclipse project artifacts for the service (left) and the client (right). Note that the contract between the client and the service consists of the AIDL artifacts and the Parcelable objects exchanged between the two parties. This is the reason that we see IStockQuoteService.aidl, project.aidl, and Person.java on both sides. Because the AIDL complier generates the Java interface, stub, proxy, and so on from the AIDL artifacts, the build process creates the IStockQuoteService.java file on the client side when we copy the contract artifacts to the client project.

Now we know how to exchange complex types between services and clients. Let's briefly touch on another important aspect of calling services: synchronous vs. asynchronous service invocation.

All of the calls that you make on services are synchronous. This brings up the obvious question, do you need to implement all of your service calls in a worker thread? Not necessarily. In most other platforms, it's common for a client to use a service that is a complete black box, so the client would have to take appropriate precautions when making service calls. With Android, you will likely know what is in the service (generally because you wrote the service yourself), so you can make an informed decision. If you know that the method you are calling is doing a lot of heavy lifting, then you should consider using a secondary thread to make the call. If you are sure that the method does not have any bottlenecks, then you can safely make the call on the UI thread. If you conclude that it's best to make the service call within a worker thread, you can create the thread from the onServiceConnected() method of ServiceConnection and then call the service. You can then communicate the result to the UI thread.

0 0

Post a comment