一个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[] {"[email protected]"}); // 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定义之外的联系人信息。 前两节主要聚焦在之体的一面:从你的应用里启动其他应用的activity。但是如果你的app能执行某个其他的app发起的action,你的app应该准备着响应来自于其他的app的action请求。例如,如果你开发一个社交应用,该应用能让用户分享消息或者图片给好友,实现对Action_SEND意图的支持将帮助你做到这点,这样用户就可以从另一个程序中发起一个“分享”的Action,并且启动的app来执行该Action。 为了允许其它的app启动你的activity,你需要在manifest文件里的相应的<activity>元素里添加
intent-filter>
元素。 当你的应用程序安装到设备上时,系统会扫描和识别的意图过滤器并且将这些意图类别和目录信息记录到一个内部的文件里。这些意图类别和目录被所有安装的app所支持。当app使用隐式意图调用
startActivity()
or
startActivityForResult()
时,系统查找哪个(或者哪些activity)能响应该意图。
Add an Intent Filter 为了合理地定义你的activity能处理哪个意图,每一个意图过滤器应该尽可能地明确Action的类型和activity接受的数据。 如果activity有一个满足如下Intent对象标准的意图过滤器,系统将发送该Intent给该activity: Action 一个定义执行动作的字符串。通常可以使用系统定义的值例如
ACTION_SEND
or
ACTION_VIEW.
在你的意图过滤器里使用<action>元素指定Action。在该元素里指定的值必须是Action字符串全名。而不是API常量(见上面的例子)。 Data 关联某个意图的数据描述。 在意图过滤器里使用<data>元素指定。使用这个元素里的一个或者多个属性,你能指定MIME类型,一个URI前缀,或者这些的组合和其他的能接收的数据类型。 注:如果你不需要专门指定Uri(例如当用户处理其他中的"extra"数据,而不是一个URI时),你应该仅仅指定android:mimeType属性声明你的activity处理的数据类型,例如
text/plain
or
image/jpeg。
Category 提供另外一种方式用于描述activity能处理的intent,通常关联于activty被开启的用户手势和位置。系统支持若干不同的类别,但是大多数很少被用。然而,所用隐式意图默认地被定义为
CATEGORY_DEFAULT
类别 在Intent里通过<category>标签指定intent的种类。
在你的意图过滤器里,你能用相应的XML元素内嵌在<intent-filter>元素里声明你的activity能接受和处理的动作的符合的标准。 例如,如下是一个有意图过滤器的activity声明,该activity处理数据类型是文本或者图片的 ACTION_SEND意图:
<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和data的行为是相互排斥的,你应该拆分成两个意图过滤器来分开定义。 例如,假设你的activity既处理
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的隐式意图。 更多关于发送和接收
ACTION_SEND意图——执行社交分享行为——的信息,参见
Receiving Simple Data from Other Apps
Handle the Intent In Your Activity 为了声明你的activity接收什么Action,你能阅读被用于开启activity的Intent。 当你的activity开启时,调用getIntent()方法获取开启你的Activity的Intent。你能在你的activity里的任何生命周期里调用getIntent()方法,但是一般地通常在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 ... } }Return a Result 如果你想要返回结果给调用你的activity,简单地调用 setResult()方法指定结果代码和结果intent。当操作被执行,用户想要返回到先前的activity时,调用 finish()方法关闭(和销毁)你的activity,例如:
// 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()方法里的结果;否则,该结果被忽略。