一个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定义之外的联系人信息。