Advanced Threading Techniques - The Android Developer’s Cookbook: Building Applications with the Android SDK, Second Edition (2013)

The Android Developer’s Cookbook: Building Applications with the Android SDK, Second Edition (2013)

Chapter 4. Advanced Threading Techniques

This chapter showcases a collection of threading techniques that are provided by the Android Framework to make the usage of threads easier and more secure. First, the Loaders API that comes with the support package is demonstrated. Next, the AsyncTask API is shown, which is a very flexible and powerful replacement for raw Java threads. Then the ways of using inter-process communication on Android are discussed.

Loaders

Typical usage scenarios for threads are the moments in an application lifecycle when screens are initialized with data from databases or caches. If done on the main thread, these operations can block the loading of the layout itself and leave the user staring at a blank screen for a couple of seconds. To overcome the challenges of dealing with data initialization, Android now provides a Loader API. A loader is a small object that gets started through a manager, executes its query in the background, and then presents the result through a common callback interface. There are two main types of loaders:

Image CursorLoaders, used for querying databases and ContentProviders

Image AsyncTaskLoaders, used for everything else

Recipe: Using a CursorLoader

This recipe uses a CursorLoader to show all contacts on the phone in a simple ListView. Depending on how many contacts the phone holds, loading this query can take a while. This is a simple activity with a ListView and no fragments. Loaders can be used with both activities and fragments; the methods to override within each class and the steps to do this are the same. To get access to the support API, the activity needs to extend the FragmentActivity class. Listing 4.1 shows the activity.

Listing 4.1. MainActivity.java


public class MainActivity extends FragmentActivity
implements LoaderCallbacks<Cursor>{

private static final int LOADER_ID = 1;
SimpleCursorAdapter mAdapter;
ListView mListView;

static final String[] CONTACTS_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME
};


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView=(ListView)findViewById(R.id.list);


mAdapter=new SimpleCursorAdapter(
getApplicationContext(), //context for layout inflation, etc.
android.R.layout.simple_list_item_1, //the layout file
null, //We don't have a cursor yet
new String[]{Contacts.DISPLAY_NAME}, //the name of the data row
//The ID of the layout for data is displayed
new int[]{android.R.id.text1}

);

mListView.setAdapter(mAdapter);

getSupportLoaderManager().initLoader(LOADER_ID,null,this);
}


@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
return new CursorLoader(
getApplicationContext(),
Contacts.CONTENT_URI,
CONTACTS_PROJECTION,
null,
null,
Contacts.DISPLAY_NAME + "COLLATE LOCALIZED ASC"
);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if(loader.getId()==LOADER_ID){
mAdapter.swapCursor(cursor);
}
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
if(loader.getId()==LOADER_ID){
mAdapter.swapCursor(null);
}
}
}


To make use of loaders, the activity needs to implement the LoaderCallbacks<T> interface, which has three functions: onCreateLoader, onLoadFinished, and onLoaderReset.

The onCreateLoader(int loaderId, Bundle args) function is called whenever a request is made to initialize a loader. The ID can be used to distinguish the different loaders, or it can be set to 0 if only one loader implementation is used. Here, a new CursorLoader class is returned and given the same arguments that would be used for making a query against a ContentProvider, mainly a content URI, a projection map, a selection string with arguments, and an order function.

The onLoadFinished(Loader<Cursor> loader, Cursor cursor) method is called when the loader is done. The loader’s ID can be checked by calling loader.getId(), and if this has the expected value, the resulting cursor is set as a data source for the list by calling mAdapter.swapCursor(). If the adapter had another cursor active before, this would now be closed.

A call to the onLoaderReset(Loader<Cursor> loader) function closes the cursor that is produced by the loader. mAdapter.swapCursor(null) must be called to make sure it is no longer used in the list.

Loaders can also be set to automatically requery the cursor if its underlying data changes, so the presenting views are kept up-to-date. A requery will result in onLoadFinished being called again. This shows that loaders are a simple yet powerful way to do initialization of activities or fragments.

AsyncTasks

AsyncTasks are classes that execute one method in a separate thread, allowing them to take work away from the UI thread easily. The interesting part of AsyncTasks is that only the doInBackground() method runs on a separate thread, allowing the onPreExecute() and onPostExecute() methods to manipulate views or other parts of the activity or fragment that started them. AsyncTasks are typed with a type for Parameters, Progress Reporting, and Result. If any of those types are not used, they can be set to Void. AsyncTasks allow reporting of progress back to the UI thread by callingsetProgress(int) in doInBackground(). This will trigger a call to the onProgressChanged() function of the AsyncTask, which is again called on the UI thread and can be used to update a progress dialog.

Recipe: Using an AsyncTask

This recipe uses an AsyncTask that loads an image from a remote location. This image will then be displayed in an ImageView. Loading images from a server is a common scenario for AsyncTasks, as doing this on the main thread could potentially block the UI and should be avoided. Implementing the AsyncTask as an inner class allows the image to be set directly into an ImageView after the download is finished. The complete code is shown in Listing 4.2.

Listing 4.2. MainActivity


public class MainActivity extends Activity {
private static final int LOADER_ID = 1;
ImageView mImageView;

private static final String IMAGE_URL =
"http://developer.android.com/images/brand/Android_Robot_100.png";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView=(ImageView)findViewById(R.id.image);
new ImageLoaderTask(getApplicationContext()).execute(IMAGE_URL);
}




public class ImageLoaderTask extends AsyncTask<String, Void, String>{
private Context context;


public ImageLoaderTask(Context context){
this.context=context;
}


@Override
protected String doInBackground(String. . . params) {
String path=context.getFilesDir()
+File.pathSeparator
+"temp_"
+System.currentTimeMillis()
+".png";


HttpURLConnection connection=null;


android.util.Log.v("TASK","opening url="+params[0]);

try {
final URL url=new URL(params[0]);
connection=(HttpURLConnection) url.openConnection();
InputStream in =
new BufferedInputStream(connection.getInputStream());

OutputStream out= new FileOutputStream(path);
int data = in.read();
while (data != -1) {
out.write(data);
data = in.read();
}
} catch (IOException e) {
android.util.Log.e("TASK","error loading image",e);
e.printStackTrace();
return null;
}finally {
if(connection!=null){
connection.disconnect();
}
}
return path;
}


@Override
protected void onPostExecute(String imagePath) {
super.onPostExecute(imagePath);
if(imagePath!=null){
android.util.Log.v("TASK","loading image from temp file"+imagePath);
Bitmap bitmap=BitmapFactory.decodeFile(imagePath);
mImageView.setImageBitmap(bitmap);
}
}

}

}


The onCreate() method loads the layout, saves the instance of the ImageView into a field variable, and starts the AsyncTask.

The ImageLoaderTask class takes a string parameter denoting the URL of the image to load and returns a string with the path of the temp file that stores the image. For the path, the application’s internal file folder is read from the context and a filename is constructed with a prefix of tempfollowed by the current time in milliseconds and a postfix of .png. The doInBackground function uses a simple URLConnection to open an InputStream to the image, read it bytewise, and write it to a FileOutputStream pointing to the path of the temp file.

The onPostExecute method gets the result of doInBackground as a parameter. If this is not null, an image exists at this path. Because onPostExecute is run on the UI thread of the activity, the image can be decoded into a bitmap and the bitmap set into the ImageView of the activity. As theonPreExecute() and onPostExecute() methods are always run on the UI thread, these can be used to communicate with the hosting activity or fragment and manipulate views if needed.

Android Inter-Process Communication

If two applications need to share resources but cannot get permissions, it is possible to define an IPC message. To support IPC, an interface is needed to serve as a bridge between applications. This is provided by the Android Interface Definition Language (AIDL).

Defining AIDL is similar to a Java interface. In fact, it can be easily done in Eclipse by creating a new Java interface and, after the definitions are complete, changing the suffix of the file from .java to .aidl.

The data types that AIDL currently supports are

Image Java primitives that include int, boolean, and float

Image String

Image CharSequence

Image List

Image Map

Image Other AIDL-generated interfaces

Image Custom classes that implement the Parcelable protocol and are passed by value

Recipe: Implementing a Remote Procedure Call

This recipe implements a remote procedure call (RPC) between two activities. First, an AIDL interface can be defined, as shown in Listing 4.3.

Listing 4.3. IAdditionalService.aidl under com.cookbook.advance.rpc


package com.cookbook.advance.rpc;

// Declare the interface
interface IAdditionService {
int factorial(in int value);
}


After the AIDL file is created, Eclipse generates an IAdditionalService.java file under the gen/ folder when the project is built. The contents of this file should not be modified. It contains a stub class that is needed to implement the remote service.

Inside the first activity, rpcService, an mBinder member is declared as the stub from IAdditionalService. It can also be interpreted as IBinder. In the onCreate() method, mBinder is initiated and defined to call the factorial() function. When the onBind() method is called, it returns mBinderto the caller. After onBind() is ready, the other process activities are able to connect to the service. This is shown in Listing 4.4.

Listing 4.4. src/com/cookbook/advance/rpc/rpcService.java


package com.cookbook.advance.rpc;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;import android.os.RemoteException;

public class RPCService extends Service {

IAdditionService.Stub mBinder;
@Override
public void onCreate() {
super.onCreate();
mBinder = new IAdditionService.Stub() {
public int factorial(int value1) throws RemoteException {
int result=1;
for(int i=1; i<=value1; i++){
result*=i;
}
return result;
}
};
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

@Override
public void onDestroy() {
super.onDestroy();
}
}


Now the second activity that runs in a different process must be specified. The associated layout file is shown in Listing 4.5. Inside the layout there are three views that actually serve the main roles. EditText takes the input from the user, Button triggers the factorial() function call, andTextView with ID result is used for displaying the result from the factorial.

Listing 4.5. res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="match_parent">
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Android CookBook RPC Demo"
android:textSize="22dp" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/value1"
android:hint="0-30"></EditText>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/buttonCalc"
android:text="GET"></Button>
</LinearLayout>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="result"
android:textSize="36dp" android:id="@+id/result"></TextView>
</LinearLayout>


The AndroidManifest.xml file is shown in Listing 4.6. Inside the service tag, there is an extra attribute: android:process=".remoteService". This asks the system to create a new process named remoteService to run the second activity.

Listing 4.6. AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cookbook.advance.rpc"
android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity android:name=".rpc" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<service android:name=".rpcService" android:process=".remoteService"/>
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>


The second activity is shown in Listing 4.7. It needs to call bindService() to retrieve the factorial() method provided in rpcService. The bindService() method requires a service connection instance as the interface for monitoring the state of an application service. Therefore, this activity has an inner class myServiceConnection that implements the service connection.

The myServiceConnection and IAdditionService classes are instantiated in the rpc activity. The myServiceConnection listens to the onServiceConnected and onServiceDisconnected callback methods. The onServiceConnected callback function passes the IBinder instance to theIAdditionService instance. The onServiceDisconnected callback method puts the IAdditionService instance to null.

There are also two methods defined inside the rpc activity: initService() and releaseService(). The initService() method tries to initiate a new myServiceConnection. Then, it creates a new intent for a specific package name and class name and passes it to the bindService method along with the myServiceConnection instance and a flag BIND_AUTO_CREATE. After the service is bound, the onServiceConnected callback function is triggered, and it passes the IBinder method to the IAdditionService instance so the rpc activity can start to call the factorial method. The output is shown in Figure 4.1.

Image

Figure 4.1 Output of the AIDL application

Listing 4.7. src/com/cookbook/advance/rpc/rpc.java


package com.cookbook.advance.rpc;

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.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class rpc extends Activity {
IAdditionService service;
myServiceConnection connection;

class myServiceConnection implements ServiceConnection {

public void onServiceConnected(ComponentName name,
IBinder boundService) {
service = IAdditionService.Stub.asInterface((IBinder) boundService);
Toast.makeText(rpc.this, "Service connected", Toast.LENGTH_SHORT)
.show();
}

public void onServiceDisconnected(ComponentName name) {
service = null;
Toast.makeText(rpc.this, "Service disconnected", Toast.LENGTH_SHORT)
.show();
}
}

private void initService() {
connection = new myServiceConnection();
Intent i = new Intent();
i.setClassName("com.cookbook.advance.rpc",
com.cookbook.advance.rpc.rpcService.class.getName());
if(!bindService(i, connection, Context.BIND_AUTO_CREATE)) {
Toast.makeText(rpc.this, "Bind Service Failed", Toast.LENGTH_LONG)
.show();
}
}

private void releaseService() {
unbindService(connection);
connection = null;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

initService();

Button buttonCalc = (Button) findViewById(R.id.buttonCalc);

buttonCalc.setOnClickListener(new OnClickListener() {
TextView result = (TextView) findViewById(R.id.result);
EditText value1 = (EditText) findViewById(R.id.value1);

public void onClick(View v) {
int v1, res = -1;
try {
v1 = Integer.parseInt(value1.getText().toString());
res = service.factorial(v1);
} catch (RemoteException e) {
e.printStackTrace();
}
result.setText(Integer.toString(res));
}
});
}

@Override
protected void onDestroy() {
releaseService();
}
}


AIDL is good for doing full RPC between processes with different user IDs (which can mean different applications). The downside to this is that AIDL is fully synchronous and slow. If one or more activities need to communicate with a service to pass queries or commands to it and get back the results, there are two simpler and faster ways to do this, as will be shown in the following two recipes.

Recipe: Using Messengers

A Messenger class is a reference to a handler in a remote process that can be used to send messages to that handler. Handlers allow messages to be sent into a queue and to be processed one at a time. By providing two handlers, one in the activity and one in the service, those two classes can communicate by sending messages between the handlers. This is done by calling Messenger.send(msg), which will inject the message into the remote handler. The Message class has a special field called Message.replyTo that can hold a messenger reference. This reference can then be used to send the results of the operation back to the original thread.

The following steps are used to create the service:

1. Create a Service class and define integer constants describing messages for registering and unregistering a client, as well as sending results.

2. Create a handler that reacts to those messages.

3. Create a messenger instance that references the handler.

4. In the service onBind function, return the binder object by calling Messenger.getBinder().

This is shown in Listing 4.8.

Listing 4.8. Messenger Service


package com.cookbook.messenger_service;

/**
* MessageControlledService is an abstract service implementation that
* communicates with clients via Messenger objects. Messages are passed
* directly into the handler of the server/client.
*/
public class MessageControlledService extends Service {

public static final int MSG_INVALID = Integer.MIN_VALUE;
public static final int MSG_REGISTER_CLIENT = MSG_INVALID+1;
public static final int MSG_UNREGISTER_CLIENT = MSG_INVALID+2;
public static final int MSG_RESULT = MSG_INVALID+3;

/** Make sure your message constants are MSG_FIRST_USER+n**/
public static final int MSG_FIRST_USER=1;



private static final String LOG_TAG =
MessageControlledService.class.getCanonicalName();


/** keeps track of all current registered clients */
ArrayList<Messenger> mClients = new ArrayList<Messenger>();

/**
* handler of incoming messages from clients
*/
private class CommandHandler extends Handler {

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);

break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);

break;
default:
handleNextMessage(msg);
}
}
}


private final Handler mHandler=new CommandHandler();

/**
* target we publish for clients to send messages to IncomingHandler
*/
private final Messenger mMessenger = new Messenger(mHandler);


/**
* Call this to send an arbitrary message to the registered clients
* @param what
* @param arg1
* @param arg2
* @param object
*/
protected final void sendMessageToClients(final int what,final int arg1,
final int arg2,final Object object) {
for (int i = mClients.size() - 1; i >= 0; i--) {
try {
Message msg = Message.obtain(null, what,arg1,arg2, object);
mClients.get(i).send(msg);
} catch (RemoteException e) {
// The client is dead. Remove it from the list;
// we are going through the list from back to front
// so this is safe to do inside the loop.
mClients.remove(i);
}
}
}

//------service stuff

@Override
public IBinder onBind(Intent arg0) {
return mMessenger.getBinder();
}


/**
* This is your main method
*
* @param msg the next message in the queue
*/
public void handleNextMessage(final Message msg){
String echo="ECHO: "+(String)msg.obj;
sendMessageToClients(MSG_RESULT, -1, -1, echo);
}

}


Connected clients are tracked with ArrayList<Messenger>. In the handler, clients are added to this list if a MSG_REGISTER_CLIENT is received and removed on MSG_UNREGISTER_CLIENT. The method sendMessageToClients(. .) loops through this list of messengers and calls send on all of them. This broadcasts the results to all connected clients and allows communication with more than one activity at a time. All other messages are given as arguments to a call of handleNextMessage(. .) where all the real work can be done. In this example, the message.obj field is read as a string, the word Echo is added to it, and it is sent back to the caller.

In this activity, when a message of type MSG_RESULT is received, message.obj is cast to a string and displayed as a toast. Whatever is in the edit field is sent to the service if the single button in the activity layout is clicked. This has created an echo service.

The activity has a lot more code for handling the connection than the service itself, as it needs to start the service and bind to it to retrieve its messenger. The steps to create a service connection follow:

1. Create a new ServiceConnection instance and implement the onServiceConnected() and onServiceDisconnected() calls.

2. Implement a handler and create a messenger instance referencing this handler. This messenger instance will be handed over to the service later.

3. In onCreate(), start the service by calling startService(. .).

4. In onResume(), bind to the service.

5. In onPause, disconnect the service by calling unbindService().

The code for the MessengerActivity is shown in Listing 4.9.

Listing 4.9. MessengerActivity


public class MessengerActivity extends Activity {
private static final String LOG_TAG = null;

EditText editText;

/** messenger for communicating with service */
Messenger mServiceMessenger = null;
/** flag indicating whether we have called bind on the service */
boolean mIsBound;


protected Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if(msg.what==MessageControlledService.MSG_RESULT){
Toast.makeText(
getApplicationContext(),
(String)msg.obj,
Toast.LENGTH_SHORT
).show();
}
}

};

Messenger mLocalMessageReceiver=new Messenger(mHandler);

/**
* class for interacting with the main interface of the service
*/
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service){
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mServiceMessenger = new Messenger(service);

// We want to monitor the service for as long as we are
// connected to it
try {
Message msg = Message.obtain(null,
MessageControlledService.MSG_REGISTER_CLIENT);
msg.replyTo = mLocalMessageReceiver;
mServiceMessenger.send(msg);

} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}

Log.v(LOG_TAG, "service connected");
}

@Override
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected--that is, its process crashed
mServiceMessenger = null;
Log.v(LOG_TAG, "service disconnected");

}
};



@Override
protected void onResume() {
super.onResume();
bindMessengerService();
}

@Override
protected void onPause() {
super.onPause();
unbindAccountService();
}

void bindMessengerService() {
Log.v(LOG_TAG,"binding accountservice");
// Establish a connection with the service. We use an explicit
// class name because there is no reason to let other
// applications replace our component.
bindService(new Intent(getApplicationContext(),
MessageControlledService.class),
mServiceConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;

}

protected void unbindAccountService() {
if (mIsBound) {
Log.v(LOG_TAG,"unbinding accountservice");
// If we have received the service, and hence registered with
// it, now is the time to unregister
if (mServiceMessenger != null) {

try
{
Message msg=Message.obtain(
null,
MessageControlledService.MSG_UNREGISTER_CLIENT
);

msg.replyTo = mServiceMessenger;

mServiceMessenger.send(msg);

} catch (RemoteException e) {

// There is nothing special we need to do if the service

// has crashed
}
}

// Detach our existing connection
unbindService(mServiceConnection);
mIsBound = false;

}
}

protected boolean sendMessageToService(final int what,
final int arg1,final int arg2,
final Object object) {
try {
Message msg = Message.obtain(null, what, arg1, arg2, object);
mServiceMessenger.send(msg);
} catch (RemoteException e) {
Log.e(LOG_TAG,"unable to send message to account service",e);
//Retry binding
bindMessengerService();
return false;
}
return true;

}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startService(new Intent(getApplicationContext(),
MessageControlledService.class));
setContentView(R.layout.main);

editText=(EditText) findViewById(R.id.editText1);

Button sendButton = (Button) findViewById(R.id.Button01);
sendButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
String text=editText.getText().toString();
sendMessageToService(MessageControlledService.MSG_FIRST_USER,
-1,-1,text);
}
});


}

}


Once the activity is bound to the service, the ServiceConnection.onServiceConnected() method is called. The Ibinder that the service returns is its messenger instance (refer back to Listing 4.8), so it gets saved in a field variable. There is a sendMessageToService() function that is similar to the SendMessageToClients() function of the service but uses the messenger just received. The next step is to register the activity at the service by sending a MSG_REGISTER_CLIENT and setting the Message.replyTo field to the messenger referencing the activity’s local handler.

In the activity’s onPause method, the service must be unbound to allow it to stop if there are no more connected clients. It is important to send a MSG_UNREGISTER_CLIENT to the service before calling unbind on it, or the list of messengers available to the service will go out of sync.

Recipe: Using a ResultReceiver

A ResultReceiver is a parcelable class that internally holds an IPC binder to direct calls across different processes. It allows calling ResultReceiver.send(int ResultCode, Bundle data) in one process while the instance of the receiver was created in another process. The other process will then be able to read the arguments and react to them. Because ResultReceiver is parcelable, it can be given as an argument to an intent. The intent can be used to start another activity or a service, which will be done here. ResultReceiver accepts a bundle as an argument, which allows sending even complex objects that implement the parcelable interface.

The IntentService from Chapter 3, “Threads, Services, Receivers, and Alerts,” is used to implement an echo service similar to the previous recipe. IntentService is the perfect class to use with ResultReceiver, as it accepts intents as commands and executes them in an ordered queue. TheResultReceiver passed within that intent can then be used to send the result back to the calling activity. The service is shown in Listing 4.10.

Listing 4.10. ResultReceiverIntentService


public class ResultReceiverIntentService extends IntentService {

public static final String EXTRA_RESULT_RECEIVER = "EXTRA_RESULT_RECEIVER";

public ResultReceiverIntentService() {
super("SimpleIntentService");
}

@Override
protected void onHandleIntent(Intent intent) {
ResultReceiver rr=intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
if(intent.hasExtra("msg")){
String text= "Echo: "+intent.getStringExtra("msg");
Bundle resultBundle=new Bundle();
resultBundle.putString("msg",text);
rr.send(1, resultBundle);

}

}



}


This service needs to implement only two functions: the constructor, in which the queue gets a name by calling super("name"); and the onHandleIntent(. .) function. Here, the ResultReceiver is extracted from the intent by calling getParcelableExtra() with a key constant defined asEXTRA_RESULT_RECEIVER. The modified text is then put into a bundle, and the bundle is sent through the receiver’s send(. .) method to the originating activity. By using integer constants as result codes, more than one type of message could be sent back to the activity. The activity is shown inListing 4.11.

Listing 4.11. Main Activity


public class SimpleActivity extends Activity {
EditText editText;

Handler handler=new Handler();

ResultReceiver resultReceiver=new ResultReceiver(handler){

@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
Toast.makeText(
SimpleActivity.this,
resultData.getString("msg"),
Toast.LENGTH_SHORT
).show();
}

};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

editText=(EditText) findViewById(R.id.editText1);

Button sendButton = (Button) findViewById(R.id.Button01);
sendButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view){
Intent intent=new Intent(
SimpleActivity.this,
ResultReceiverIntentService.class
);
intent.putExtra("msg",editText.getText().toString());

intent.putExtra(
ResultReceiverIntentService.EXTRA_RESULT_RECEIVER,
resultReceiver
);
startService(intent);
}
});


}
}


The activity holds an implementation of the ResultReceiver that overrides the onReceiveResult() method. This is called by the internal binder object, and then another process executes the send() function on the ResultReceiver. A handler instance must be given to the ResultReceiver, which will execute the onReceiveResult method on this handler thread. The implementation here will just read the text sent back by the service and display it in a toast.

To send out a command, the string from an edit field is read and put into an intent. The ResultReceiver instance is set as an extra into the intent as well. The IntentService is called with startService, which will start it if it is not already running and deliver the intent with its argument into the queue. The combination of an IntentService and a ResultReceiver is the easiest way of doing inter-process communication on Android.