向另一个应用发送用户

Android 最重要的功能之一是应用能够基于它要执行的“操作”将用户转到其他应用。例如,如果您的应用包含您想要在地图上显示的商家地址,您无需在应用中构建用于显示地图的 activity,而是可以创建使用 Intent 查看地址的请求。然后,Android 系统即会启动能够在地图上显示地址的应用。

正如第一课构建首个应用中所述,您必须使用 intent 在自己应用中的 activity 之间进行导航。通常情况下,您可以使用显式 intent 来实现该目标,显式 intent 可定义您希望启动的组件的确切类名称。但是,如果您希望让另一个应用来执行某项操作(例如,“查看地图”),则必须使用隐式 intent。

本课向您展示如何针对特定操作创建隐式 intent,以及如何使用该 intent 启动在另一个应用中执行操作的 activity。此外,请观看此处的嵌入视频,了解为隐式 intent 添加运行时检查的重要性。

构建隐式 intent

隐式 intent 不会声明要启动的组件的类名称,而是声明要执行的操作。该操作指定您要执行的操作,例如查看、编辑、发送或获取某项内容。

将 intent 操作与数据相关联

intent 通常还包括与操作相关的数据,例如您要查看的地址或您要发送的电子邮件。根据您要创建的 intent,数据可能是 Uri 或其他几种数据类型之一,也可能该 intent 根本不需要数据。

如果您的数据是 Uri,则可以使用一个简单的 Intent() 构造函数来定义操作和数据。

例如,以下代码段展示了如何创建 intent 来发起通话(使用 Uri 数据指定电话号码):

Kotlin

val callIntent: Intent = Uri.parse("tel:5551234").let { number ->
    Intent(Intent.ACTION_DIAL, number)
}

Java

Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);

当您的应用通过调用 startActivity() 来调用此 intent 时,“电话”应用会发起对指定手机号码的呼叫。

以下是一些其他 intent 及其操作和 Uri 数据对:

查看地图

Kotlin

// Map point based on address
val mapIntent: Intent = Uri.parse(
        "geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California"
).let { location ->
    // Or map point based on latitude/longitude
    // val location: Uri = Uri.parse("geo:37.422219,-122.08364?z=14") // z param is zoom level
    Intent(Intent.ACTION_VIEW, location)
}

Java

// 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);

查看网页

Kotlin

val webIntent: Intent = Uri.parse("https://www.android.com").let { webpage ->
    Intent(Intent.ACTION_VIEW, webpage)
}

Java

Uri webpage = Uri.parse("https://www.android.com");
Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);

向 intent 添加额外数据

其他类型的隐式 intent 需要“额外”数据,以提供字符串等不同数据类型。您可以使用各种不同的 putExtra() 方法添加一条或多条额外数据。

默认情况下,系统根据所包含的 Uri 数据确定 intent 需要的相应 MIME 类型。如果您不在 intent 中包括 Uri,通常应使用 setType() 来指定与 intent 相关联的数据类型。设置 MIME 类型可以进一步指定应接收 intent 的 activity 类型。

以下是一些添加了额外数据来指定所需操作的其他 intent:

发送带有附件的电子邮件

Kotlin

Intent(Intent.ACTION_SEND).apply {
    // The intent does not have a URI, so declare the "text/plain" MIME type
    type = "text/plain"
    putExtra(Intent.EXTRA_EMAIL, arrayOf("jan@example.com")) // recipients
    putExtra(Intent.EXTRA_SUBJECT, "Email subject")
    putExtra(Intent.EXTRA_TEXT, "Email message text")
    putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"))
    // You can also attach multiple items by passing an ArrayList of Uris
}

Java

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[] {"jan@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

创建日历活动

注意:仅 API 级别 14 及更高级别支持此日历活动的 intent。

Kotlin

// Event is on January 23, 2021 -- from 7:30 AM to 10:30 AM.
Intent(Intent.ACTION_INSERT, Events.CONTENT_URI).apply {
    val beginTime: Calendar = Calendar.getInstance().apply {
        set(2021, 0, 23, 7, 30)
    }
    val endTime = Calendar.getInstance().apply {
        set(2021, 0, 23, 10, 30)
    }
    putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.timeInMillis)
    putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.timeInMillis)
    putExtra(Events.TITLE, "Ninja class")
    putExtra(Events.EVENT_LOCATION, "Secret dojo")
}

Java

// Event is on January 23, 2021 -- from 7:30 AM to 10:30 AM.
Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
Calendar beginTime = Calendar.getInstance();
beginTime.set(2021, 0, 23, 7, 30);
Calendar endTime = Calendar.getInstance();
endTime.set(2021, 0, 23, 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");

注意:请务必尽可能具体地定义您的 Intent。例如,如果您要使用 ACTION_VIEW intent 显示图像,则应指定 MIME 类型 image/*。这可防止 intent 触发能够“查看”其他类型的数据的应用(例如,地图应用)。

使用 intent 启动 activity

创建 Intent 并设置额外信息后,请调用 startActivity() 以将其发送到系统:

Kotlin

startActivity(intent)

Java

startActivity(intent);

应对没有应用可接收 intent 的情况

尽管许多 intent 都可以由设备上安装的其他应用(例如“电话”“电子邮件”或“日历”应用)成功处理,但您的应用应该做好准备,以便妥善应对没有 activity 可处理您应用的 intent 的情况。每次调用 intent 时,都做好捕获 ActivityNotFoundException 的准备,如果没有其他 activity 可处理您应用的 intent,就会出现此异常:

Kotlin

try {
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}

Java

try {
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

捕获此异常后,决定您的应用接下来应执行的操作。接下来的这个操作取决于您尝试调用的 intent 的具体特征。例如,如果您知道可以处理相应 intent 的特定应用,则可以为用户提供下载该应用的链接。不妨详细了解如何在 Google Play 上链接到您的产品

消除歧义对话框

如果系统识别出多个可以处理 intent 的 activity,则会向用户显示一个对话框(有时称为“消除歧义对话框”),以供其选择要使用的应用,如图 1 所示。如果只有一个 activity 可以处理 intent,系统会立即启动它。

在屏幕底部附近显示的面板。此面板列出了可以处理 intent 的不同应用。

图 1. 当多个应用都可以处理某个 intent 时显示的选择对话框示例。

完整示例

以下是一个完整示例,展示了如何创建 intent 来查看地图,验证是否存在可以处理该 intent 的应用,然后启动它:

Kotlin

// Build the intent.
val location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California")
val mapIntent = Intent(Intent.ACTION_VIEW, location)

// Try to invoke the intent.
try {
    startActivity(mapIntent)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}

Java

// 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);

// Try to invoke the intent.
try {
    startActivity(mapIntent);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

显示应用选择器

图 2. 选择器对话框。

注意,当您通过将 Intent 传递给 startActivity() 启动一个 activity,并且有多个应用可以响应该 intent 时,用户可以选择默认使用哪个应用(通过选中对话框底部的复选框;请参见图 1)。当执行用户通常希望每次都使用同一应用完成的操作时,比如打开网页(用户可能只使用某个网络浏览器)或拍照(用户可能更喜欢某个相机),这非常有用。

但是,如果要执行的操作可以由多款应用处理并且用户可能希望每次都使用不同的应用(例如,“分享”操作,用户可能有多款应用可用来分享内容),您应明确显示选择器对话框,如图 2 所示。选择器对话框会强制用户每次都要选择用于相应操作的应用(用户无法针对该操作选择默认应用)。

如需显示选择器,请使用 createChooser() 创建 Intent,并将其传递给 startActivity()。例如:

Kotlin

val intent = Intent(Intent.ACTION_SEND)

// Create intent to show chooser
val chooser = Intent.createChooser(intent, /* title */ null)

// Try to invoke the intent.
try {
    startActivity(chooser)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}

Java

Intent intent = new Intent(Intent.ACTION_SEND);

// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, /* title */ null);

// Try to invoke the intent.
try {
    startActivity(chooser);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

这会显示一个对话框,其中包含可响应向 createChooser() 方法传递的 intent 的应用列表。如果操作不是 ACTION_SEND ACTION_SEND_MULTIPLE,则可以提供 title 参数