2015.10.31
个人英文阅读练习笔记(低水准)。原文地址:http://developer.android.com/training/basics/intents/index.html。
2015.10.31
一个安卓应用程序通常都会有几个活动。每个活动展示了用户可以完成特定任务(如查看地图、拍照)的用户界面。要实现用户从一个活动跳转到另一个活动中的功能,在应用程序中必须用Intent来定义应用程序的“intent”来做些什么,当传递一个具体的Intent给诸如startActivity()的系统方法时,系统会用这个Intent来识别具体的活动并启动应用程序中合适的组件。使用Intent还能够允许应用程序开启其它应用程序中的活动。
Intent可以是显示地开启一个指定的组件(具体的Activity实例),Intent也可以隐式地开启可以达到目的操作(如捕获一张图片)的任何组件。
此笔记记录了如何使用Intent来和其它的应用程序发生一些基本的交互,如启动另外一个应用程序、接收此应用程序的响应以及响应另外一个应用程序的目的。
此部分内容展示如何创建一个隐式的目的来启动另外的一个能执行某些操作的应用程序。
安卓最为重要的特性之一是通过某个操作能够将用户置于另外一个应用程序中。例如,假设您的应用程序中有些事务的地址且您想将它们显示在地图上,您不必在应用程序中构建一个显示地图的活动。您可以用Intent来创建一个访问这写地址的需求。安卓系统会启动一个能够将这些地址显示在地图上的应用程序来完成这个功能。
如Building Your First App笔记中记录的那样,在应用程序中必须使用intent来作为各活动之间的导航。通常您都会使用一个显示的目的(explicit intent),显示的目的指的是目的所对应的所要启动的组件都有对应的类名。然而,当希望另外一个独立的应用程序执行某操作时,如查看地图,您必须使用隐式的目的(implicit intent)。
此笔记内容包括怎么创建一个隐式目的来支持一个特别的操作,怎么使用隐式目的来开启另外一个应用程序并让另外一个应用程序执行一个操作。
隐式目的不需要为要开启的组件声明类,但需要声明将要执行的操作。这个操作指定了将要做的事情,如查看、编辑、发送或者是获取某些东西。目的通常还包含与操作关联的数据,如您想查看的地址,或者是您想要发送的信息。基于您想要创造的目的,数据可能是Uri、其它数据类型中的一种或者目的中根本不需要包含任何数据。
如果数据是Uri,可以用构造方法Intent()来定义操作和数据。
例如,以下代码展示了如何创建目的来启动一个用Rri数据指定电话号码的电话拨打操作:
Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
当在应用程序调用startActivity()来启动此目的时,电话应用程序将给指定的电话号码用户拨打一个电话。
以下是一些其它的目的以及目的的操作和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);
Uri webpage = Uri.parse("http://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
其余种类的目的需要提供不同数据类型的数据,如字符串。您可以通过调用putExtra()来增加一个或多个片段的数据。
默认情况下,系统通过基于包含Uri数据的目的来决定所需的合适的MIME类型。如果目的不包含任何Uri,需要用setType()来指定与目的关联的数据。设置MIME类型能够指定接收目的的活动种类。
以下是需要增加额外数据来指定操作的目的:
- 发送带有附件的邮件
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");
注:此目的中的日历事件只支持API 14及更高版本。
注:尽量精确的定义Intent是很重要的。例如,如果您想用ACTION_VIEW目的来展示图片,您应该指定一个类型为image/*的MIME。这能够阻止能查看其它数据类型的应用程序(如地图应用程序)被此目的触发。
虽然安卓平台保证特定的目的能够访问到安卓系统的内建应用程序(如电话、邮件或日历),但您在调用一个目的前还是应该包含确定是否有应用程序能够响应此目的这个步骤。
警告:当调用一个目的时,如果此设备上无能够操作此目的的应用程序,调用目的的应用程序将会崩溃。
调用queryIntentActivities()来获取能够操作所定义的目的的活动的列表,以确保是否有活动可以响应所定义的目的。如果函数返回的列表非空,就可以安全的使用这个目的。
例如:
PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;
如果isIntentSafe是true,就意味着至少有一个应用程序可以响应此目的;如果为false,意味着没有任何应用程序可以响应此目的。
注:应该在活动第一次启动时就做是否有应用程序可以响应目的的检查,如果没有,则在用户使用它们之前关闭这些目的。如果您知道某个特殊的程序能够支持所定义的目的,那么您可以给用户提供一个下载此应用程序的地址。(参见 link to your product on Google Play)
一旦创建了Intent并设置了其额外的数据,那么就调用startActivity()将目的发送给系统。如果系统识别到超过一个能够识别此目的的应用程序,系统将会通过对话框的方式呈现出来供用户选择。如图1.如果只有一个活动能够识别目的,系统将会立即启动它。
startActivity(intent);
图1.当超过一个应用程序可以识别目的时系统用对话框展示应用程序的图示
以下代码功能:创建了访问地图的目的,确定是否有应用程序能够操作此目的,如果存在则启动目的。
// 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()来启动活动并且有多个应用程序满足要求时,用户可以选择一个默认的应用程序(通过选择对话框底部的复选框,见图1)。对与用户每次都想选择这个应用程序来响应目的的情况来说是非常方便的,如打开网页(选择某网页浏览器)或者拍照(选择相机)。
然而,如果用户每次都希望用不同的应用程序来响应操作(目的)时,如“分享”操作,此时就应该用选择列表的方式呈现应用程序,如图2.选择列表每次都会让用户选择一个具体的应用程序(用户不能选择默认的应用程序来响应目的)。
图2.选择对话框
用createChooser()创建Intent可以显示选择对话框。例如:
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);
}
这段代码会展示一个列满能够响应传递给createChooser()的目的的应用程序的选择对话框,并用提供的文本作为对话框标题。
此部分内容展示如何开启另外一个活动并从这个活动中接收结果。
启动另外的活动并不一定是单向的。也可以在启动另外的活动后接收一个返回结果,这可以通过调用startActivityForResult()来实现。
例如,在启动相机应用程序后接收一张所拍的照片作为返回结果。或者启动联系人应用程序选择一个联系人信息作为返回结果。
当然,相应的活动也要被设计为能够返回结果的功能才行。当具有这样的功能时,它将返回结果保存在另外一个Intent中。您的活动在onActivityResult()回调方法中或取这个返回结果。
注:当调用startActivityForResult()时可以使用显示或者隐式的目的。当启动某个活动来获取返回结果时,需要用显示的目的来确保是否接收到了期望的返回结果。
当要从另外一个活动接收返回结果时,目的不需要额外的操作。但需要给startActivityForResult()方法传递一个额外的整型参数。
这个整型参数是标识请求的“请求码”。当收到返回的Intent时,回调方法提供相同的请求码,这样您的应用程序就能够标识返回结果并决定怎么处理返回结果。
例如,以下代码开启允许用户挑选联系人的活动:
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);
}
当用户完成操作从后续活动中返回时,系统会调用活动的onActivityResult()方法。此方法包含3个参数:
例如,以下代码展示“选择联系人”目的的操作:
@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)
}
}
}
在这个例子中,结果Intent包含了跟用户所选择的联系人对应的安卓系统的联系程序提供或者是联系人应用程序提供的Uri。
为了成功的处理返回结果,需要知道返回结果Intent的格式。当返回结果是本应用程序某活动类型时比较简单。安卓平台上的应用程序包含了它们自己的APIs来处理特定的返回数据。例如,联系人应用程序返回包含URI内容来标识被选择联系人的信息,相机返回Bitmap数据(参见 Capturing Photos)。
附:读联系人信息
以上从联系人应用程序读取返回结果的代码并不能从返回结果中读取到细节,因为它需要更多的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...
}
}
}
注:在安卓2.3(API 9)之前,用Contacts Provider查询时需要应用程序声明READ_CONTACTS权限。然而,从安卓2.3开始,联系人程序提供给其它应用程序一个暂时访问的权限。暂时的权限意味着只有在指定联系任请求时才能访问,除了用目的Uri指定外的联系人不能被访问,除非在应用程序中声明了READ_CONTACTS权限。
此部分内容展示通过定义声明隐式目的的目的过滤器来使应用程序中的活动能被其他应用程序打开。
本笔记前两部分都集中于开启其它应用程序的活动。若您的应用程序也能够执行对其它应用程序有用的操作,那您的应用程序要能够响应其它应用程序的操作(action)。例如,一个能够分享信息或照片给用户朋友的应用程序,就比较感兴趣支持ACTION_SEND目的,这样用户根据其它应用程序的操作来开启您的这个应用并在您的应用中实现“分享”操作。
为了允许其它的应用程序来开启您的应用程序的活动,您需要在与具体活动关联的清单文件中的元素下增加元素。
当您的应用程序安装到具体设备上时,在安装的过程中系统会识别应用程序中的目的过滤器(intent filter)并将目的过滤器的信息添加到目的(intent)的内部目录中。当应用程序调用startActivity()或者startActivityForResult()时,系统会通过使用隐式的目的去寻找哪些活动可以关联到这个目的之上。
为了定义您的活动能够操控的具体的目的,所增加的每一个目的过滤器都应该被明确地指明活动所需的操作和数据的类型。
如果活动有一个能够执行以下标准的目的过滤器的对象,系统会发送一个给定的目的给活动。
操作(Action)
一个字符串可以表明操作的功能。平台下常见的值有ACTION_SEND或ACTION_VIEW。
在目的过滤器的元素中指定。用操作的全名指定操作的值,不能用API常量(见以下的例子)。
数据(Data)
与目的关联的数据的一个描述。
在过目的过滤器的元素中指定。在此元素中可以使用一个或多个属性,可以只指定一个MIME类型,一个URI前缀,或者这些类型的联合,或者其它可被接受的类型。
注:如果不需要指定数据Uri(如活动操作其它的“额外”数据,而不是URI),您应该只使用android:mimeType属性来声明活动所操控的数据类型,如text/plain或者image/jpeg。
类别(Category)
提供的另外的一种标识活动操控目的的方式,常跟用户的手势或者位置关联。系统支持好集中不同的类别,但是大多数都是很少被用到。然而,默认情况下,隐式的目的被CATEGOGY_DEFAULT定义。
在目的过滤器中的元素下指定类别。
在目的过滤器中,在与活动关联的XML文件中的元素下定义活动所接受的目的。
例如,以下活动能够处理数据类型为文本或者图片时的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_SEND和ACTION_SENDTO两个操作,它们都支持文本和图片。在这种情况下,必须为两个操作定义两个独立的目的过滤器,因为ACTION_SENDTO目的必须使用数据Uri通过send或sendto URI方案确定接收者用的地址。例如:
<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()和startActivityForResult()方法像对声明在CATEGORY_DEFAULT类别下的目的那样对待所有的目的。如果没有在目的过滤器中声明此类别,那么您的活动不会处理任何的隐式目的。
更多关于具有共享行为的发送和接收的ACTION_SEND目的参见 Receiving Simple Data from Other Apps.
为了决定在活动中操作目的的哪一个操作,可以读用来开启操作的目的。
当活动被开启时,调用getIntent()来检索目的。在活动的任何一个生命周期里都可以这样做,但应该在活动早期的生命活动周期函数中做这个事情,如在onCreate()或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 ...
}
}
如果想给来开启您应用程序的活动的应用程序一个返回结果,调用setResult()来指定返回代码和返回目的。当这个操作完成且用户要返回原来的活动中时,调用finish()来关闭(并销毁)您的活动。例如:
// 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。若有必要,接着就可以为目的准备其余的数据。
注:默认时返回值被设置为RESULT_CANCELED。所以,如果用户在完成处理操作或设置返回结果之前按返回键,用户的原始活动就会收到“canceled”返回结果。
如果只是需要返回标识几个选择的整型值,可以设置返回码为大于0的值。如果使用返回码作为整数返回值,那么就没有必要再包含目的,可以通过调用setResult()来传递返回码。例如:
setResult(RESULT_COLOR_RED);
finish();
在这种情况下,返回结果可能只有少数,所以返回码是一些本地定义的整数(大于0)。当从应用程序返回结果到其它应用中时会工作良好,因为活动能根据公开的常量来判断返回码。
注:没有必要检查活动是否用startActivity()或者startActivityForResult()启动的。如果目的启动了您的活动并要求一个返回值时,直接调用setResult()。如果原始的活动调用了startActivityForResult(),系统会将返回结果应用到setResult();否则返回结果会被忽略。
[2015.11.11-10:00]