显式Intent和隐式Intent的使用

显式Intent示例

显式Intent是指用于启动某个特定应用组件(例如,应用中的某个特定Activity或服务)的Intent。要创建显式Intent,请为Intent对象定义组件名称,Intent的所有其他属性均为可选属性。

例如,如果在应用中构建了一个名为DownloadService、旨在从Web中下载文件的服务,则可使用一下代码启动服务:

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Intent(Context, Class)构造函数分别为应用和组件提供Context和Class对象。因此,此Intent将显式启动该应用中的DownloadService类。

隐式 Intent 示例

隐式Intent指定能够在可以执行相应操作的设备上调用任何应用的操作。如果您的应用无法执行该操作而其他应用可以,且您希望用户选取要使用的应用,则使用隐式Intent非常有用。

例如,如果您希望用户与他人共享您的内容,请使用ACTION_SEND操作创建Intent,并添加指定共享内容的Extra。使用该Intent调用startActivity()时,用户可以选取共享内容所使用的应用。

警告:用户可能没有任何应用处理发送到startActivity()的隐式Intent。如果出现这种情况,则调用将会失败,切应用会崩溃。要验证Activity是否会接收Intent,请对Intent对象调用resolveActivity()。如果结果为非空,则至少有一个应用能够处理该Intent,且可以安全调用startActivity()。如果结果为空,则不应使用该Intent。如有可能,您应禁用发出该Intent的功能。

// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

注意:在这种情况下,系统并没有使用URI,但已声明Intent的数据类型,用于指定Extra携带的内容

调用startActivity()时候,系统将检查已安装的所有应用,确定哪些应用能够处理这种Intent(即:含ACTION_SEND操作并携带“文本/纯”数据的Intent)。如果只有一个应用能够处理,则该应用将立即打开并提供给Intent。如果多个Activity接收Intent,则系统将显式一个对话框,使用户能够选择要使用的应用。

强制使用应用选择器

如果多个应用相应隐式Intent,则用户可以选择要使用的应用,并将其设置为该操作的默认选项。如果用户可能希望今后一直使用相同的应用执行某项操作(例如,打开网页时,用户往往倾向于仅使用一种Web浏览器),这一点十分有用。

但是,如果多个应用可以相应Intent,且用户可能希望每次使用不同的应用,则应采用显式方式显式选择器对话框。选择器对话框要求用户选择每次操作要使用的应用(用户无法为该操作选择默认应用)。例如,当应用使用ACTION_SEND操作执行“共享”时,用户根据目前的状况可能需要使用另一不同的应用,因此应当始终使用选择器对话框。

要显式选择器,请使用createChooser()创建Intent,并将其传递给startActivity()

Intent sendIntent = 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 the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);

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

这将显示一个对话框,其中包含响应传递给createChooser()方法的Intent的应用列表,并使用提供的文本作为对话框标题。

接收隐式Intent

要公布应用可以接收哪些隐式Intent,请在清单文件中使用<intent-filter>元素为每个应用组件声明一个或多个Intent多虑器。每个Intent过滤器均根据Intent的操作、数据和类别指定自身接收的Intent类型。仅当隐式Intent可以通过Intent过滤器之一传递时,系统才会将该Intent传递给应用组件。

注意:显式Intent始终会传递给其目标,无论组件声明的Intent过滤器如何均是如此

应用组件应当为自身可执行的每个独特作业声明单独的过滤器。例如,图像库应用中的一个Activity可能会有两个过滤器,分别用于查看图像和编辑图像。当Activity启动时,它将检查Intent并根据Intent中的信息决定具体的行为(例如,是否显式编辑器控件)。

每个均由应用清单文件中的元素定义,并嵌套在相应的应用组件(例如,activity元素)中。在内部,您可以使用一下三个元素中的一个或多个指定要接收的Intent类型:

  • action

    在name属性中,声明接受Intent操作。该值必须是操作的文本字符串值,而不是类常量

  • data

    使用一个或多个指定数据URI(scheme、host、port、path等)各个方面和MIME类型的属性,声明接收的数据类型。

  • category

    在category属性中,声明接受的Intent类别,该值必须是操作的文本字符串值,而不是类常量

注意,为了接收隐式Intent,必须将CATEGORY_DEFAULT类别包括在Intent过滤器中。方法startActivity()和startActivityForResult()将按照已声明CATEGORY_DEFAULT类别的方式处理所有Intent。如果未在Intent过滤器中声明此类别,则隐式Intent不会解析为您的Activity。

例如,以下是一个使用Intent过滤器进行的Activity声明,当数据类型为文本时,系统将接收ACTION_SENDIntent:

<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"/>
    </intent-filter>
</activity>

您可以创建一个包括多个action、data或category实例的过滤器。创建时,仅需确定组件能够处理这些过滤器元素的任何及所有组合即可。

如需仅以操作、数据和类别类型的特定组合来处理多种 Intent,则需创建多个 Intent 过滤器。

系统通过将 Intent 与所有这三个元素进行比较,根据过滤器测试隐式 Intent。隐式 Intent 若要传递给组件,必须通过所有这三项测试。如果 Intent 甚至无法匹配其中任何一项测试,则 Android 系统不会将其传递给组件。但是,由于一个组件可能有多个 Intent 过滤器,因此未能通过某一组件过滤器的 Intent 可能会通过另一过滤器。如需了解有关系统如何解析 Intent 的详细信息,请参阅下文的 Intent 解析部分。

警告:为了避免无意中运行不同应用的 Service,请始终使用显式 Intent 启动您自己的服务,且不必为该服务声明 Intent 过滤器。

注意: 对于所有 Activity,您必须在清单文件中声明 Intent 过滤器。但是,广播接收器的过滤器可以通过调用 registerReceiver() 动态注册。稍后,您可以使用 unregisterReceiver() 注销该接收器。这样一来,应用便可仅在应用运行时的某一指定时间段内侦听特定的广播。

intent-filter示例

为了更好地了解一些 Intent 过滤器的行为,我们一起来看看从社交共享应用的清单文件中截取的以下片段。

<activity android:name="MainActivity">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

第一个 Activity MainActivity 是应用的主要入口点。当用户最初使用启动器图标启动应用时,该 Activity 将打开:

  • ACTION_MAIN 操作指示这是主要入口点,且不要求输入任何 Intent 数据。

  • CATEGORY_LAUNCHER 类别指示此 Activity 的图标应放入系统的应用启动器。如果activity元素未使用 icon 指定图标,则系统将使用application元素中的图标。

这两个元素必须配对使用,Activity 才会显示在应用启动器中。

第二个 Activity ShareActivity 旨在便于共享文本和媒体内容。尽管用户可以通过从 MainActivity 导航进入此 Activity,但也可以从发出隐式 Intent(与两个 Intent 过滤器之一匹配)的另一应用中直接进入 ShareActivity。

注意:MIME 类型 application/vnd.google.panorama360+jpg 是一个指定全景照片的特殊 数据类型,您可以使用 Google 全景 API 对其进行处理。

使用待定Intent

PendingIntent对象是Intent对象的包装却,PendingIntent的主要目的是授权外部应用使用包含的Intent,就像是它从您应用本身的进程中执行的一样。

待定Intent的主要用例包括:

  • 声明用户使用您的通知执行操作时所要执行的Intent(Android系统的NotificationManager执行Intent)。

  • 声明用户使用您的应用小工具执行操作时要执行的Intent(主屏幕应用执行Intent)

  • 声明未来某一特定时间要执行的Intent(Android系统的AlarmManager执行Intent)

由于每个Intent对象均设计为由特定类型的应用组件进行处理(Activity、Service或BroadcastReceiver),因此还必须基于相同的考虑因粗创建PendingIntent。使用待定Intent时,应用不会使用调用(如startActivity())执行该Intent。相反,通过调用相应的创建器方法创建PendingIntent时,您必须声明所需的组件类型:

  • PendingIntent.getActivity(),适用于启动Activity的Intent

  • PendingIntent.getService(),适用于启动Service的Intent

  • PendingIntent.getBroadcast(),适用于启动BroadcastReceiver的Intent

除非您的应用正在从其他应用中接收待定Intent,否则上述用于创建PendingIntent的方法可能是您所需的位移PendingIntent方法。

每种方法均会提供当前的应用Context、您要包装的Intent以及一个或多个指定应如何使用该Intent的标志(例如,是否可以多册使用该Intent)。

Intent解析

当系统收到隐式Intent以启动Activity时,它根据以下三个方面将该Intent与Intent-filter进行比较,搜索该Intent的最佳Activity:

  • Intent操作

  • Intent数据(URI和数据类型)

  • Intent类别

下文根据如何在应用的清单文件中声明Intent-filter,描述Intent如何与相应的组件匹配。

操作测试

要指定接受Intent操作,Intent-filter既可以不声明任何action员孙,也可以声明多个此类元素。例如:

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

要通过此过滤器,您在Intent中指定的操作必须与过滤器中列出的某一操作匹配。

如果该过滤器未列出任何操作,则Intent没有任何匹配项,因此所有Intent均无法通过测试。但是,如果Intent未指定操作,则会通过测试(只要过滤器至少包含一个操作)

类别测试

要指定接受的Intent类别,Intent过滤器既可以不声明任何category元素,也可以声明多个此类元素。例如:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

若要Intent通过类别测试,则Intent中的每个类别均必须通过与过滤器中的类别匹配。泛指则为必然,Intent-filter声明的类别可以超出Intent中指定的数据,且Intent仍会通过测试。因此,不含类别的Intent应当始终会通过此测试,无论intent-filter中声明何种类别均是如此。

注意:Android会自动将CATEGORY_DEFAULT类别应用于传递给startActivity()和startActivityForResult()的所有隐式Intent。因此,如需Activity接收隐式Intent,则必须将“android.intent.category.DEFAULT”的类别包括在其Intent过滤器中

数据测试

要指定接受的Intent数据,Intent-filter既可以不声明任何data元素,也可以声明多个此类元素。例如:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

每个data元素均可指定URI结构和数据类型(MIME介质类型)。URI的每个部分均可包含单独的scheme、host、port和path属性:

例如:

content://com.example.project:200/folder/subfolder/etc

在此URI中,架构是content,主机是com.example.project,端口是200,路径是folder/subfolder/etc

在data元素中,上述每个属性均为可选,但存在线性以来关系:

  • 如果没有指定架构,则会忽略主机

  • 如果没有指定主机,则会会略端口

  • 如果没有指定架构和组件,则会忽略路径

将Intent中的URI和过滤器中的URI规范进行比较时,它仅与过滤器中包含的部分URI进行比较,例如:

  • 如果过滤器仅指定架构,则具有该架构的所有URI均与该过滤器匹配

  • 如果过滤器指定架构和权限,但没有指定路径,则具有相同架构和权限的所有URI都会通过过滤器,无论其路径是否均是如此。

  • 如果过滤器指定架构、权限和路径,则进具有相同架构、权限和路径的URI才会通过过滤器。

注意:路径规范可以包含星号通配符,因此仅需部分匹配路径名即可

数据测试会将Intent中的URI和MIME类型与过滤器中指定的URI和MIME类型进行比较,规则如下:

  • 仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 Intent 才会通过测试。

  • 对于包含 URI、但不含 MIME 类型(既未显式声明,也无法通过 URI 推断得出)的 Intent,仅当其 URI 与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。

  • 仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME 类型、但不含 URI 的 Intent 才会通过测试。

  • 仅当 MIME 类型与过滤器中列出的类型匹配时,包含 URI 和 MIME 类型(通过显式声明,或可以通过 URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。如果 Intent 的 URI 与过滤器中的 URI 匹配,或者如果 Intent 具有 content: 或 file: URI 且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。换而言之,如果过滤器仅列出 MIME 类型,则假定组件支持 content: 和 file: 数据。

最后一条规则,反映了期望组件能够从文件中或内容提供商处获得本地数据。因此,其过滤器可以仅列出数据类型,而不必显式命名 content: 和 file: 架构。这是一个典型的案例。例如,下文中的 data 元素向 Android 指出,组件可从内容提供商处获得并显示图像数据:

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

由于大部分可用数据均由内容提供商分发,因此指定数据类型(而非 URI)的过滤器也许最为常见。

另一常见的配置是具有架构和数据类型的过滤器。例如,下文中的 data 元素向 Android 指出,组件可从网络中检索视频数据以执行操作:

<intent-filter>
    <data android:scheme="http" android:type="video/*" />
    ...
</intent-filter>

Intent匹配

通过 Intent 过滤器匹配 Intent,这不仅有助于发现要激活的目标组件,还有助于发现设备上组件集的相关信息。例如,主页应用通过使用指定 ACTION_MAIN 操作和 CATEGORY_LAUNCHER 类别的 Intent 过滤器查找所有 Activity,以此填充应用启动器。

您的应用可以采用类似的方式使用 Intent 匹配。PackageManager 提供了一整套 query...() 方法来返回所有能够接受特定 Intent 的组件。此外,它还提供了一系列类似的 resolve...() 方法来确定响应 Intent 的最佳组件。例如,queryIntentActivities() 将返回能够执行那些作为参数传递的 Intent 的所有 Activity 列表,而 queryIntentServices() 则可返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。对于广播接收器,有一种类似的方法: queryBroadcastReceivers()。

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1