1.0 Activities And Intents: A Simplified Model
The Android Application/Activity model is complex bordering on baroque. The following is not intended to be definitive, just sufficient to make it possible to explain how Intents are used in the context of Activities.
-
An Activity is an Application Component with an associated UI which is used to interact with the User.
-
An Application can comprise one or more Activities.
-
An Application can use one or more Activities to interact with the User but only one at a time.
-
An Application can use both Activities defined by itself and those defined by other Applications.
-
The Activities being used by an Application are usually grouped together in a task
-
The set of Activities in a task are represented as a stack.
-
The first Activity in the task is at the bottom of the stack.
-
The current Activity is at the top of the stack.
-
Activities start in response to Intents.
-
The Intents which an Activity will start in response to are specified using Intent Filters.
-
An Activity which is not started by another Activity will become the first Activity in a new task.
-
An Activity which is started by another Activity is by default in the same task as the Activity which started it.
-
When it finishes an Activity may return an Intent to the Activity which started it.
2.0 Associating Activities And Intents
The set of Intents an Activity can handle are specified by declaring intent-filter
elements as children of the activity
element used to declare the Activity itself in the Application’s manifest (the AndroidManifest.xml file).
When an Intent Filter is being declared in the context of an Activity the priority attribute cannot have a value greater than zero unless the Activity being declared is part of a System Application.
3.0 Starting Activities
3.1 Starting Activities And Intent Resolution
An Activity is started by specifying an Intent. Which Activity, if any, gets started for a given Intent is determined by the process of Intent resolution.
If an Intent explicitly specifies a component then the Intent resolves to that Application Component irrespective of whether it is actually an Activity or even whether it exists at all.
If an Intent does not specify a component then a search is made for all those Activities which satisfy two criteria
-
one of their associated Intent Filters matches, as defined by the IntentFilter.match() method, the given Intent, and
-
one of their associated Intent Filters specifies the
android.intent.category.DEFAULT
category
If the Intent specifies a package then the search is confined to the Activities in that Application package.
If more than one Activity is found then the Activity with the highest priority as defined by the associated Intent Filter(s) is chosen.
If all the Activities found have the same priority then the System will present the User with a choice and let them decide.
The methods used by the System to perform Intent resolution, or at least their functional equivalents, are available to Applications.
The Intent
method
public ComponentName resolveActivity(PackageManager pm)
will return the name of the Component to which the given Intent resolves, even if it is not an Activity or does not exist. In the case where the Intent resolves to multiple Activities with the same priority the name of the Activity which will be used to ask the User to choose will be returned.
The Intent
method
public ActivityInfo resolveActivityInfo(PackageManager pm, int flags)
behaves in the same way except that it will return null
if the Intent resolves to a named Component which does not exist or is not an Activity.
These are both wrappers around a call to an implementation of the android.content.pm.PackageManager
public abstract ResolveInfo resolveActivity (Intent intent, int flags)
method, with the flags
argument either being, or including, the value specified by the class constant
PackageManager.MATCH_DEFAULT_ONLY
The PackageManager
class also defines the methid
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags)
An implementation of this method will return the set of all the Activities which are capable of being started in response to the given Intent.
3.2 Starting Activities And Intent Flags
The values which can be used as Intent flags are defined by the System as Intent
class constants. The majority of them, those with the prefix FLAG_ACTIVITY_
, are intended for use when an Intent is being used to start an Activity.
Some of the more interesting ones in this context are those that can be used for task management and Activity selection
3.2.1 FLAG_ACTIVITY_CLEAR_TOP
If the Intent resolves to an Activity in the current task the Activities above it on the stack are destroyed so that it is at the top of the stack, and it is re-used.
3.2.1.1 Example
There are four Activities in a task that have been started in the order One, Two, Three, Four.
Four starts an Activity using an Intent with the FLAG_ACTIVITY_CLEAR_TOP
which resolves to Two.
3.2.2 FLAG_ACTIVITY_NEW_TASK
If the Intent resolves to an Activity which needs to be started it will be started in a new task.
3.2.3 FLAG_ACTIVITY_PREVIOUS_IS_TOP
For the purposes of deciding whether to re-use the top Activity the previous rather than the current Activity is considered to be top.[1]
3.2.4 FLAG_ACTIVITY_REORDER_TO_FRONT
If the Intent resolves to an Activity in the current task that Activity is moved to the top of the stack and re-used.
To be consistent with some of the other flags FLAG_ACTIVITY_REORDER_TO_TOP
might have been a better name.
3.2.4.1 Example
There are four Activities in a task that have been started in the order One, Two, Three, Four.
Four starts an Activity using an Intent with the FLAG_ACTIVITY_REORDER_TO_FRONT
which resolves to Two.
3.2.5 FLAG_ACTIVITY_SINGLE_TOP
If the Intent resolves to the Activity at the top of the current task stack it is re-used.
Note that in this context the top Activity is not necessarily the Activity at the top of the stack. See FLAG_ACTIVITY_PREVIOUS_IS_TOP
.
3.3 Starting An Activity
An Activity can be started using an implementation of the android.content.Context
public abstract void startActivity(Intent intent)
method.
If the given Intent cannot be resolved to an Activity or what it resolves to is not an Activity, or does not exist, an instance of the class
android.content.ActivityNotFoundException
will be thrown.
If the Application invoking the method has not been granted the permission necessary to start the Activity a SecurityException will be thrown.
If the method is not being invoked from an Activity the Intent must have the FLAG_ACTIVITY_NEW_TASK
flag set.
3.3.1 The onNewIntent() Method
The effect of the startActivity()
method is not always to start a new Activity.
In some cases an existing Activity will be re-used and its
protected void onNewIntent(Intent intent)
method will be called with the starting Intent being passed as the intent
argument.
For example, if the Activity SelfStart is declared as follows
<activity
android:name = "SelfStart"
android:label = "SelfStart">
<intent-filter>
<action
android:name = "xper.activity.ACTIVITY_SELF_START_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
and its onNewIntent()
method is defined to be
protected void onNewIntent(Intent intent)
{
System.out.println("SelfStart.onNewIntent()");
System.out.println("\t" + intent);
System.out.println("SelfStart.onNewIntent(): done");
}
If, when it is the current Activity, it executes
startActivity(
new Intent(
"xper.activity.ACTIVITY_SELF_START_INTENT").
setFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP));
then a new instance of SelfStart is not created and the onNewIntent()
method prints
SelfStart.onNewIntent()
Intent { act=xper.activity.ACTIVITY_SELF_START_INTENT flg=0x20000000 cmp=xper.example.selfstart/.SelfStart }
SelfStart.onNewIntent(): done
3.4 Starting An Activity And Getting A Result
The android.app.Activity
method
public void startActivityForResult(Intent intent, int requestCode)
will start an Activity in the same way as the startActivity()
method above.
In addition if the requestCode
argument is greater than or equal to zero then when the Activity that was started completes the caller’s
protected void onActivityResult(int requestCode, int resultCode, Intent data)
method will be invoked.
The value of the requestCode
argument will be the same as that passed to the orignal call to the startActivityForResult()
method, so it can be used to identify the Activity from which the result originated.
In some cases the behaviour of the method is subtly different depending upon whether a result is requested or not.
For example, if the SelfStart Activity above executes the following
startActivityForResult(
new Intent(
"xper.activity.ACTIVITY_SELF_START_INTENT").
setFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP),
-1);
the behaviour is the same as before.
However, if it executes
startActivityForResult(
new Intent(
"xper.activity.ACTIVITY_SELF_START_INTENT").
setFlags(
Intent.FLAG_ACTIVITY_SINGLE_TOP),
3);
then the Intent.FLAG_ACTIVITY_SINGLE_TOP
flag is ignored and a second instance is created.
3.4.1 Returning A Result
An Activity can return a result code to the Activity that started it by calling the method
public final void setResult(int resultCode)
before calling the finish()
method.
It can return a result code and an Intent by calling the method
public final void setResult(int resultCode, Intent data)
instead.
For example, assume two Activities Foo and Bar are part of the same Application.
Foo defines the following onActivityResult()
method
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
System.out.println("Foo.onActivityResult(...)");
System.out.println("\trequestCode == " + requestCode);
System.out.println("\tresultCode == " + resultCode) ;
System.out.println("\tdata == " + data);
System.out.println("ActivityFoo.onActivityResult(...) done");
}
Bar is declared in the Application’s manifest as follows
<activity
android:name = "Bar">
<intent-filter>
<action
android:name = "xper.activity.ACTIVITY_BAR_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Foo starts Bar
startActivityForResult(
new Intent(
"xper.activity.ACTIVITY_BAR_INTENT"),
1);
At some point Bar returns a result
setResult(117, new Intent("xper.activity.ACTIVITY_BAR_RESULT_INTENT"));
finish();
and Foo’s onActivityResult()
method prints
Foo.onActivityResult(...)
requestCode == 1
resultCode == 117
data == Intent { act=xper.activity.ACTIVITY_BAR_RESULT_INTENT }
ActivityFoo.onActivityResult(...) done
A result can be returned from an Activity defined in one Application to an Activity defined in another as long as they are in the same task.
If the effect of a call to startActivityForResult()
is to start the Activity in a new task then the callers’s Activity.onActivityResult()
method will be invoked immediately with a resultCode
argument of zero and data
argument of null
.
If an Activity started using the startActivityForResult()
method does not call either of the above methods before it finishes then the Activity.onActivityResult()
method will be invoked with a resultCode
argument of zero and data
argument of null
.
3.3.2 “Forwarding” A Result
If Activity One starts Activity Two using the startActivityForResult()
method and Activity Two then starts Activity Three, Activity Two can specify that the result from Activity Three be returned to Activity One rather to itself. This is done by setting the flag defined by the Intent
class constant FLAG_ACTIVITY_FORWARD_RESULT
in the Intent used to start the Activity.
For example, assume the Activity Baz is also part of the Application in the example above.
Baz is declared in the Application’s manifest as follows
<activity
android:name = "Baz">
<intent-filter>
<action
android:name = "xper.activity.ACTIVITY_BAZ_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Foo starts Bar as before
startActivityForResult(
new Intent(
"xper.activity.ACTIVITY_BAR_INTENT"),
1);
At some point Bar then starts Baz
startActivity(
new Intent(
"xper.activity.ACTIVITY_BAZ_INTENT").
setFlags(
Intent.FLAG_ACTIVITY_FORWARD_RESULT));
At some point Baz returns a result
setResult(211, new Intent("xper.activity.ACTIVITY_BAZ_RESULT_INTENT"));
finish();
At some point Bar returns a result as before
setResult(117, new Intent("xper.activity.ACTIVITY_BAR_RESULT_INTENT"));
finish();
and Foo’s onActivityResult()
method then prints
Foo.onActivityResult(...)
requestCode == 1
resultCode == 211
data == Intent { act=xper.activity.ACTIVITY_BAZ_RESULT_INTENT }
ActivityFoo.onActivityResult(...) done
Note that Bar starts Baz using the startActivity()
method. It is not possible to use the FLAG_ACTIVITY_FORWARD_RESULT
flag in conjunction with the startActivityForResult()
. Doing so will result in an instance of
android.util.AndroidRuntimeException
being thrown.
Note also that the result from Baz is not returned to Foo until Bar itself has finished, and that the resultCode
and data
returned by Baz override those returned by Bar but the requestCode
is unchanged.
4.0 Starting An Activity: Variations On A Theme
The android.app.Activity
class provides a couple of more specialized ways to start Activities.
4.1 Starting An Activity Only If Needed
The method
public boolean startActivityIfNeeded(Intent intent, int requestCode)
is analagous to the startActivityForResult()
method.
If the requestCode
argument is greater than or equal to zero it works in the same way. The difference between them is that this method will only start a new Activity if it is needed.
A new Activity is not needed if the Intent specified by the intent
argument resolves to the caller of the method, that is to the currently running Activity, and there may only be one instance of that Activity. In this case a new Activity is not started.
In any other circumstances a new Activity will be started.
The method returns true
if a new Activity is started, and false
otherwise, in which case the caller is expected to handle the Intent directly.
For example, assume an Application declares the following Activities
<activity
android:name = ".Humber"
android:label = "@string/app_name">
<intent-filter>
<action
android:name = "android.intent.action.MAIN" />
<category
android:name = "android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name = "Thames"
android:launchMode = "singleTop">
<intent-filter>
<action
android:name = "xper.activity.ACTIVITY_THAMES_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name = "Dover">
<intent-filter>
<action
android:name = "xper.activity.ACTIVITY_DOVER_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
If Humber executes
startActivityIfNeeded(
new Intent(
"xper.activity.ACTIVITY_THAMES_INTENT"),
-1);
the Activity Thames is started and the method returns true
.
If Thames executes
startActivityIfNeeded(
new Intent(
"xper.activity.ACTIVITY_THAMES_INTENT"),
-1);
it is not necessary to start a new instance of Thames and the method returns false
.
If Thames executes
startActivityIfNeeded(
new Intent(
"xper.activity.ACTIVITY_DOVER_INTENT"),
-1);
the Activity Dover is started and the method returns true
.
If Dover executes
startActivityIfNeeded(
new Intent(
"xper.activity.ACTIVITY_DOVER_INTENT"),
-1);
a second instance of the Dover Activity is started, because by default there may be multiple instances of it, and the method returns true
.
Passing a requestCode greater than or equal to zero results in behaviour identical to startActivityForResult() with the same arguments.
For example, if Thames executes
startActivityIfNeeded(
new Intent(
"xper.activity.ACTIVITY_THAMES_INTENT"),
2);
then a second instance of the Thames Activity is started, and the method returns true.
Given that in these circumstances it is apparently always necessary to start an Activity it is not clear why the method needs to take a second argument at all.
If an Activity needs to be started and one cannot be found an ActivityNotFoundException will be thrown.
4.2 Starting The Next Matching Method
If the method
public boolean startNextMatchingActivity(Intent intent)
is called with the Intent used to start the current Activity, then the method will start the next Activity, if any, capable of being started in response to that Intent.
The Intent passed as the intent
argument does not have to be identical to the one that started the current Activity, it can differ in the extras that it specifies, which means it is possible to pass data between Activities started in this mode.
The method returns true
if an Activity has been started, in which case the current Activity should then call the finish()
method, otherwise false
.
In this context next means the Activity, if any, after the current Activity, in the list of Activities constructed during Intent resolution.
For example, if there are three Activities each declared in a different Application as follows
<activity
android:name = "Plymouth"
android:label = "Plymouth">
<intent-filter
android:priority = "-2">
<action
android:name = "xper.activity.CHANNEL_ACTIVITY_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
...
<activity
android:name = "Portland"
android:label = "Portland">
<intent-filter
android:priority = "-1">
<action
android:name = "xper.activity.CHANNEL_ACTIVITY_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
...
<activity
android:name = "Wight"
android:label = "Wight">
<intent-filter>
<action
android:name = "xper.activity.CHANNEL_ACTIVITY_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
then, when used to start an Activity, an Intent with the action xper.activity.CHANNEL_ACTIVITY_INTENT
will be resolved to these Activities in the order
-
Wight
-
Portland
-
Plymouth
If some other Activity executes
startActivity(
new Intent(
"xper.activity.CHANNEL_ACTIVITY_INTENT"))
then Wight will be started.
If Wight executes
startNextMatchingActivity(
new Intent(
"xper.activity.CHANNEL_ACTIVITY_INTENT"))
the Portland will be started, and the method will return true
.
If Portland executes
startNextMatchingActivity(
new Intent(
"xper.activity.CHANNEL_ACTIVITY_INTENT"))
the Plymouth will be started, and the method will return true
.
If Plymouth executes
startNextMatchingActivity(
new Intent(
"xper.activity.CHANNEL_ACTIVITY_INTENT"))
the method will return false
as there are no other Activities to start.
The behaviour is the same regardless of which Activity is started first.
If Plymouth is started first and it executes
startNextMatchingActivity(
new Intent(
"xper.activity.CHANNEL_ACTIVITY_INTENT"))
the method will still return false
. It will not wrap around to Wight.
5.0 Activity Implementation And Intent Access
An Activity can access the Intent which was used to start it by calling the method
public Intent getIntent()
By default the Intent returned does not reflect any subsequent calls to the Activity’s onNewIntent()
method.
However, an Activity can set the Intent which will be returned by calling the
public void setIntent(Intent newIntent)
method.
6.0 Activity Intent Filters And Intent Categories
The System pre-defines a number of Intent categories. When these are specified in Intent Filters associated with Activities the System treats some of them as declarations which apply to those Activities or to the Applications that declare those Activities.
For example, if the category defined by the Intent()
class constant
CATEGORY_HOME
with the value
"android.intent.category.HOME"
is specified, then the Application containing the declaration is treated by the System as the home Application and is started automatically when the System starts up. If there are multiple such Applications then the user may be offered the choice of which one to start as the home Application.
If the the category defined by the Intent()
class constant
CATEGORY_LAUNCHER
with the value
"android.intent.category.LAUNCHER"
is specified, then the System will make it possible for the User to launch the Application containing the declaration via the UI in some way.
There is no way of knowing whether the overloading of Intent categories in this context was originally part of the design or whether its just something that somebody hit upon afterwards.
One slight problem with it is that it is not possible to know simply by looking at an Application’s manifest whether the Application or one or more of its Activities might have some special status or mode of behaviour, unless you are familiar with all the System pre-defined Intent categories and their semantics.
7.0 Starting A Specific Activity
As mentioned above if an Intent specifies a Component explicitly then the Intent resolves to that Component regardless of anything else specified by the Intent.
This makes it possible to use an Intent to start any Activity given only its ComponentName as long as the Application doing so has the necessary permission.
For example if the Forties Activity is declared in the xper.example.activity.forties
Application package as follows
<activity
android:name = "Forties"
android:label = "Forties">
<intent-filter>
<action
android:name = "xper.activity.ACTIVITY_FORTIES_INTENT"/>
<category
android:name = "android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
then it can be started as follows
startActivity(
new Intent(
"xper.activity.ACTIVITY_CROMARTY_INTENT").
setClassName(
"xper.example.activity.forties",
"xper.example.activity.forties.Forties"));
despite the fact that the xper.activity.ACTIVITY_CROMARTY_INTENT
action does not appear in its Intent Filter.
8.0 “Anonymous” Activities
It is possible to specify an Activity without any Intent Filters, for example,
<activity android:name="AnonymousActivity"/>
This Activity cannot be accessed from outside the Application which declared it, but from within that Application it can still be started by specifying it explicitly as described above, for example,
startActivity(new Intent(this, AnonymousActivity.class));
If an Activity is internal to an Application and only ever needs to be started explicitly then this is actually useful functionality especially since it avoids the expense of full on Intent resolution.
Perhaps less usefully it is also possible to make an Activity without any Intent Filters accessible outside the Application which declared it by explicitly exporting it. For example,
<activity
android:name = "PublicAnonymousActivity"
android:exported = "true"
/>
Notes
[1] It is not clear in what circumstances the FLAG_ACTIVITY_PREVIOUS_IS_TOP
Intent flag actually works.
The flag is handled in the com.android.server.am.ActivityStack
method
final int startActivityUncheckedLocked(
ActivityRecord r,
ActivityRecord sourceRecord,
Uri[] grantedUriPermissions,
int grantedMode,
boolean onlyIfNeeded,
boolean doResume)
(lines 2039-2385).
The ActivityStack implementation can be found in the file
$(ANDROID_SRC)/frameworks/base/services/java/com/android/server/am/ActivityStack.java
At line 2060 the notTop
variable is initialized as follows (code slightly reformatted)
ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;
At line 2259 the the top
variable is initialized as follows
ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
This is immediately preceeded by the following comment(s) (lines 2256-2258)
// If the activity being launched is the same as the one currently
// at the top, then we need to check if it should only be launched
// once.
The topRunningNonDelayedActivityLocked()
method is defined as follows (lines 332-342)
final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
int i = mHistory.size()-1;
while (i >= 0) {
ActivityRecord r = (ActivityRecord)mHistory.get(i);
if (!r.finishing && !r.delayedResume && r != notTop) {
return r;
}
i--;
}
return null;
}
so the top Activity as returned by this method is the first ActivityRecord, starting from the back of the list of ActivityRecords held in the mHistory
instance variable, which is not finishing, is not in the delayedResume state, and is not the ActivityRecord passed in as the notTop
argument.
If the notTop
argument is null
the expression
r != notTop
is necessarily true.
If the notTop
argument is not null
then the expression
r != notTop
may be false, in which case the current ActivityRecord r
will be skipped.
Given the use of the !=
operator for the expression
r != notTop
to be false, r
and notTop
have to be the same ActivityRecord object, and hence the ActivityRecord passed as the notTop
argument must be in list of ActivityRecords held in mHistory
.
This is necessarily true regardless of what the ActivityRecord passed as the notTop
argument represents or what mHistory
represents.
When invoked from the startActivityUncheckedLocked()
topRunningNonDelayedActivityLocked()
is passed the notTop
variable.
When the FLAG_ACTIVITY_PREVIOUS_IS_TOP
is set the notTop
variable is the ActivityRecord passed as the argument r
.
The method startActivityUncheckedLocked()
is invoked by the
final int startActivityLocked(
IApplicationThread caller,
Intent intent,
String resolvedType,
Uri[] grantedUriPermissions,
int grantedMode,
ActivityInfo aInfo,
IBinder resultTo,
String resultWho,
int requestCode,
int callingPid,
int callingUid,
boolean onlyIfNeeded,
boolean componentSpecified)
method [sic] (lines 1877-2037).
The ActivityRecord r
is constructed (line 2002) but it is not added to the list of ActivityRecords held in mHistory
before being passed to startActivityUncheckedLocked()
(line 2035).
Nor, in the case when FLAG_ACTIVITY_PREVIOUS_IS_TOP
is set, does it appear to have been added before being passed to topRunningNonDelayedActivityLocked()
in the guise of the notTop
variable, which implies that passing it cannot have any effect at all on the behaviour of that method.
This may explain why it does not seem to be possible to devise an example where setting the FLAG_ACTIVITY_PREVIOUS_IS_TOP
flag has any effect.
Copyright (c) 2011 By Simon Lewis. All Rights Reserved.