向另一个应用发送用户

Android 最重要的功能之一是应用能够基于它要执行的“操作”向另一个应用发送用户。例如,如果您的应用有您要在地图上显示的公司地址,您无需在显示地图的应用中构建 Activity,而是可以创建使用 Intent 查看地址的请求。随后,Android 系统会启动可以在地图上显示该地址的应用。

正如第一堂课(构建您的第一个应用)所述的,您必须使用 Intent 在自己应用中的 Activity 之间进行导航。您通常使用显式 Intent 执行此操作,该 Intent 定义您希望启动的组件的确切类名称。但是,当您希望另一应用执行“查看地图”等操作时,则必须使用隐式 Intent

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

构建隐式 Intent

隐式 Intent 不声明要启动的组件的类名称,而是声明要执行的操作。该操作指定您要执行的操作,例如查看编辑发送获取内容。Intent 通常还包括与操作相关的数据,例如您想查看的地址或您想发送的电子邮件消息。根据您想创建的 Intent,数据可能是 Uri、其他数据类型之一,或者 Intent 可能根本不需要数据。

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

例如,下方代码为如何使用指定电话号码的 Uri 数据创建发起电话呼叫的 Intent:

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
        // Uri location = 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("http://www.android.com").let { webpage ->
        Intent(Intent.ACTION_VIEW, webpage)
    }
    

    Java

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

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

默认情况下,系统根据添加的 Uri 数据确定 Intent 所需的相应 MIME 类型。如果您未在 Intent 中添加 Uri,则通常应使用 setType() 指定与 Intent 关联的数据类型。设置 MIME 类型可进一步指定哪些类型的 Activity 应接收 Intent。

以下是一些通过添加 extra 数据来指定所需操作的 Intent:

  • 发送带附件的电子邮件:

    Kotlin

    Intent(Intent.ACTION_SEND).apply {
        // The intent does not have a URI, so declare the "text/plain" MIME type
        type = HTTP.PLAIN_TEXT_TYPE
        putExtra(Intent.EXTRA_EMAIL, arrayOf("jon@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[] {"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
    
  • 创建日历事件:

    Kotlin

    Intent(Intent.ACTION_INSERT, Events.CONTENT_URI).apply {
        val beginTime: Calendar = Calendar.getInstance().apply {
            set(2012, 0, 19, 7, 30)
        }
        val endTime = Calendar.getInstance().apply {
            set(2012, 0, 19, 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

    Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
    Calendar beginTime = Calendar.getInstance();
    beginTime.set(2012, 0, 19, 7, 30);
    Calendar endTime = Calendar.getInstance();
    endTime.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。

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

验证是否存在接收 Intent 的应用

尽管 Android 平台保证某些 Intent 可以分解为内置应用之一(例如“电话”、“电子邮件”或“日历”应用),但您应始终在调用 Intent 之前执行验证步骤。

注意:如果您调用了 Intent,但设备上没有可以处理 Intent 的应用,您的应用将崩溃。

要确认是否存在可响应 Intent 的可用 Activity,请调用 queryIntentActivities() 来获取能够处理您的 Intent 的 Activity 列表。如果返回的 List 不为空,您可以安全地使用该 Intent。例如:

Kotlin

val activities: List<ResolveInfo> = packageManager.queryIntentActivities(
        intent,
        PackageManager.MATCH_DEFAULT_ONLY
)
val isIntentSafe: Boolean = activities.isNotEmpty()

Java

PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
        PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;

如果 isIntentSafetrue,则至少有一个应用将响应该 Intent。如果其为 false,则没有任何应用处理该 Intent。

请注意:如果您需要在用户试图使用该 Intent 前停用使用该 Intent 的功能,您应在 Activity 初次启动时执行此检查。如果您知道可以处理 Intent 的特定应用,则还可以为用户提供下载该应用的链接(请参阅如何在 GooglePlay 上链接到您的产品)。

启动具有 Intent 的 Activity

图 1. 多个应用皆可处理 Intent 时显示的选择对话框示例。

创建 Intent 并设置 extra 信息后,请调用 startActivity() 以将其发送给系统。如果系统识别出可处理 Intent 的多个 Activity,其会为用户显示对话框(有时称为“消歧对话框”),供其选择要使用的应用,如图 1 所示。如果只有一个 Activity 处理 Intent,系统会立即将其启动。

Kotlin

startActivity(intent)

Java

startActivity(intent);

以下为完整示例,展示如何创建查看地图的 Intent,验证是否存在处理 Intent 的应用,然后启动 Activity:

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)

// Verify it resolves
val activities: List<ResolveInfo> = packageManager.queryIntentActivities(mapIntent, 0)
val isIntentSafe: Boolean = activities.isNotEmpty()

// Start an activity if it's safe
if (isIntentSafe) {
    startActivity(mapIntent)
}

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

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

显示应用选择器

图 2. 选择器对话框。

注意,当您通过将您的 Intent 传递至 startActivity() 来启动 Activity 时,有多个应用响应 Intent,用户可以选择默认使用哪个应用(通过选中对话框底部的复选框;见图 1)。当执行用户通常希望每次使用相同应用执行的操作时,例如打开网页(用户可能只使用一个网络浏览器)或拍照(用户可能习惯使用一个相机)时,这非常有用。

但是,如果要执行的操作可由多个应用处理并且用户可能喜欢每次选择不同的应用 — 例如“共享”操作,用户有多个应用分享项目 — 您应明确显示选择器对话框,如图 2 所示。选择器对话框强制用户选择用于每次操作的应用(用户不能对此操作选择默认应用)。

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

Kotlin

val intent = Intent(Intent.ACTION_SEND)
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
val title = resources.getString(R.string.chooser_title)
// Create intent to show chooser
val chooser = Intent.createChooser(intent, title)

// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}

Java

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() 方法的 Intent 的应用列表,并且将提供的文本用作对话框标题。