一个Android应用一般都有若干个activities。每个activity展示一个用户界面,用于执行特定的用户任务(例如浏览地图或者拍照)。为了让用户从一个activity跳到另一个activity。你的app必须使用一个 Intent
定义你的app想要做某事的“意图”。当你通过调用如 startActivity()
这样的方法传递一个Intent给系统时,系统使用Intent标识和开启合适的应用组件。使用intent甚至可以让你启动其他的app里的activity。
为了启动特定的组件(例如一个特定的Activity实例)intent可以是显示的,也可以是隐式的,为了启动任何可以处理该意图action(例如拍照)的组件。
本文告诉你如何使用Intent执行一些和其他的app交互的基本操作,例如启动另一个应用,从该app接受返回的结果,和响应来自于其他app的intent。
Lessons
Sending the User to Another App
如何产生隐式意图,启动能执行该action的其他的app
Getting a Result from an Activity
如何启动另一个activity,并从该activity获取返回结果
Allowing Other Apps to Start Your Activity
如何定义一个你的app接受的隐式意图的意图过滤器使得你的app里的activity开发给其他应用使用
Sending the User to Another App
Android最重要的特性之一就是基于“action”用户能从一个app跳到另一个app。例如,你的app有一个想要显示在地图的地址,你不必在你的app里创建一个显示地图的activity。你可以产生一个请求展示该地址的意图。然后,Android系统会启动一个能将该地址显示到地图上的app。
如Building Your First App里所解释的,你必须使用intent在你的app里的activity间切换。你一般通过显示意图(定义了你想要启动的组件的确切的类名)实现同一个app里的activity间的切换。然而,当你想要其他的app处理你的app的intent时,例如浏览一个地图,你必须使用隐式意图。
本文讲述如何产生特定操作的隐式意图,以及如何使用隐式意图启动其他的app里的activity来处理你的隐式意图。
Build an implicit Intent
隐式意图并不需要声明启动的组件类名,而是声明一个执行的动作(action)。该action描述了你想做的事情,例如,浏览、编辑、发送或者获取一些数据。intent也可以通过action来附加要传递的数据,例如你想要访问的地址、或者你想要发送的email信息。根据你想要创建的intent,数据可能是一个Uri,或者其他的数据类型,也可能不包含任何数据。
如果你的数据是是一个Uri,那么你可以简单的调用Intent()构造方法来定义action和数据。
例如,下面是如何定义一个intent来打电话,并用Uri来表示电话号码。
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
当你的应用调用startActivity()来启动该Intent时,电话应用程序就会向指定的电话号码打电话。
以下是一些其他的intent,和action和Uri映射对。
- 浏览地图
// Map point based on address
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
// Or map point based on latitude/longitude
// Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
- 访问一个Web网页
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
其它的隐式意图需要“附加的”的其他的数据,比如字符串,你可以使用不同的putExtra()方法来添加一个或者多个“附加”数据。
默认地,系统通过添加到intent里的Uri数据类型累决定适合的MIME类型,如果你的Intent里没有Uri,你应该使用setType()方法来明确intent里的数据类型。更进一步设置MIME类型明确了哪一类的activity解释和处理该意图。
下面是一些intent的例子,通过添加额外的数据来明确期望的action:
- 发送带附件的邮件:
Intent emailIntent = new Intent(Intent.ACTION_SEND); // The intent does not have a URI, so declare the "text/plain" MIME type emailIntent.setType(HTTP.PLAIN_TEXT_TYPE); emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"jon@example.com"}); // recipients emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject"); emailIntent.putExtra(Intent.EXTRA_TEXT, "Email message text"); emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment")); // You can also attach multiple items by passing an ArrayList of Uris
- 产生日历事件:
Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI); Calendar beginTime = Calendar.getInstance().set(2012, 0, 19, 7, 30); Calendar endTime = Calendar.getInstance().set(2012, 0, 19, 10, 30); calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis()); calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis()); calendarIntent.putExtra(Events.TITLE, "Ninja class"); calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo");
注:仅仅API14或者更高的API支持日历时间的intent。
注:把你的intent定义的尽可能明确是什么重要的。例如,如果你想要展示一个image,你可以使用ACTION_VIEW,同时你也应该指定MIME类型为 image/*
,这防止能浏览其他类型数据的app(例如地图应用)被该intent出发和调起。
Verify There is an App to Receive the Intent
虽然Anroid平台确保特定的intent会被内建app中(例如Phone、Email或者日历应用)的一个处理,你也应该总是在使用前确认有app能处理你的意图。
注意:如果你的发起一个intent,但android设备上没有可以处理你的intent的app,你的app将crash。
为了确保有一个activity能响应的意图,调用queryIntentActivities()方法获得一个能处理你的intent的activity列表。如果返回的list非空,你能安全地使用该intent。例如:
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isIntentSafe = activities.size() > 0;
如果isIntentSafe是true,那么至少一个app将响应该intent,如果为false,那么没有任何app处理该intent。
注:你应该在你的activity第一次启动时就执行该检查。以防you need to disable the feature that uses the intent before the user attempts to use it。如果你知道一个特点的app能处理该intent,你也能提供一个链接让用户去下载该app(参见如何 link to your product on Google Play)。
Start an Activity with the Intent
一旦你已产生了你的Intent并设置了额外的信息,你能调用startActivity()将该intent发送给系统。如果系统发现不止一个activity能处理该intent,系统将展示一个选择对话框来供用户选择一个app来处理,如图1.如果有仅仅一个activity能处理,系统立即的启动它。
如下是一个复杂点的例子,显示了如何产生一个intent来浏览地图,确保一个存在一个app处理该intent,然后开启它:
// Build the intent
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
// Verify it resolves
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
boolean isIntentSafe = activities.size() > 0;
// Start an activity if it's safe
if (isIntentSafe) {
startActivity(mapIntent);
}
当你嗲用startActivity()方法来传递Intent,而且有许多应用匹配该Intent时,用户可以选择一个默认程序(通过选中如图1的对话框底的一个checkbox)来处理该意图。如果用户每次都想用同一个app来执行某个action时这是十分好的用户体验。
然而,如果一个action能被多个app执行,而用户每次都希望不同的app来执行——例如一个“share”操作用户可能需要一个分享item分享到不同的app——这时你需要明晰的展示一个选择对话框,如图2。该选择对话框迫使用户每次选择一个app来执行action(用户不能选择一个默认app来处理intent)。
为了显示chooser,使用createChooser()来产生Intent,作为参数传递该Intent给startActivity()。例如:
Intent intent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);
// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
如上,把Intent传入createChooser()方法,从而显示了一个应用程序列表对话框,并将提供的文字作为对话框标题。
Getting a Result from an Activity
启动另一个activity不止一种方式。你也能启动一个Activity并接收一个返回结果。为了接收一个结果调用 startActivityForResult()
(而不是startActivity())。
例如,你的app能启动一个照相机应用,接收一个拍摄的照片作为返回结果。或者,你可能要启动一个联系人应用来选择一个联系人,你将接收联系人详情作为返回结果。
当然,响应的activity必须被设计为能返回一个结果。当activity能返回结果时,它通过另一个intent对象来发送返回结果。你的activity能在onActivityResult()回调方法里接收到该结果。
注:当你调用startActivityForResult()
时你能使用显示或者隐式意图。当使用你自己的activity接收结果数据时,你应该使用显式意图确保你的接收到期望的结果。
Start the Activity
当启动有返回结果的activity时,所用的intent并没有什么特别的。但是,你需要传递一个额外的整型参数给startActivityForResult()方法。
整型参数是一个请求码,用于标识你的请求。当你接收到结果意图时,回调提供相同的请求码以便你的app能合理的区分属于你的结果并确定如何处理该结果。
例如,如下显示一个如何开启选择联系人的activity:
static final int PICK_CONTACT_REQUEST = 1; // The request code
...
private void pickContact() {
Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
pickContactIntent.setType(Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
}
Receive the Result
当后续的Activity执行完成并返回结果时,系统将调用前面的Activity的onActivityResult()方法。该方法包含三个参数:
- 你传递给
startActivityForResult()
的请求码。 - 第二个Activity指定的结果码。如果操作成功结果码将是RESULT_OK,如果由于某些原因用户退出或者操 作失败,其值是RESULT_CANCELED。
- 携带返回结果数据的Intent。
例如,如下显示了如何处理选择联系人intent的返回结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == PICK_CONTACT_REQUEST) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
// The user picked a contact.
// The Intent's data Uri identifies which contact was selected.
// Do something with the contact here (bigger example below)
}
}
}
该例子里,由Android的Contract或者People应用返回的结果Intent提供了一个内容Uri,该Uri标识了用户选择的联系人。
为了成功里处理结果,你必须理解结果意图的格式是什么。当返回的结果的activity是你的应用里自己写的activity时,很容易知道是什么格式。然而,Android平台里的app提供了属于自己的APIs,它们有自己特定结果数据。例如,People应用(在一些旧版本的Android平台上是Contact应用)始终返回标识选定联系人的内容Uri,相机应用返回一个Bitmap,该Bitmap放在附加的“data”里(参见类 Capturing Photos)。
Bouns:Read the contact data
上面的代码显示了如何从联系人应用中获取一个返回结果,但并没有涉及从返回结果中获取联系人的获取细节,因为这需要一些有关 content providers的更进一步的知识。然而,如果你是好奇的,下面是如何从返回的联系人结果数据中获取电话号码的代码:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request it is that we're responding to
if (requestCode == PICK_CONTACT_REQUEST) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
// Get the URI that points to the selected contact
Uri contactUri = data.getData();
// We only need the NUMBER column, because there will be only one row in the result
String[] projection = {Phone.NUMBER};
// Perform the query on the contact to get the NUMBER column
// We don't need a selection or sort order (there's only one result for the given URI)
// CAUTION: The query() method should be called from a separate thread to avoid blocking
// your app's UI thread. (For simplicity of the sample, this code doesn't do that.)
// Consider using CursorLoader to perform the query.
Cursor cursor = getContentResolver()
.query(contactUri, projection, null, null, null);
cursor.moveToFirst();
// Retrieve the phone number from the NUMBER column
int column = cursor.getColumnIndex(Phone.NUMBER);
String number = cursor.getString(column);
// Do something with the phone number...
}
}
}
注:Android2.3(API 9)之前,在
Contacts Provider
(如上所示)上执行查询时,要求你的app声明
READ_CONTACTS
权限(参见
Security and Permissions)。然而,从Android2.3开始,Contract/People应用给你的app提供一个从Contract Provider读取信息的临时权限。但这个临时权限仅适用于所请求的特定联系人,除非你声明READ_CONTACTS权限,否则不能查询一个在那个intent Uri定义之外的联系人信息。
Allowing Other Apps to Start Your Activity
intent-filter>
元素。
startActivity()
or
startActivityForResult()
时,系统查找哪个(或者哪些activity)能响应该意图。
ACTION_SEND
or
ACTION_VIEW.
text/plain
or
image/jpeg。
CATEGORY_DEFAULT
类别
<activity android:name="ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
每个传入的意图指定仅仅一个Action和一个数据类型,当然你也可以在每个<intent-filter>里声明多个
<action>
,
<category>
, and
<data>
实例。
ACTION_SENDTO
也处理
ACTION_SEND
意图的文本和图片,你必须为这两个Action定义两个意图过滤器。因为Action_SENDTO意图必须定义Uri数据指定接收地址(使用send或者sendtoURI scheme)。例如:
<activity android:name="ShareActivity"> <!-- filter for sending text; accepts SENDTO action with sms URI schemes --> <intent-filter> <action android:name="android.intent.action.SENDTO"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="sms" /> <data android:scheme="smsto" /> </intent-filter> <!-- filter for sending text or images; accepts SEND action and text or image data --> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="image/*"/> <data android:mimeType="text/plain"/> </intent-filter> </activity>为了声明接收隐式意图,你必须在意图过滤器里声明 CATEGORY_DEFAULT类别。方法
startActivity()
and
startActivityForResult()
把所用Intent都认为是声明为
CATEGORY_DEFAULT类别来对待。如果你不在你的意图过滤器里声明,就没有启动你的activity的隐式意图。
onCreate()
or
onStart()里
做这。例如:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get the intent that started this activity
Intent intent = getIntent();
Uri data = intent.getData();
// Figure out what to do based on the intent type
if (intent.getType().indexOf("image/") != -1) {
// Handle intents with image data ...
} else if (intent.getType().equals("text/plain")) {
// Handle intents with text ...
}
}
// Create intent to deliver some kind of result data
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
setResult(Activity.RESULT_OK, result);
finish();
你必须要指定一个结果码。一般地,它要么是
RESULT_OK
,要么是
RESULT_CANCELED。然后,你你能用intent提供额外的必要的数据。
注:结果默认地设置为RESULT_CANCELED。因此,如果用户在完成Action和设置结果之前按返回键,前一个activity将接收到“canceled”的结果。
如果你仅仅简单地需要返回一个整数来表明若干结果选项之一,你能设置结果代码为大于0的任何值。如果你使用结果码传递整数,你不必包含Intent,你能调用setResult()方法,传递仅仅一个结果码。例如:
setResult(RESULT_COLOR_RED);
finish();
在这种情况里,可能仅仅有很少的几个可能的结果,因此结果代码是一个局部定义的整数(大于0)。当你返回结果给你自己的activity时这是很好的方式,因为接收结果的activity能引用公共常量决定结果代码值。
注:不用检查你的activity是使用 startActivity()
还是startActivityForResult()被拉起。如果开启你的activity的意图期望一个结果,简单地调用setResult()方法。如果前一个activity调用方法,那么系统传递你设置到setResult()方法里的结果;否则,该结果被忽略。