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
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 |
---|---|---|
Web page |
||
|
List of contacts |
|
|
Contact details for one person |
|
|
Location and zoom |
Need Google API |
|
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
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:
-
Modify the AndroidManifest.xml file to allow for an internet connection so that email can be sent. This is shown in Example 4-1.
-
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.
-
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
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.
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
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.
See Also
The blog post “Playing with Intents”, the developer documentation on Intent.putExtra()
.
4.5 Retrieving Data from a Subactivity Back to Your Main Activity
Ulysses Levy
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 afterMySubActivity
.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 asRESULT_OK
. -
finish()
essentially pushes the result fromsetResult()
. -
The data from
MySubActivity
doesn’t get “pulled” until we’re back on the other side withMyMainActivity
, 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, resultCode
“gotcha”, 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
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
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
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
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
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 Button
s 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 setProgressBarIndeterminateVisibility(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.
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:
mProgressDialog
=
new
ProgressDialog
(
this
)
;
mProgressDialog
.
setProgressStyle
(
ProgressDialog
.
STYLE_HORIZONTAL
)
;
mProgressDialog
.
setMessage
(
"Retrieving recipes..."
)
;
mProgressDialog
.
setCancelable
(
false
)
;
return
mProgressDialog
;
}
return
null
;
}
We should handle the request and creation of all dialogs here. The
DIALOG_KEY
is anint
constant with an arbitrary value (we used0
) to identify this dialog.We set the progress style to
STYLE_HORIZONTAL
, which shows a horizontal progress bar. The default isSTYLE_SPINNER
.We set our custom message, which is displayed above the progress bar.
By calling
setCancelable()
with the argumentfalse
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
(
)
;
}
@Override
protected
ArrayList
<
Datum
>
doInBackground
(
String
.
.
.
urls
)
{
ArrayList
<
Datum
>
datumList
=
new
ArrayList
<
Datum
>
(
)
;
for
(
int
i
=
0
;
i
<
urls
.
length
;
i
+
+
)
{
try
{
datumList
=
parse
(
urls
[
i
]
)
;
publishProgress
(
(
int
)
(
(
(
i
+
1
)
/
(
float
)
urls
.
length
)
*
100
)
)
;
}
catch
(
IOException
e
)
{
e
.
printStackTrace
(
)
;
}
catch
(
XmlPullParserException
e
)
{
e
.
printStackTrace
(
)
;
}
}
return
datumList
;
}
@Override
protected
void
onProgressUpdate
(
Integer
.
.
.
values
)
{
mProgressDialog
.
setProgress
(
values
[
0
]
)
;
}
@Override
protected
void
onPostExecute
(
ArrayList
<
Datum
>
result
)
{
mListView
.
setAdapter
(
new
ArrayAdapter
<
Datum
>
(
MainActivity
.
this
,
R
.
layout
.
list_item
,
result
)
)
;
mProgressDialog
.
dismiss
(
)
;
}
}
We see a couple of new things here:
Before we start our background process we show the modal dialog.
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.
We can update the progress by calling
publishProgress()
. Notice that the argument is of typeint
, which will be auto-boxed to the second type defined in our class definition,Integer
.The call to
publishProgress()
will result in a call toonProgressUpdate()
, which again has arguments of typeInteger
. You could, of course, useString
or something else as the argument type by simply changing the second type in the inner class definition toString
and, of course, in the call topublishProgress()
.We use the first
Integer
to set the new progress value in ourProgressDialog
.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
)
;
mLoadRecipesTask
.
execute
(
url
,
url
,
url
,
url
,
url
)
;
}
}
)
;
We show the dialog by calling
showDialog()
with theDIALOG_KEY
argument, which will trigger our previously definedonCreateDialog()
method.We execute our new task with five URLs, simply to show a little bit of progress.
It will look something like Figure 4-3.
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
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
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:
-
Download the Epoch DHTML/JavaScript calendar.
-
Create an assets directory under your Android project folder (e.g., TestCalendar/assets/).
-
Code your main HTML file for referencing the Epoch calendar.
-
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”).
Get Android Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.