Chapter 4. Inter-/Intra-Process Communication

Android offers a unique collection of mechanisms for inter-application (and intra-application) communication. This chapter discusses the following:

Intents

Specify what you intend to do next: either to invoke a particular class within your application, or to invoke whatever application the user has configured to process a particular request on a particular type of data

Broadcast receivers

In conjunction with Intent filters, allow you to define an application as able to process a particular request on a particular type of data (i.e., the target of an Intent)

AsyncTasks

Allow you to specify long-running code that should not be on the “GUI thread” or “main event thread” to avoid slowing the app to the point that it gets ANR (“Application Not Responding”) errors

Handlers

Allow you to queue up messages from a background thread to be handled by another thread such as the main Activity thread, usually to cause information to update the screen safely

4.1 Opening a Web Page, Phone Number, or Anything Else with an Intent

Ian Darwin

Problem

You want one application to have some entity processed by another application without knowing or caring what that other application is.

Solution

Invoke the Intent constructor; then invoke startActivity() on the constructed Intent.

Discussion

The Intent constructor takes two arguments: the action to take and the entity to act on. Think of the first as the verb and the second as the object of the verb. The most common action is Intent.ACTION_VIEW, for which the string representation is android.intent.action.VIEW. The second will typically be a URL or, in Android, a URI (uniform resource identifier). URI objects can be created using the static parse() method in the Uri class (note the two lowercase letters in the class name do not use the URI class from java.net). Assuming that the string variable data contains the location we want to view, the code to create an Intent for it might be something like the following:

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(data));

That’s all! The beauty of Android is shown here—we don’t know or care if data contains a web page URL with http:, a phone number with tel:, or even something we’ve never seen. As long as there is an application registered to process this type of Intent, Android will find it for us, after we invoke it. How do we invoke the Intent? Remember that Android will start a new Activity to run the Intent. Assuming the code is in an Activity, we just call the inherited startIntent() method. For example:

startActivity(intent);

If all goes well, the user will see the web browser, phone dialer, maps application, or whatever.

Google defines many other actions, such as ACTION_OPEN (which tries to open the named object). In some cases VIEW and OPEN will have the same effect, but in other cases the former may display data and the latter may allow the user to edit or update the data.

If the request fails because your particular device doesn’t have a single Activity in all its applications that has said it can handle this particular Intent, the user will not see another Activity, but instead the startActivity() call will throw the unchecked ActivityNotFoundException.

And even if things do work, we won’t find out about it. That’s because we basically told Android that we don’t care whether the Intent succeeds or fails. To get feedback, we would instead call startActivityForResult():

startActivityForResult(intent, requestCode);

The requestCode is an arbitrary number used to keep track of multiple Intent requests; you should generally pick a unique number for each Intent you start, and keep track of these numbers to track the results later (if you only have one Intent whose results you care about, just use the number 1).

Just making this change will have no effect, however, unless we also override an important method in Activity:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Do something with the results...
}

It may be obvious, but it is important to note that you cannot know the result of an Intent until the Activity that was processing it is finished, which may be an arbitrary time later. However, onActivityResult() will eventually be called.

The resultCode is, of course, used to indicate success or failure. There are defined constants for these, notably Activity.RESULT_OK and Activity.RESULT_CANCELED. Some Intents provide their own, more specific result codes; for one example, see Recipe 9.7. For information on use of the passed Intent, please refer to recipes on passing extra data, such as Recipe 4.4.

The sample program attached to this recipe allows you to type in a URL and either OPEN or VIEW it, using the actions defined previously. Some example URLs that you might try are shown in the following table.

URL Meaning Note

http://www.google.com/

Web page

content://contacts/people/

List of contacts

content://contacts/people/1

Contact details for one person

geo:50.123,7.1434?z=10

Location and zoom

Need Google API

geo:39.0997,-94.5783

Location

Need Google API

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory IntentsDemo (see “Getting and Using the Code Examples”).

4.2 Emailing Text from a View

Wagied Davids

Problem

You want to send an email containing text or images from a view.

Solution

Pass the data to be emailed to the mail app as a parameter using an Intent.

Discussion

The steps for emailing text from a view are pretty straightforward:

  1. Modify the AndroidManifest.xml file to allow for an internet connection so that email can be sent. This is shown in Example 4-1.

  2. Create the visual presentation layer with an Email button that the user clicks. The layout is shown in Example 4-2, and the strings used to populate it are shown in Example 4-3.

  3. Attach an OnClickListener to allow the email to be sent when the user clicks the Email button. The code for this is shown in Example 4-4.

Example 4-1. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.examples"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-permission
            android:name="android.permission.INTERNET" />

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name">
        <activity
            android:name=".Main"
            android:label="@string/app_name">
            <intent-filter>
                <action
                    android:name="android.intent.action.MAIN" />
                <category
                    android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>
Example 4-2. 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="fill_parent">

    <Button
        android:id="@+id/emailButton"
        android:text="Email Text!"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
    </Button>

    <TextView
        android:id="@+id/text_to_email"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/my_text" />

</LinearLayout>
Example 4-3. Strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string
        name="hello">Hello World, Main!</string>
    <string
        name="app_name">EmailFromView</string>
    <string
        name="my_text">
        Lorem Ipsum is simply dummy text of the printing and typesetting industry.
        Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
        when an unknown printer took a galley of type and scrambled it to make a
        type specimen book. It has survived not only five centuries, but also the
        leap into electronic typesetting, remaining essentially unchanged. It was
        popularized in the 1960s with the release of Letraset sheets containing Lorem
        Ipsum passages, and more recently with desktop publishing software like
        Aldus PageMaker including versions of Lorem Ipsum.
</string>
</resources>
Example 4-4. Main.java
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class Main extends Activity implements OnClickListener {
        private static final String TAG = "Main";
        private Button emailButton;

        /** Called when the Activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);

                // Set the view layer
                setContentView(R.layout.main);

                // Get reference to Email button
                this.emailButton = (Button) this.findViewById(R.id.emailButton);

                // Set the onClick event listener
                this.emailButton.setOnClickListener(this);

            }

        @Override
        public void onClick(View view) {
            if (view == this.emailButton) {
                Intent emailIntent = new Intent(Intent.ACTION_SEND);
                emailIntent.setType("text/html");
                emailIntent.putExtra(Intent.EXTRA_TITLE, "My Title");
                emailIntent.putExtra(Intent.EXTRA_SUBJECT, "My Subject");

                // Obtain reference to String and pass it to Intent
                emailIntent.putExtra(Intent.EXTRA_TEXT,
                    getString(R.string.my_text));
                startActivity(emailIntent);
            }
        }
    }

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory EmailTextView (see “Getting and Using the Code Examples”).

4.3 Sending an Email with Attachments

Marco Dinacci

Problem

You want to send an email with attachments.

Solution

Create an Intent, add extended data to specify the file you want to include, and start a new Activity to allow the user to send the email.

Discussion

The easiest way to send an email is to create an Intent of type ACTION_SEND:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_SUBJECT, "Test single attachment");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{recipient_address});
intent.putExtra(Intent.EXTRA_TEXT, "Mail with an attachment");

To attach a single file, we add some extended data to our Intent:

intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File("/path/to/file")));
intent.setType("text/plain");

The MIME type can always be set as text/plain, but you may want to be more specific so that applications parsing your message will work properly. For instance, if you’re including a JPEG image you should write image/jpeg.

To send an email with multiple attachments, the procedure is slightly different, as shown in Example 4-5.

Example 4-5. Multiple attachments
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, "Test multiple attachments");
intent.putExtra(Intent.EXTRA_TEXT, "Mail with multiple attachments");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{recipient_address});

ArrayList<Uri> uris = new ArrayList<Uri>();
uris.add(Uri.fromFile(new File("/path/to/first/file")));
uris.add(Uri.fromFile(new File("/path/to/second/file")));

intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);

First, you need to use Intent.ACTION_SEND_MULTIPLE, which has been available since Android 1.6. Second, you need to create an ArrayList with the URIs of the files you want to attach to the mail and call putParcelableArrayListExtra(). If you are sending different types of files you may want to use multipart/mixed as the MIME type.

Finally, in both cases, don’t forget to start the desired Activity with the following code:

startActivity(this, intent);

Which mail program will be used if there’s more than one on the device? If the user has previously made a choice that will be respected, but if the user hasn’t selected an application to handle this type of Intent a chooser will be launched.

The example in the source download shows both the single attachment and multiple attachment options, each connected to a Button with obvious labeling. The multiple attachment button looks like Figure 4-1 in my email client.

ack2 0401
Figure 4-1. Email with multiple attachments

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory EmailWithAttachments (see “Getting and Using the Code Examples”).

4.4 Pushing String Values Using Intent.putExtra()

Ulysses Levy

Problem

You need to pass some parameters into an Activity while launching it.

Solution

A quick solution is to use Intent.putExtra() to push the data. Then use getIntent().getExtras().getString() to retrieve it.

Discussion

Example 4-6 shows the code to push the data.

Example 4-6. The push data
import android.content.Intent;
    ...

    Intent intent =
        new Intent(
            this,
            MyActivity.class );
    intent.putExtra( "paramName", "paramValue" );
    startActivity( intent );

This code might be inside the main Activity. MyActivity.class is the second Activity we want to launch and it must be explicitly included in your AndroidManifest.xml file:

<activity android:name=".MyActivity" />

Example 4-7 shows the code to pull the data in the target (receiving) Activity.

Example 4-7. The code to pull the data
import android.os.Bundle;

    ...

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
        String myParam = extras.getString("paramName");
    }
    else {
        //Oops!
    }

In this example, the code would be inside your main Activity.java file.

In addition to Strings, the Bundle (the “extras”) can contain several other types of data; see the documentation for Bundle for a complete list.

4.5 Retrieving Data from a Subactivity Back to Your Main Activity

Ulysses Levy

Problem

Your main Activity needs to retrieve data from another Activity, sometimes informally called a “subactivity.”

Solution

Use startActivityForResult(), onActivityResult() in the main Activity, and setResult() in the subactivity.

Discussion

In this example, we return a string from a subactivity (MySubActivity) back to the main Activity (MyMainActivity). The first step is to “push” data from MyMainActivity via the Intent mechanism (see Example 4-8).

Example 4-8. The push data from the Activity
public class MyMainActivity extends Activity
{
    //For logging
    private static final String TAG = "MainActivity";

    //The request code is supposed to be unique
    public static final int MY_REQUEST_CODE = 123;

    @Override
    public void onCreate( Bundle savedInstanceState ) {
        ...
    }

    private void pushFxn() {
        Intent intent =
            new Intent(
                this,
                MySubActivity.class );

        startActivityForResult( intent, MY_REQUEST_CODE );
    }

    protected void onActivityResult(
            int requestCode,
            int resultCode,
            Intent pData) {
         if ( requestCode == MY_REQUEST_CODE ) {
             if (resultCode == Activity.RESULT_OK ) {
                 final String zData = pData.getExtras().getString
                     ( MySubActivity.EXTRA_STRING_NAME );

                 //Do something with our retrieved value

                 Log.v( TAG, "Retrieved Value zData is "+zData );
                 //Logcats "Retrieved Value zData is returnValueAsString"

             }
         }

     }
}

There will be a button with an event listener that calls the pushFxn() method; this starts the subactivity. In Example 4-8, the following occurs:

  • The main Activity’s onActivityResult() method gets called after MySubActivity.finish().

  • The retrieved value is technically an Intent, and so we could use it for more complex data (such as a URI to a Google contact). However, in Example 4-8, we are only interested in a string value via Intent.getExtras().

  • The requestCode (MY_REQUEST_CODE) is supposed to be unique, and is used to differentiate among multiple outstanding subactivity calls.

The second major step is to “pull” data back from MySubActivity to MyMainActivity (see Example 4-9).

Example 4-9. The pull data from the subactivity
public class MySubActivity extends Activity
{
    public static final String EXTRA_STRING_NAME = "extraStringName";

    @Override
    public void onCreate( Bundle savedInstanceState ) {
        ...
    }


    private void returnValuesFxn() {
        Intent iData = new Intent();
        iData.putExtra(
            EXTRA_STRING_NAME,
            "returnValueAsString" );

        setResult(
            android.app.Activity.RESULT_OK,
            iData );

        //Returns us to the parent "MyMainActivity"
        finish();
    }
}

Again, something in the MySubActivity will call the returnValuesFxn() method in Example 4-9. Note the following:

  • Once again, Intents are used as data (i.e., iData).

  • setResult() requires a result code such as RESULT_OK.

  • finish() essentially pushes the result from setResult().

  • The data from MySubActivity doesn’t get “pulled” until we’re back on the other side with MyMainActivity, so arguably it is more similar to a second “push.”

  • We don’t have to use a public static final String variable for our “extra” field name, but I chose to do so because I thought it was a good style.

Use case (informal)

In my app, I have a ListActivity with a ContextMenu (the user long-presses a selection to do something), and I wanted to let the MainActivity know which row the user had selected for the ContextMenu action (my app only has one action). I ended up using Intent extras to pass the selected row’s index as a string back to the parent Activity; from there I could just convert the index back to an int and use it to identify the user’s row selection via ArrayList.get(index). This worked for me; however, I am sure there is another/better way.

See Also

Recipe 4.4, resultCodegotcha, startActivityForResultExample (under “Returning a Result from a Screen”); Activity.startActivityForResult().

4.6 Keeping a Background Service Running While Other Apps Are on Display

Ian Darwin

Problem

You want part of your application to continue running in the background while the user switches to interact with other apps.

Solution

Create a Service class to do the background work; start the Service from your main application. Optionally provide a Notification icon to allow the user either to stop the running Service or to resume the main application.

Discussion

A Service class (android.app.Service) runs as part of the same process as your main application, but keeps running even if the user switches to another app or goes to the Home screen and starts up a new app.

As you know by now, Activity classes can be started either by an Intent that matches their content provider or by an Intent that mentions them by class name. The same is true for Services. This recipe focuses on starting a Service directly; Recipe 4.1 covers starting a Service implicitly. The following example is taken from JPSTrack, a GPS tracking program for Android. Once you start tracking, you don’t want the tracking to stop if you answer the phone or have to look at a map(!), so we made it into a Service. As shown in Example 4-10, the Service is started by the main Activity when you click the Start Tracking button, and is stopped by the Stop button. Note that this is so common that startService() and stopService() are built into the Activity class.

Example 4-10. The onCreate{} method
@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    Intent theIntent = new Intent(this, TrackService.class);
    Button startButton = (Button) findViewById(R.id.startButton);
    startButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(theIntent);
                Toast.makeText(Main.this, "Starting", Toast.LENGTH_LONG).show();
            }
    });
    Button stopButton = (Button) findViewById(R.id.stopButton);
    stopButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(theIntent);
                Toast.makeText(Main.this, "Stopped", Toast.LENGTH_LONG).show();
            }
    });
    ...
}

The TrackService class directly extends Service, so it has to implement the abstract onBind() method. This is not used when the class is started directly, so it can be a stub method. You will typically override at least the onStartCommand() and onUnbind() methods, to begin and end some Activity. Example 4-11 starts the GPS service sending us notifications that we save to disk, and we do want that to keep running, hence this Service class.

Example 4-11. The TrackService (GPS-using service) class
public class TrackService extends Service {
        private LocationManager mgr;
        private String preferredProvider;

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

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
                initGPS();      // Sets up the LocationManager mgr

                if (preferredProvider != null) {
                    mgr.requestLocationUpdates(preferredProvider, MIN_SECONDS * 1000,
                                    MIN_METRES, this);
                        return START_STICKY;
                }
                return START_NOT_STICKY;
        }

        @Override
        public boolean onUnbind(Intent intent) {
                mgr.removeUpdates(this);
                return super.onUnbind(intent);
        }

You may have noticed the different return values from onStartCommand(). If you return START_STICKY, Android will restart your Service if it gets terminated. If you return START_NOT_STICKY, the Service will not be restarted automatically. These values are discussed in more detail in the online documentation for the Service class. Remember to declare the Service subclass in the Application part of your AndroidManifest.xml:

<service android:enabled="true" android:name=".TrackService">

4.7 Sending/Receiving a Broadcast Message

Vladimir Kroz

Problem

You want to create an Activity that receives a simple broadcast message sent by another Activity.

Solution

Set up a broadcast receiver, instantiate the message receiver object, and create an IntentFilter. Then register your receiver with an Activity that must receive the broadcast message.

Discussion

The code in Example 4-12 sets up the broadcast receiver, instantiates the message receiver object, and creates the IntentFilter.

Example 4-12. Creating and registering the BroadcastReceiver
// Instantiate message receiver object. You should
// create this class by extending android.content.BroadcastReceiver.
// The method onReceive() of this class will be called when broadcast is sent.
MyBroadcastMessageReceiver _bcReceiver = new MyBroadcastMessageReceiver();

// Create IntentFilter
IntentFilter filter = new IntentFilter(MyBroadcastMessageReceiver.class.getName());

// Register your receiver with your Activity, which must receive broadcast messages.
// Now whenever this type of message is generated somewhere in the system the
// _bcReceiver.onReceive() method will be called within main thread of myActivity.
myActivity.registerReceiver(_bcReceiver, filter);

The code in Example 4-13 shows how to publish the broadcast event.

Example 4-13. Publishing the broadcast event
Intent intent = new Intent(MyBroadcastMessageReceiver.class.getName());
intent.putExtra("some additional data", choice);
someActivity.sendBroadcast(intent);

4.8 Starting a Service After Device Reboot

Ashwini Shahapurkar

Problem

You have a Service in your app and you want it to start after the phone reboots.

Solution

Listen to the Intent for boot events and start the Service when the event occurs.

Discussion

Whenever a platform boot is completed, an Intent is broadcast with the android.intent.action.BOOT_COMPLETED action. You need to register your application to receive this Intent, and to request permission for it. To do so, add the following code to your AndroidManifest.xml file:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application>
    <receiver android:name=".ServiceManager">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
    </receiver>
...

For ServiceManager to be the broadcast receiver that receives the Intent for the boot event, the ServiceManager class would be coded as shown in Example 4-14.

Example 4-14. The BroadcastReceiver implementation
public class ServiceManager extends BroadcastReceiver {

    Context mContext;
    private final String BOOT_ACTION = "android.intent.action.BOOT_COMPLETED";

    @Override
    public void onReceive(Context context, Intent intent) {
               // All registered broadcasts are received by this
        mContext = context;
        String action = intent.getAction();
        if (action.equalsIgnoreCase(BOOT_ACTION)) {
                        // Check for boot complete event & start your service
            startService();
        }
    }

    private void startService() {
        // Here, you will start your service
        Intent mServiceIntent = new Intent();
        mServiceIntent.setAction("com.bootservice.test.DataService");
        mContext.startService(mServiceIntent);
    }
}

4.9 Creating a Responsive Application Using Threads

Amir Alagic

Problem

You have an application that performs long tasks, and you don’t want your application to appear nonresponsive while these are ongoing.

Solution

By using threads, you can create an application that is responsive even when it is handling time-consuming operations.

Discussion

To make your application responsive while time-consuming operations are running on the Android OS you have a few options. If you already know Java, you know you can create a class that extends the Thread class and overrides the public void run() method and then call the start() method on that object to run the time-consuming process. If your class already extends another class, you can implement the Runnable interface. Another approach is to create your own class that extends Android’s AsyncTask class, but we will talk about AsyncTask in Recipe 4.10.

In the early days of Java and Android, we were taught about direct use of the Thread class. This pattern was coded as follows:

Thread thread = new Thread(new Runnable() {        // Deprecated, do not use!
    public void run() {
        getServerData();
    }
});
thread.start();

There are many issues around this usage of threads, but the biggest strike against it is the overhead of creating threads. For all but the simplest cases, it is now recommended to use thread pools, which have been in Java for half of its lifetime. Example 4-15 shows the pool-based implementation of this class.

Example 4-15. The networked Activity implementation
public class NetworkConnection extends Activity {

    ExecutorService pool = Executors.newSingleThreadExecutor();

    /** Called when the Activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        pool.submit(new Runnable() {
            public void run() {
                getServerData();
            }
        });
   }
}

As you can see, when we start our Activity in the onCreate() method we create and submit a Runnable object. The Runnable method run() will be executed some time after we call the submit() method on the pool. From here you can call another method or a few other methods and operations that are time-consuming and that would otherwise block the main thread and make your application look unresponsive.

Often when we are done with the thread we get results that we want to present to the application user. If you try to update the GUI from the thread that you started (not the main thread) your application will crash. The Android UI is not thread safe—this was done deliberately, for performance reasons—so that if you try to change any UI component (even a single call to, e.g, someTextView.setText()) from a thread other than the main thread, your app will crash.

Of course, there are several ways to send data from background threads to the UI. One way is to use a Handler class; see Recipe 4.11. Alternatively, you can divide your code differently using AsyncTask (see Recipe 4.10).

4.10 Using AsyncTask to Do Background Processing

Johan Pelgrim

Problem

You have to do some heavy processing, or load resources from the network, and you want to show the progress and results in the UI.

Solution

Use AsyncTask and ProgressDialog.

Discussion

As explained in the Processes and Threads” section of the Android Developers API Guides, you should never block the UI thread, or access the Android UI toolkit from outside the UI thread. Bad things will happen if you do.

You can run processes in the background and update the UI inside the UI thread (a.k.a. the main thread) in several ways, but using the AsyncTask class is very convenient and every Android developer should know how to do so.

The steps boil down to creating a class that extends AsyncTask. AsyncTask itself is abstract and has one abstract method, Result doInBackground(Params… params);. The AsyncTask simply creates a callable working thread in which your implementation of doInBackground() runs. Result and Params are two of the types we need to define in our class definition. The third is the Progress type, which we will talk about later.

Our first implementation will do everything in the background, showing the user a spinner in the title bar and updating the ListView once the processing is done. This is the typical use case, not interfering with the user’s task at hand and updating the UI when we have retrieved the result.

The second implementation will use a modal dialog to show the processing progressing in the background. In some cases we want to prevent the user from doing anything else when some processing takes place, and this is a good way to do just that.

We will create a UI that contains three Buttons and a ListView. The first button is to start our first refresh process. The second is for the other refresh process and the third is to clear the results in the ListView (see Example 4-16).

Example 4-16. The main layout
<?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="fill_parent">
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal" android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <Button android:text="Refresh 1" android:id="@+id/button1"
            android:layout_width="fill_parent" android:layout_height="wrap_content"
            android:layout_weight="1"></Button>
        <Button android:text="Refresh 2" android:id="@+id/button2"
            android:layout_width="fill_parent" android:layout_height="wrap_content"
            android:layout_weight="1"></Button>
        <Button android:text="Clear" android:id="@+id/button3"
            android:layout_width="fill_parent" android:layout_height="wrap_content"
            android:layout_weight="1"></Button>
    </LinearLayout>
    <ListView android:id="@+id/listView1" android:layout_height="fill_parent"
        android:layout_width="fill_parent"></ListView>
</LinearLayout>

We assign these UI elements to various fields in onCreate() and add some click listeners (see Example 4-17).

Example 4-17. The onCreate() and onItemClick() methods
ListView mListView;
Button mClear;
Button mRefresh1;
Button mRefresh2;

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

    mListView = (ListView) findViewById(R.id.listView1);
    mListView.setTextFilterEnabled(true);
    mListView.setOnItemClickListener(this);

    mRefresh1 = (Button) findViewById(R.id.button1);

    mClear = (Button) findViewById(R.id.button3);
    mClear.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            mListView.setAdapter(null);
        }
    });

}

public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    Datum datum = (Datum) mListView.getItemAtPosition(position);
    Uri uri = Uri.parse("http://androidcookbook.com/Recipe.seam?recipeId=" +
        datum.getId());
    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    this.startActivity(intent);
}

The following subsections describe two use cases: processing in the background and processing in the foreground.

Use case 1: Processing in the background

First we create an inner class that extends AsyncTask:

protected class LoadRecipesTask1 extends AsyncTask<String, Void, ArrayList<Datum>> {
...
}

As you can see, we must supply three types to the class definition. The first is the type of the parameter we will provide when starting this background task—in our case a String, containing a URL. The second type is used for progress updates (we will use this later). The third type is the type returned by our implementation of the doInBackground() method, and typically is something with which you can update a specific UI element (a ListView in our case).

Let’s implement the doInBackground() method:

@Override
protected ArrayList<Datum> doInBackground(String... urls) {
    ArrayList<Datum> datumList = new ArrayList<Datum>();
    try {
        datumList = parse(urls[0]);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (XmlPullParserException e) {
        e.printStackTrace();
    }
    return datumList;
}

As you can see, this is pretty simple. The parse() method—which creates a list of Datum objects—is not shown as it’s related to a specific data format in one application. The result of the doInBackground() method is then passed as an argument to the onPostExecute() method in the same (inner) class. In this method we are allowed to update the UI elements in our layout, so we set the adapter of the ListView to show our result:

@Override
protected void onPostExecute(ArrayList<Datum> result) {
    mListView.setAdapter(new ArrayAdapter<Datum>(MainActivity.this,
        R.layout.list_item, result));
}

Now we need a way to start this task. We do this in mRefresh1’s onClickListener() by calling the execute(Params… params) method of AsyncTask (execute(String… urls) in our case):

mRefresh1.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        LoadRecipesTask1 mLoadRecipesTask = new LoadRecipesTask1();
        mLoadRecipesTask.execute(
            "http://androidcookbook.com/seam/resource/rest/recipe/list");
    }
});

Now, when we start the app it indeed retrieves the recipes and fills the ListView, but the user has no idea that something is happening in the background. In order to notify the user of ongoing activity, we can set the window state to “indefinite progress”; this displays a small progress animation in the top right of our app’s title bar. We request this feature by calling the following method in onCreate(): requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS). (Be aware that this feature is deprecated; we will show a better way to keep the user informed on progress in use case 2, next.)

Then we can start the progress animation by calling the set​ProgressBarIndeterminateVisibility(boolean visibility) method from within a new method in our inner class, the onPreExecute() method:

protected void onPreExecute() {
    MainActivity.this.setProgressBarIndeterminateVisibility(true);
}

We stop the spinning progress bar in our window title by calling the same method from within our onPostExecute() method, which will become:

protected void onPostExecute(ArrayList<Datum> result) {
    mListView.setAdapter(new ArrayAdapter<Datum>(MainActivity.this,
        R.layout.list_item, result));
    MainActivity.this.setProgressBarIndeterminateVisibility(false);
}

We’re done! Take your app for a spin (pun intended). See Figure 4-2.

ack2 0402
Figure 4-2. The AsyncTask demo in action

As you can see, this is a nifty feature for creating a better user experience!

Use case 2: Processing in the foreground

In this example, we show a modal dialog to the user that displays the progress of loading the recipes in the background. Such a dialog is called a ProgressDialog. First we add it as a field to our Activity:

ProgressDialog mProgressDialog;

Then we add the onCreateDialog() method to be able to answer showDialog() calls and create our dialog:

protected Dialog onCreateDialog(int id) {
        switch (id) {
        case DIALOG_KEY:                                                       1
            mProgressDialog = new ProgressDialog(this);
            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 2
            mProgressDialog.setMessage("Retrieving recipes...");               3
            mProgressDialog.setCancelable(false);                              4
            return mProgressDialog;
        }
        return null;
    }
1

We should handle the request and creation of all dialogs here. The DIALOG_KEY is an int constant with an arbitrary value (we used 0) to identify this dialog.

2

We set the progress style to STYLE_HORIZONTAL, which shows a horizontal progress bar. The default is STYLE_SPINNER.

3

We set our custom message, which is displayed above the progress bar.

4

By calling setCancelable() with the argument false we disable the Back button, making this dialog modal.

Our new implementation of AsyncTask is as shown in Example 4-18.

Example 4-18. The AsyncTask implementation
protected class LoadRecipesTask2 extends AsyncTask<String, Integer, ArrayList<Datum>>{

        @Override
        protected void onPreExecute() {
            mProgressDialog.show();                                               1
        }

        @Override
        protected ArrayList<Datum> doInBackground(String... urls) {
            ArrayList<Datum> datumList = new ArrayList<Datum>();
            for (int i = 0; i < urls.length; i++) {                               2
                try {
                    datumList = parse(urls[i]);
                    publishProgress((int) (((i+1) / (float) urls.length) * 100)); 3
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (XmlPullParserException e) {
                    e.printStackTrace();
                }
            }
            return datumList;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {                      4
            mProgressDialog.setProgress(values[0]);                               5
        }

        @Override
        protected void onPostExecute(ArrayList<Datum> result) {
            mListView.setAdapter(new ArrayAdapter<Datum>(
                MainActivity.this, R.layout.list_item, result));
            mProgressDialog.dismiss();                                            6
        }
    }

We see a couple of new things here:

1

Before we start our background process we show the modal dialog.

2

In our background process we loop through all the URLs, expecting to receive more than one. This will give us a good indication of our progress.

3

We can update the progress by calling publishProgress(). Notice that the argument is of type int, which will be auto-boxed to the second type defined in our class definition, Integer.

4

The call to publishProgress() will result in a call to onProgressUpdate(), which again has arguments of type Integer. You could, of course, use String or something else as the argument type by simply changing the second type in the inner class definition to String and, of course, in the call to publishProgress().

5

We use the first Integer to set the new progress value in our ProgressDialog.

6

We dismiss the dialog, which removes it.

Now we can bind this all together by implementing our onClickListener() for our second refresh button.:

mRefresh2.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        LoadRecipesTask2 mLoadRecipesTask = new LoadRecipesTask2();
        String url =
            "http://androidcookbook.com/seam/resource/rest/recipe/list";
        showDialog(DIALOG_KEY);                             1
        mLoadRecipesTask.execute(url, url, url, url, url);  2
    }
});
1

We show the dialog by calling showDialog() with the DIALOG_KEY argument, which will trigger our previously defined onCreateDialog() method.

2

We execute our new task with five URLs, simply to show a little bit of progress.

It will look something like Figure 4-3.

ack2 0403
Figure 4-3. Retrieving recipes in the background

Implementing background tasks with AsyncTask is very simple and should be done for all long-running processes that also need to update your user interface.

See Also

The developer documentation on processes and threads.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory RecipeList (see “Getting and Using the Code Examples”).

4.11 Sending Messages Between Threads Using an Activity Thread Queue and Handler

Vladimir Kroz

Problem

You need to pass information or data from a Service or other background task to an Activity. Because Activities run on the UI thread, it is not safe to call them from a background thread.

Solution

You can write a nested class that extends Android’s Handler class, then override the handleMessage() method that will read messages from the thread queue. Pass this handler to the worker thread, usually via the worker class’s constructor; in the worker thread, post messages using the various obtainMessage() and sendMessage() methods. This will cause the Activity to be called in the handleMessage() method, but on the event thread so that you can safely update the GUI.

Discussion

There are many situations in which you must have a thread running in the background and send information to the main Activity’s UI thread. At the architectural level, you can take one of the following two approaches:

  • Use Android’s AsyncTask class.

  • Start a new thread.

Though using AsyncTask is very convenient, sometimes you really need to construct a worker thread by yourself. In such situations, you likely will need to send some information back to the Activity thread. Keep in mind that Android doesn’t allow other threads to modify any content of the main UI thread. Instead, you must wrap the data into messages and send the messages through the message queue.

To do this, you must first add an instance of the Handler class to, for example, your MapActivity instance (see Example 4-19).

Example 4-19. The handler
public class MyMap extends MapActivity {
    .
    public Handler _handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, String.format("Handler.handleMessage(): msg=%s", msg));
            // This is where the main Activity thread receives messages
            // Put incoming message handling posted by other threads here
            super.handleMessage(msg);
        }

    };
.
}

Now, in the worker thread, post a message to the Activity’s main queue whenever you need to add the Handler class instance to your main Activity instance (see Example 4-20).

Example 4-20. Posting a Runnable to the queue
    /**
    * Performs background job
    */
    class MyThreadRunner implements Runnable {
        // @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                // Dummy message -- real implementation
                // will put some meaningful data in it
                Message msg = Message.obtain();
                msg.what = 999;
                MyMap.this._handler.sendMessage(msg);
                // Dummy code to simulate delay while working with remote server
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

4.12 Creating an Android Epoch HTML/JavaScript Calendar

Wagied Davids

Problem

You need a custom calendar written in JavaScript, and you want it to understand how to interact between JavaScript and Java.

Solution

Use a WebView component to load an HTML file containing the Epoch JavaScript calendar component.

Note

An epoch is a contiguous set of years, such as the years of recorded history BC or BCE, the years AD or CE, or the years since “time zero,” the beginning of modern computer timekeeping (January 1, 1970 in Unix, macOS, Java 1.0’s Date class, some MS Windows time functions, and so on). The “Epoch” discussed here is a JavaScript calendaring package that takes its name from the conventional meaning of epoch.

Briefly, here are the steps in creating this calendar app:

  1. Download the Epoch DHTML/JavaScript calendar.

  2. Create an assets directory under your Android project folder (e.g., TestCalendar/assets/).

  3. Code your main HTML file for referencing the Epoch calendar.

  4. Create an Android Activity for launching the Epoch calendar.

Files placed in the Android assets directory are referenced as file:///android_asset/ (note the triple leading slash and the singular spelling of asset).

Discussion

To enable interaction between the JavaScript-based view layer and the Java-based logic layer, a Java‒JavaScript bridge interface is required: the MyJavaScriptInterface inner class. The onDayClick() function, shown in Example 4-21, shows how to call a JavaScript function from an Android Activity—for example, webview.loadUrl("javascript: popup();");. The HTML/JavaScript component is shown in Example 4-21, and the Java Activity code is shown in Example 4-22.

Example 4-21. calendarview.html
<html>
    <head>
        <title>My Epoch DHTML JavaScript Calendar</title>
        <style type="text/css">
            dateheader {
                -background-color: #3399FF;
                -webkit-border-radius: 10px;
                -moz-border-radius: 10px;
                -border-radius: 10px;
                -padding: 5px;
            }
        </style>

        <style type="text/css">
        html {height:100%;}
        body {height:100%; margin:0; padding:0;}
        #bg {position:fixed; top:0; left:0; width:100%; height:100%;}
        #content {position:relative; z-index:1;}
        </style>
        <!--[if IE 6]>
        <style type="text/css">
        html {overflow-y:hidden;}
        body {overflow-y:auto;}
        #page-background {position:absolute; z-index:-1;}
        #content {position:static;padding:10px;}
        </style>
        <![endif]-->

        <link rel="stylesheet" type="text/css" href="epoch_v106/epoch_styles.css" />
        <script type="text/javascript" src="epoch_v106/epoch_classes.js"></script>

        <script type="text/javascript">
            /*You can also place this code in a separate
              file and link to it like epoch_classes.js*/
            var my_cal;

            window.onload = function () {
                my_cal = new Epoch('epoch_basic','flat',
                    document.getElementById('basic_container'));
            };

            function popup() {
                var weekday=new Array("Sun","Mon","Tue","Wed","Thur","Fri","Sat");
                var monthname=new Array("Jan","Feb","Mar","Apr","May","Jun",
                    "Jul","Aug","Sep","Oct","Nov","Dec");
                var date = my_cal.selectedDates.length > 0 ?
                    my_cal.selectedDates[0] :
                    null;
                if ( date != null ) {
                        var day = date.getDate();
                        var dayOfWeek= date.getDay();
                        var month = date.getMonth();
                        var yy = date.getYear();
                        var year = (yy < 1000) ? yy + 1900 : yy;

                        /* Set the user-selected date in HTML form */
                        var dateStr= weekday[dayOfWeek]  + ", " + day + " " +
                            monthname[month] + " " + year;
                            document.getElementById("selected_date").value= dateStr;

                            /* IMPORTANT:
                             * Call Android JavaScript->Java bridge setting a
                             * Java-field variable
                             */
                            window.android.setSelectedDate( date );
                            window.android.setCalendarButton( date );
                        }
                }
        </script>
    </head>
    <body>
    <div id="bg"><img src="bg.png" width="100%" height="100%" alt=""></div>
        <div id="content">
            <div class="dateheader" align="center">
                <form name="form_selected_date">
                        <span style="color:white">Selected day:</span>
                        <input id="selected_date" name="selected_date" type="text"
                            readonly="true">
                </form>
            </div>
            <div id="basic_container" onClick="popup()"></div>
        </div>
    </body>
</head>>
Example 4-22. CalendarViewActivity.java
import java.util.Date;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.pfizer.android.R;
import com.pfizer.android.utils.DateUtils;
import com.pfizer.android.view.screens.journal.CreateEntryScreen;

public class CalendarViewActivity extends Activity {
        private static final String tag = "CalendarViewActivity";
        private ImageView calendarToJournalButton;
        private Button calendarDateButton;
        private WebView webview;
        private Date selectedCalDate;

        private final Handler jsHandler = new Handler();

        /** Called when the Activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
                Log.d(tag, "Creating View ...");
                super.onCreate(savedInstanceState);

                // Set the view layer
                Log.d(tag, "Setting-up the View Layer");
                setContentView(R.layout.calendar_view);

                // Go to CreateJournalEntry
                calendarToJournalButton = (ImageView) this.findViewById
                    (R.id.calendarToJournalButton);
                calendarToJournalButton.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                                Log.d(tag, "Re-directing -> CreateEntryScreen ...");
                                Intent intent = intent =
                                    new Intent(getApplicationContext(),
                                        CreateEntryScreen.class);
                                startActivity(intent);
                            }
                    });

                // User-selected calendar date
                calendarDateButton =
                 (Button) this.findViewById(R.id.calendarDateButton);

                // Get access to the WebView holder
                webview = (WebView) this.findViewById(R.id.webview);

                // Get the settings
                WebSettings settings = webview.getSettings();

                // Enable JavaScript
                settings.setJavaScriptEnabled(true);

                // Enable ZoomControls visibility
                settings.setSupportZoom(true);

                // Add JavaScript interface
                webview.addJavaScriptInterface(new MyJavaScriptInterface(), "android");

                // Set the Chrome client
                webview.setWebChromeClient(new MyWebChromeClient());

                // Load the URL of the HTML file
                webview.loadUrl("file:///android_asset/calendarview.html");

            }

        public void setCalendarButton(Date selectedCalDate) {
                Log.d(tag, jsHandler.obtainMessage().toString());
                calendarDateButton.setText(
                    DateUtils.convertDateToSectionHeaderFormat(
                        selectedCalDate.getTime()));
            }

        /**
         *
         * @param selectedCalDate
         */
        public void setSelectedCalDate(Date selectedCalDate) {
                this.selectedCalDate = selectedCalDate;
            }

        /**
         *
         * @return
         */
        public Date getSelectedCalDate() {
                return selectedCalDate;
            }

        /**
         * JAVA->JAVASCRIPT INTERFACE
         *
         * @author wagied
         *
         */
        final class MyJavaScriptInterface {
                private Date jsSelectedDate;
                MyJavaScriptInterface() {
                        // EMPTY;
                }

                public void onDayClick() {
                        jsHandler.post(new Runnable() {
                                public void run() {
                                        // Java telling JavaScript to do things
                                        webview.loadUrl("javascript: popup();");
                                    }
                            });
                    }

                /**
                 * NOTE: THIS FUNCTION IS BEING SET IN JAVASCRIPT
                 * User-selected date in WebView
                 *
                 * @param dateStr
                 */
                public void setSelectedDate(String dateStr) {
                        Toast.makeText(getApplicationContext(), dateStr,
                            Toast.LENGTH_SHORT).show();
                        Log.d(tag, "User Selected Date: JavaScript -> Java : " + dateStr);

                        // Set the user-selected calendar date
                        setJsSelectedDate(new Date(Date.parse(dateStr)));
                        Log.d(tag, "java.util.Date Object: " +
                            Date.parse(dateStr).toString());
                    }
                private void setJsSelectedDate(Date userSelectedDate) {
                        jsSelectedDate = userSelectedDate;
                    }
                public Date getJsSelectedDate() {
                        return jsSelectedDate;
                    }
            }

        /**
         * Alert pop-up for debugging purposes
         *
         * @author wdavid01
         *
         */
        final class MyWebChromeClient extends WebChromeClient {
                @Override
                public boolean onJsAlert(WebView view, String url,
                    String message, JsResult result) {
                        Log.d(tag, message);
                        result.confirm();
                        return true;
                    }
            }

        @Override
        public void onDestroy() {
                Log.d(tag, "Destroying View!");
                super.onDestroy();
            }
    }

For debugging purposes, a MyWebChromeClient is created—this is the final inner class extending WebChromeClient defined near the end of the main class—and in particular the onJsAlert() method is overridden.

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory EpochJSCalendar (see “Getting and Using the Code Examples”).

..................Content has been hidden....................

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