Android 开发中 Intent 和 Intent 过滤器学习笔记

时间:2022-01-28 15:33:10

Android 中的 Intent 是一个消息传递对象,我们可以使用它来从其他应用,组件等发起和传递操作请求。其主要使用场景包含如下三种:

  • 启动 Activity:
    通过将 Intent 传递给 startActivity(),我们可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了启动Activity必要的数据,如:初始状态等等。如果我们想要在 Activity 完成后收到结果,需要调用 startActivityForResult() 来启动Activity,并在 Activity 的 onActivityResult() 回调中处理相关结果。

  • 启动服务:
    通过将 Intent 传递给 startService(),我们可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了必要的数据。

  • 传递广播:
    通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。(广播是任何应用均可接收的消息。例如:系统针对系统事件(如:系统启动,设备开始充电等状态事件)进行广播。)

Intent 类型分类

Intent 分为两种类型:

  • 显式 Intent:
    按名称(完全限定类名)指定要启动的组件(Activity,Service)。 当我们知道要启动的 Activity 或 Service 的类名时,我们通常使用显式 Intent 来启动组件。创建显示的Intent示例:
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
  • 隐式 Intent :
    不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。 例如,如需在地图上显示用户位置,则可以使用隐式 Intent,请求具有此功能的应用来完成这项任务。创建隐式的Intent示例:
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);
}

创建显式 Intent 启动 Activity 或服务时,系统将立即启动 Intent 对象中指定的应用组件。

创建隐式 Intent 时,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件(Application/AndroidManifest.xml)中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果存在多个 Intent 过滤器兼容,则系统会显示一个对话框,让用户决定选取要使用的应用,如:我们安装了多个浏览器,当发起一个网页显示的 Intent 请求时,会让用户决定使用什么浏览器打开网页。

Intent 过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent 类型。通过为 Activity 声明 Intent 过滤器,我们可以使得其他应用能够直接使用某一特定类型的 Intent 来启动我们配置好的 Activity。如果我们没有为 Activity 声明任何 Intent 过滤器,则该 Activity 只能通过显式 Intent 启动。例如下面的代码片段中,我们配置了包含 intent-filter 的Activity,这样其他应用就可以通过隐式 Intent 来启动该Activity。

<activity
android:name=".wxapi.WXEntryActivity"
android:label="@string/launcher_name"
android:exported="true">

<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="WECHATAPPID"/>
</intent-filter>
</activity>

PS: 为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为我们无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动,这样的机制很可能被恶意软件利用。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会引发异常。

Intent 启动Activity的过程示例

先来看看一张官方的示列图(隐式 Intent 如何通过系统传递以启动其他 Activity 的图解):
Android 开发中 Intent 和 Intent 过滤器学习笔记

  1. Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。
  2. Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。
  3. 找到匹配项之后,该系统通过调用匹配 Activity(Activity B)的 onCreate() 方法并将其传递给 Intent,以此启动匹配到的 Activity。

构建可用的 Intent

Intent 对象携带了帮助系统底层用来确定要启动的组件的描述信息(如:准确的组件名称或应当接收该 Intent 的组件类别),以及响应组件在接收到 Intent 之后要采取的操作以及要处理的数据等信息。

Intent 中包含的主要信息如下:

  • 组件名称(目标组件的完全限定类名,可选,类型是:android.content.ComponentName)
    要启动的组件名称。如果需要构建显式 Intent 这是必选项,这意味着 Intent 只能传递给由组件名称定义的应用组件。 如果没有指定组件名称,则构建出来的 Intent 是隐式的,且系统将根据其他 Intent 信息,如:Intent 的操作、数据和类别,来决定哪个组件应当接收 Intent。例如我们想要启动 com.example.ExampleActivity 这个组件。我们可以使用 Intent 实例的 setComponent()、setClass()、setClassName() 方法来填充组件名称,或在构建 Intent 的时候显示的向构造函数传递组件名称。PS:当需要使用 Intent 来启动 Service 时,应该始终指定组件名称,使得 Intent 始终是显示的。

  • 操作
    指定要执行的通用操作(例如,“查看”或“选取”)的字符串。使用 setAction() 或 Intent 构造函数为 Intent 指定操作。对于用于广播的 Intent,操作是指已发生且正在报告的事件。操作在很大程度上决定了其 Intent 的构成,特别是数据和 extra 中包含的内容。Intent 类内置了几类常见的操作常量, 如:ACTION_VIEWACTION_SEND。 当然我们也可以指定自己的操作,供 Intent 在应用内或者供其他应用在我们自己的应用中调用相关组件来响应操作。如果定义自己的操作类型,最佳实践是确保将应用的软件包名称作为前缀。

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
  • 数据
    引用待操作数据和/或该数据 MIME 类型的 URI(Uri 对象)。具体的数据类型通常由 Intent 的操作决定。例如,如果操作是 ACTION_EDIT,则数据应包含待编辑文档的 URI。创建 Intent 时,除了指定 URI 以外,指定数据类型(其 MIME 类型)往往也很重要。例如,能够显示图像的 Activity 可能无法播放音频文件,即便 URI 格式十分类似时也是如此。因此,指定数据的 MIME 类型有助于系统找到接收 Intent 的最佳组件。当 MIME 类型可以从 URI 中推断得出时,系统底层会帮助我们进行判断,可不制定数据类型。调用 setData() 设置数据 URI。调用 setType() 设置 MIME 类型。使用 setDataAndType() 同时显式设置二者。注意:若要同时设置 URI 和 MIME 类型,请始终使用 setDataAndType()。

  • 类别
    使用 addCategory() 指定类别, 类别是一个包含应处理 Intent 组件类型的附加信息的字符串。 可以将任意数量的类别描述放入一个 Intent 中,但大多数 Intent 均不需要类别。一些常见类别 CATEGORY_BROWSABLE – 目标 Activity 允许本身通过网络浏览器启动,以显示链接引用的数据,如图像或电子邮件。CATEGORY_LAUNCHER – 目标 Activity 是任务的初始 Activity,在系统的应用启动器中列出。

通过上面我们罗列出的表示 Intent 的既定特征的信息:组件名称、操作、数据和类别等信息。Android 系统能够通过读取这些属性并决定应当启动哪个应用组件来响应 Intent 消息。当然,Intent 也可以携带其他不影响系统解析的额外信息。如:

  • Extra
    携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将Bundle 插入 Intent 中。例如,使用 ACTION_SEND 创建用于发送电子邮件的 Intent 时,可以使用 EXTRA_EMAIL 键指定“目标”收件人,并使用 EXTRA_SUBJECT 键指定“主题”。
    Intent 类将为标准化的数据类型指定多个 EXTRA_* 常量。如需声明自己的 extra 键(对于应用接收的 Intent),请确保将应用的软件包名称作为前缀。 例如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
  • 标志
    调用setFlags() 方法设置,充当 Intent 元数据的标志。 标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理后续操作(例如,它是否属于最近的 Activity 列表)。