- 论坛徽章:
- 0
|
Introduction
This tutorial will show you how to create a sub-Activity from a
calling-Activity, and process the results produced by the sub-Activity, if you
want to do so. Also, the various ways of launching a sub-Activity are covered,
along with the Android Activity history stack. A subclass of Activity is also
provided that makes it trivial to launch sub-Activities and respond to results
from them.
Android 101
To get started with Android,
click here
. Instead of
reading the SDK docs to get the basics, I recommend that you read
this book
to get going.
What’s an activity?
An activity is the equivalent of a Frame/Window in GUI toolkits. It takes up
the entire drawable area of the screen (minus the status and title bars on top).
An activity is what gets bound to the AndroidManifest.xml as the main entry
point into an application. For long running tasks, it’s best to use a service
that talks to this activity.
Service
Activities are meant to display the UI and get input from the user. However,
long running tasks are not meant to be spawned in a activity, since they can be
frozen when the focus is switched away from them (by the user or incoming phone
call or system event). Services on the other hand keep running for the duration
of the user’s ‘session’ on the device.
sub-Activity
An Activity (calling-Activity) can spawn another Activity (sub-Activity) in 2
ways:
Fire and forget – create an event (Intent) and fire it
Async callback – create an event (Intent), fire it, and wait for it’s
response in a callback method (of the calling-Activity).
Here
are more details on spawning new “screens” or Activities from the Android SDK
documentation.
Here’s some sample code of an Intent being created and a sub-Activity (called
WidgetActivity) being launched in response to a button being pressed (a detailed
analysis of this is provided in the sections below) in the MainActivity
class:
b4.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i = new Intent(ctx, WidgetActivity.class);
Log.i(Global.TAG, "b4 pressed - about to launch sub-activity");
// the results are called on widgetActivityCallback
ctx.startSubActivity(i, Global.WIDGET_REQ_CODE);
Log.i(Global.TAG, "b4 pressed - sucessfully launched sub-activity (startSubActivity called)");
}
});
Activity history stack
Please note that Android maintains a history stack of all the Activities that
have been spawned in an application’s Linux process. In the sample code above,
the calling-Activity simply provides the class name of the sub-Activity. When
the sub-Activity finishes, it puts it’s result code and any data back on the
stack and finishes itself. Since Android maintains the stack, it knows which
calling Activity to pass the result back to. The calling-Activity has an
onActivityResult method that handles all the callbacks from the sub-Activities.
This is pretty confusing at first, since to keep it all straight we have to use
CorrelationIds (more on that below).
1. Fire and forget
An Intent is just an event. It can have a target of an Activity class along
with some data that’s passed in via a Bundle (which is like a Hashtable).
Fire/forget is easy. Just create an Intent (with a reference to the sub-Activity
class), and call startActivity(intent, correlationId) and this will launch the
sub-Activity. This correlationId (requestCode) is used to identify which
sub-Activity called the callback method (which happens when doing the async
callback). So it’s not really needed in the fire-forget scenario.
2. Async callback, and correlationId
The calling-Activity has to provide a correlationId/request code to the
Intent/event, before firing/raising it. This is then used by the sub-Activity to
report it’s results back to the calling-Activity when it’s ready. The
calling-Activity does not stop when it spawns the sub-Activity (it does not wait
for the callback to happen to become unblocked). Please note that this
sub-Activity is not "modal", that is, the calling Activity does not block, when
startSubActivity() is called. So if you’re thinking that this is like a modal
dialog box, where the calling-Activity will wait for the sub-Activity to produce
a result, then you have to think of it differently.
The sub-Activity is an Activity. It has to have an onCreate() method to build
the UI. The only difference is that once it’s complete, it has to report it’s
results to the caller by doing the following:
setResult(resultCode, strdata, bundledata) – this is how results are sent to
the caller. The resultCode is either RESULT_CANCELED or RESULT_OK. Note that you
don’t have to identify which calling-Activity this result goes to, and you don’t
have to identify what correlation ID/requestCode to use; these are inferred by
Android since it keeps a history stack of Activity objects around. Also note
that you have to provide the correlationID when you created the Activity that
launches the sub-Activity… so this is how Android knows how to pass the results
to the calling-Activity with the correct correlationID. Whew!
![]()
finish() – equivalent of calling return… it sends control back to the
caller. Note that the calling-Activity never blocked, this is not "modal"
behavior. finish() just reclaims the resources allocated for the sub-Activity,
and removes it from the Activity history stack (for this application process).
On the calling-Activity side, the method that gets called when the
sub-Activity completes (with result or error, with or without data) is
onActivityResult(…). This method contains the result code, and any data that’s
set by the sub-Activity. If there are any errors, these get reported there as
well. There’s a special result code that is passed if there are any problems –
RESULT_CANCELED. If the sub-Activity is launched, and then you press the "Back
button" on the emulator then RESULT_CANCELED is passed on your behalf, and
finish() is automatically called. Note that you can pass parameters back to the
calling-Activity (via a Bundle) when you cancel the sub-Activity. Here’s some
sample code that does this when a button is pressed:
b2.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Bundle map = new Bundle();
map.putString("de", "nied");
activity.returnErrorToCaller(map);
Log.i(Global.TAG,
"cancel button pressed, calling returnToCaller with bundle" + map.toString());
}
});/** call this to return control to the calling activity - {@link MainActivity#onActivityResult} */
public void returnErrorToCaller(Bundle bundle) {
// sets the result for the calling activity
setResult(RESULT_CANCELED, null, bundle);
// equivalent of 'return'
finish();
}
Note that when you press the "Back button" on the emulator, the
RESULT_CANCELED is sent back to the calling-Activity, but no parameters are
passed, they are null.
Intent and sub-Activity details
When creating an Intent (event) that will be used to create a new
sub-Activity, you can pass the class of this Activity as a parameter to the
Intent. Here’s the code to create an Intent:
Intent i = new Intent(ctx, WidgetActivity.class);
Log.i(Global.TAG, "b4 pressed - about to launch sub-activity");
// the results are called on widgetActivityCallback
ctx.startSubActivity(i, Global.WIDGET_REQ_CODE);
When you create this new sub-Activity, you have to register it in the
AndroidManifest.xml file. Here’s an example:
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
package="com.devlife">
"@drawable/stlogo">
".mainactivity.MainActivity" android:label="@string/app_name">
"android.intent.action.MAIN" />
"android.intent.category.LAUNCHER" />
".subactivity.WidgetActivity" android:label="@string/widgetactivity_name"/>
Here are a few notes on this AndroidManifest.xml file:
The name of the sub-Activity defined by the "android:name" attribute, has to
be the name of the class (in this case
"com.devlife.subactivity.WidgetActivity").
If you’ve specified a root package attribute for the manifest tag, then you
have to precede this class name with a "."; if you don’t have a root package
attribute defined, then just insert the fully qualified name of the class.
In the example above, I have a package attribute defined as:
package="com.devlife". Android uses this name field to do a class lookup when
you create an Intent and point it to a .class object.
Processing the result from the sub-Activity in the calling-Activity (cancel
or ok)
When creating the Intent, to launch this sub-Activity, you have to pass it a
correlation ID, which will be used by the calling-Activity to determine what
code is run in the onActivityResult() method. This correlation ID is called a
request code, and it’s necessary due to the fact that one Activity might spawn
more than one sub-Activity, and if these sub-Activities send a result code (and
some data) back to the calling-Activity, then there has to be a way to sort out
who sent what. This callback mechanism is pretty clunky, and a bit confusing.
The implementation of the onActivityResult() method has to have a switch
statement with case statements for each correlation ID. It makes sense to create
a global static class that holds these correlation IDs, that are implicitly
mapped to each of your sub-Activity classes. Here’s an example of the callback
implementation in the calling-Activity MainActivity:
@Override protected void onActivityResult(int requestCode,
int resultCode,
String strdata,
Bundle bundle)
{
Log.i(Global.TAG, "MainDriver main-activity got result from sub-activity");
if (resultCode == Activity.RESULT_CANCELED) {
Log.i(Global.TAG2,
"WidgetActivity was cancelled or encountered an error. resultcode == result_cancelled");
Log.i(Global.TAG2,
"WidgetActivity was cancelled - data =" + bundle);
}
else
switch (requestCode) {
case Global.WIDGET_REQ_CODE:
widgetActivityCallback(resultCode, bundle);
break;
}
Log.i(Global.TAG, "MainDriver main-activity got result from sub-activity");
}
In addition the correlation ID, a result code is passed from the sub-Activity
to calling-Activity. It’s strange that the sub-Activity has to "kill itself" in
order for this result to pass back to the caller. This exchange is very clunky
as well. Here’s an example of a sub-Activity passing data, result code, and
correlation ID back to the calling-Activity when a button is pressed; please
note that the calling Activity is implicitly determined (not explicitly, since
Android maintains a history stack of Activities in the application’s
process:
b.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Bundle map = new Bundle();
map.putString("monkey", "donkey");
activity.returnToCaller(map);
Log.i(Global.TAG, "button pressed, calling returnToCaller with bundle" + map.toString());
}
});/** call this to return control to the calling activity - {@link MainActivity#onActivityResult} */
public void returnToCaller(Bundle bundle) {
// sets the result for the calling activity
setResult(RESULT_OK, null, bundle);
// equivalent of 'return'
finish();
}
Keep in mind that if the "Back button" is pressed, while the sub-Activity is
visible, then a RESULT_CANCELED resultCode is passed to the onActivityResult()
method of the calling-Activity. Also, the bundle parameter is null in this case.
So there are 3 ways to return control back to the calling-Activity:
Pass RESULT_OK, with or without some return value (passed as a Bundle or
String) - in this example a Bundle is used ({"monkey"="donkey"}).
Pass RESULT_CANCELED, with or without some return value; in this example a
Bundle is passed back to the calling-Activity ({"de"="nied]").
Press the "Back button" which will pass RESULT_CANCELED, with null bundle
and string parameter to the calling-Activity.
Summary - Return to sender… err… caller?
As you have probably inferred by now, the sub-Activity doesn’t need to know
anything about it’s caller. The calling-Activity has to provide a correlationId
and a class name to the Intent that launches the sub-Activity. This
correlationId is then used by the calling-Activity to figure out what to do with
the result (and which sub-Activity produced these results).
In the code example above, you don’t really need to know what the calling
Activity’s class is… since it just fires an Intent to launch the sub-Activity.
When the sub-Activity finishes, the results are sent back to the
calling-Activity implicitly by Android itself. This is why I only showed the
calling-Activity (in my example MainActivity) fire the Intent and process the
callback with results.
Is there a better way? Yes, there is!
Instead of dealing with the clunky requestCode/correlationId, I decided to
create a subclass of Activity that would make launching sub-Activities, and
dealing with their result (OK or CANCELED) trivial. Here’s the class that makes
it all very simple, it’s called SimpleActivity:
/**
* SimpleActivity is a subclass of Activity that makes it trivial to create a sub-Activity, and handle it's
* results (ok or cancel). No need to deal with requestCodes, since this class handles creating correlationIds
* automatically.
*
* @author Nazmul Idris
* @version 1.0
* @since Jul 3, 2008, 12:08:46 PM
*/
public class SimpleActivity extends Activity {
/** holds the map of callbacks */
protected HashMap _callbackMap = new HashMap();
/** use this method to launch the sub-Activity, and provide a functor to handle the result - ok or cancel */
public void launchSubActivity(Class subActivityClass, ResultCallbackIF callback) {
Intent i = new Intent(this, subActivityClass);
Random rand = new Random();
int correlationId = rand.nextInt();
_callbackMap.put(correlationId, callback);
startSubActivity(i, correlationId);
}
/**
* this is the underlying implementation of the onActivityResult method that handles auto generation of
* correlationIds and adding/removing callback functors to handle the result
*/
@Override protected void onActivityResult(int correlationId, int resultCode, String paramStr, Bundle paramMap) {
try {
ResultCallbackIF callback = _callbackMap.get(correlationId);
switch (resultCode) {
case Activity.RESULT_CANCELED:
callback.resultCancel(paramStr, paramMap);
_callbackMap.remove(correlationId);
break;
case Activity.RESULT_OK:
callback.resultOk(paramStr, paramMap);
_callbackMap.remove(correlationId);
break;
default:
Log.e(Global.TAG3, "Couldn't find callback handler for correlationId");
}
}
catch (Exception e) {
Log.e(Global.TAG3, "Problem processing result from sub-activity", e);
}
}
/**
* ResultCallbackIF is a simple interface that you have to implement to handle results - ok or cancel from a
* sub-Activity.
*
* @author Nazmul Idris
* @version 1.0
* @since Jul 3, 2008, 12:11:31 PM
*/
public static interface ResultCallbackIF {
public void resultOk(String resultString, Bundle resultMap);
public void resultCancel(String resultString, Bundle resultMap);
}//end interface ResultCallbackIF
}//end class SimpleActivity
Here’s some sample code to show you how clean it is to use SimpleActivity,
instead of the default stuff; this code (it’s a subclass of SimpleActivity)
spawns a new sub-Activity in response to a button press, and the callback to
handle the results is passed in-line, with the call to spawn the
sub-Activity:
// setup b2 to spawn a subactivity - TableActivity
b2.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Log.i(Global.TAG3, "b2 pressed - about to launch sub-activity");
ctx.launchSubActivity(TableActivity.class,
new SimpleActivity.ResultCallbackIF() {
public void resultOk(String str, Bundle result) {
Log.i(Global.TAG3, "subactivity completed successfully, result=" + result);
}
public void resultCancel(String str, Bundle result) {
Log.i(Global.TAG3, "subactivity was cancelled, result=" + result);
}
});
Log.i(Global.TAG3, "b2 pressed - sucessfully launched sub-activity (startSubActivity called)");
}
});
No need to mess with correlationIds, or overriding onActivityResult in the
calling-Activity. Simple, elegant, and it works
![]()
.
Feedback and comments
To share your thoughts on this tutorial
click here
.
Further reading in related categories
Android
Android WebView
(WebKit)
This article shows you the limitations and capabilities of the WebView
component. You will see how to download files from the network and use them in
HTML, as well as assets loaded in the APK file.
Android Service creation
and consumption
This tutorial will show you how to create a simple service, that does not
use IPC (inter process communication). Services are great for running long
running tasks and business logic, outside an Activity, which is tied to the user
interface. For example, if you have a background task that has to download data
periodically, then you should put that task in a Service. You can explicitly
start a service and stop it as well. With IPC you can connect to a running
service and call methods on it, however, in this example, I won't be using any
IPC; instead all data transfer will happen via a shared object and a listener.
Android Animation
Framework
This tutorial is an introduction to the built in animation frameworks that
are part of the Android UI library. Without writing any animation/drawing code,
you can do 2 types of animations - layout transitions that affect ViewGroups,
and sequences inside a View. You can also do frame by frame animation, but this
tutorial will not cover that. The basics covered here affect layout transitions,
and animation of a View itself, using tweening animation, which includes each of
the following effects (or any combination) - Alpha, Rotate, Scale, and
Translate.
Android ListView and
custom adapter
This tutorial will show you how to use ListView to display selectable lists
of non trivial data, using complex cell renderers. The ListView is a selectable
list. You can attach a variety of data models to it and load different display
layouts (cell renderers). You can create your own model and cell renderer. This
model-view combo is called an Adapter. In this tutorial, I will show you how to
extend create your own Adapter from scratch, and create your own cell renderers
from scratch as well.
Android LinearLayout
This tutorial shows you how to use the LinearLayout container (using Java
code, not XML markup), which is the simplest layout mechanism available on
Android. If you're familiar with Swing's BoxLayout then you will have a good
idea of what this container has to offer. Linear layouts are really simple… you
can add components horizontally or vertically to a ‘bag’ or ‘box’.
Android UI Themes
This tutorial will show you how to use Android's theme-ing capabilities. You
can set background color, image, etc. on widgets, dialogs, and activities.
Android TableLayout
This tutorial will show you how to use the TableLayout container, which is
like an HTML table. The UI layout code is done in Java, not XML. A class
(LayoutUtils) is provided to make it easier to attach layout params to View
objects.
Android Option and
Context menu
This tutorial will show you how to create options menu (hooks into the MENU
button) and context menu (press and hold a component).
Android XML View
inflation
This tutorial will show you how to instantiate or inflate a View from XML;
this is useful for components that don't provide a Java API to tweak with
certain style attributes. The Button class is used as an example; you can only
get certain styles to show up via XML that aren't available via the Java API.
Android SDK and tools -
Getting started
This tutorial has helpful pointers for developers who are just getting
started with Android. In addition to Google’s documentation on the SDK itself,
there are lots of tools that come with the SDK and others that you can download
elsewhere that make it a little bit easier to work with Android.
How to build a
service-enabled Android app - Part 3/3 Multithreading
I've written 3 tutorials to show you how to create a service enabled Android
application that performs all of it's network I/O in a background thread (not
the UI thread). These tutorials are split into three parts. This tutorial shows
you how to use background threads to perform long running network IO operations,
so that the main UI thread is not locked up.
How to build a
service-enabled Android App - Part 2/3 Networking
I've written 3 tutorials to show you how to create a service enabled Android
application that performs all of it's network I/O in a background thread (not
the UI thread). These tutorials are split into three parts. This one shows you
how to use Apache HTTP Client to connect to services over HTTP or HTTPS and
exchange serialized Java objects with services.
How to build a
service-enabled Android app - Part 1/3 UI
I've written 3 tutorials to show you how to create a service enabled Android
application that performs all of it's network I/O in a background thread (not
the UI thread). This tutorial shows you how to build a simple UI without using
XML, by writing Java code to layout the UI.
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/85805/showart_1667037.html |
|