将用户转到其他应用

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

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

本课程介绍了如何为特定操作创建隐式 Intent,以及如何使用它启动在其他应用中执行操作的 Activity。另请参阅此处嵌入的视频,了解为什么为隐式 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
            // 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 需要“额外”数据,以提供字符串等不同数据类型。您可以使用各种不同的 putExtra() 方法添加一条或多条额外数据。

默认情况下,系统会根据 Intent 中所包含的 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 = 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。

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

使用 Intent 启动 Activity

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

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

Kotlin

    startActivity(intent)
    

Java

    startActivity(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)

    // 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 的应用列表,并将提供的文本用作对话框标题。