AppComponents之Intent And Intent Filters

Intent

Intent介绍

Intent是Android应用组件之间通信的消息对象,它通常表明了两个通信组件的身份以及通信的内容。它最基础的3个用法如下:

  • Starting an activity

    Activity表示应用程序中的单个界面。您可以通过 startActivity(Intent intent) 来启动Activity的新实例。Intent描述要启动的Activity以及要传递的数据。

    如果要在Activity结束后收到返回结果,请调用startActivityForResult(Intent intent) 。您的Activity能在onActivityResult()回调中的Intent对象中接收到返回结果。

  • Starting a service

    Service是一个没有用户界面且在后台执行操作的应用组件。您可以通过startService(Intent intent)启动Service来执行一次性操作(例如下载文件)。如果是Android 5.0(21)及以上可以用JobScheduler来完成启动Service的操作。Intent描述要启动的Service以及要传递的数据。

    如果Service是使用客户端-服务器接口设计的,则可以通过bindService(Intent intent)来启动绑定到另一个组件的服务。

  • Delivering a broadcast

    Broadcast是任何应用可以接收的消息。系统为系统事件提供各种Broadcast,例如系统启动或设备开始充电时。可以通过sendBroadcast(Intent intent)或sendOrderedBroadcast(Intent intent)向其他应用发送Broadcast。

Intent对象

Intent 对象携带了 Android 系统用来确定要启动哪个组件的信息(例如,准确的组件名称或应当接收该 Intent 的组件类别),以及收件人组件为了正确执行操作而使用的信息等(例如,要采取的操作以及要处理的数据)。

  • Component name

    要启动的组件名称。

    这是可选项,但也是构建显式Intent的一项重要信息,这意味着Intent应当仅传递给由组件名称定义的应用组件。 如果没有组件名称,则Intent是隐式的,且系统将根据其他Intent信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收Intent。因此,如需在应用中启动特定的组件,则应指定该组件的名称。

    注: 启动 Service 时,您应始终指定组件名称。 否则,您无法确定哪项服务会响应 Intent,且用户无法看到哪项服务已启动。

    Intent 的这一字段是一个ComponentName对象,您可以使用目标组件的完全限定类名指定此对象,其中包括应用的软件包名称。 例如,com.example.ExampleActivity。您可以使用setComponent()、setClass()、setClassName() 或 Intent 构造函数设置组件名称。

  • action

    指定要执行的通用操作(例如,“查看”或“选取”)的字符串。

    对于广播 Intent,这是指已发生且正在报告的操作。操作在很大程度上决定了其余Inten的构成,特别是数据和 extra 中包含的内容。

    您可以指定自己的操作,供Intent在您的应用内使用(或者供其他应用在您的应用中调用组件)。但是,您通常应该使用由 Intent 类或其他框架类定义的操作常量。以下是一些用于启动Activity的常见操作:

    • ACTION_VIEW

      如果您拥有一些某项 Activity可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查看的地址),请使用 Intent 将此操作与 startActivity() 结合使用。

    • ACTION_SEND

      这也称为“共享”Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 将此操作与 startActivity() 结合使用。

    您可以使用 setAction() 或 Intent 构造函数为 Intent 指定操作。

    如果定义自己的操作,请确保将应用的软件包名称作为前缀。 例如:

      static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
    
  • data

    引用待操作数据和/或该数据MIME类型的URI(Uri对象)。提供的数据类型通常由Intent的操作决定。例如,如果操作是 ACTION_EDIT,则数据应包含待编辑文档的 URI。

    创建 Intent 时,除了指定URI以外,指定数据类型(其MIME类型)往往也很重要。例如,能够显示图像的 Activity 可能无法播放音频文件,即便URI格式十分类似时也是如此。因此,指定数据的MIME类型有助于 Android 系统找到接收Intent的最佳组件。但有时,MIME类型可以从URI中推断得出,特别当数据是 content: URI 时尤其如此。这表明数据位于设备中,且由ContentProvider控制,这使得数据MIME类型对系统可见。

    要仅设置数据 URI,请调用 setData()。 要仅设置MIME类型,请调用setType()。如有必要,您可以使用 setDataAndType() 同时显式设置二者。

    注意:若要同时设置URI和MIME类型,请勿调用setData()和setType(),因为它们会互相抵消彼此的值。请始终使用 setDataAndType() 同时设置 URI 和 MIME 类型。

  • Category

    一个包含应处理 Intent 组件类型的附加信息的字符串。 您可以将任意数量的类别描述放入一个 Intent 中,但大多数 Intent 均不需要类别。 以下是一些常见类别:

    • CATEGORY_BROWSABLE

      目标 Activity 允许本身通过网络浏览器启动,以显示链接引用的数据,如图像或电子邮件。

    • CATEGORY_LAUNCHER

      该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。

    您可以使用 addCategory() 指定类别。

  • 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";
    
  • flag

    在 Intent 类中定义的、充当 Intent 元数据的标志。 标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。

Intent类型

Explicit intents(明确的意图;显式意图)

Explicit intents 通过名称(完全限定类名称)来指定要启动的组件。在应用中,一般我们都会通过Explicit intents来启动组件,因为一般我们都知道要启动的Activity或者Service的名称。

当通过显式意图启动Activity或Service时,系统将立即启动Intent中指定的应用程序组件。

构建显式Intent

为了构建显示Intent,Intent对象的Component name属性必须定义,所有其它属性都是可选的。

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

Implicit intents(含蓄的意图;隐式意图)

Implicit intents 通过描述要启动组件的特点(如action,data等)来匹配相对应的组件,我们可以通过 Implicit intents访问其他应用程序或者系统声明的组件。

当通过隐式意图启动Activity或Service时,Android系统通过将Intent的内容与在设备上包括其他应用的清单文件中声明的 Intent过滤器进行比较,从而找到要启动的相应组件。如果Intent与Intent过滤器匹配,则系统将启动该组件,并向其传递Intent对象。如果多个Intent过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。

下图展示了,系统如何通过隐式意图启动另外一个Activity。

  • Activity A 创建一个携带action等描述信息的Intent,并传递给startActivity()
  • Android 系统搜索所有应用的Intent filters去匹配Intent,当被匹配的Activity B被找到时。
  • Android 系统将会调用Activity B的onCreate()方法启动Activity B,并通过它传递Intent消息。
image

注意:为了确保应用的安全性,启动 Service 时,请使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent启动服务存在安全隐患,因为您无法确定哪些服务将响应Intent,且用户无法看到哪些服务已启动。从Android 5.0(21)开始,如果使用隐式Intent调用bindService(),系统会引发异常。

隐式Intent的构建

隐式意图指定可以调用能够执行该操作的设备上的任何应用程序的操作。也就是说我们可以通过隐式意图让设备上的其它应用程序来响应我们的操作。如果有多个应用程序可以响应我们的Intent,则会打开一个选择框由用户选择应用程序来响应我们的操作。但是也有可能由于配置文件或者管理员设置等等,没有App能响应本次操作,如果发生这种情况,应用程序会崩溃,所以我们需要检测是否有Activity能够匹配Intent,调用resolveActivity()来检测。

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

隐式Intent的应用选择器

当有多个应用程序响应你的隐式Intent时,用户可以选择使用哪个应用程序来响应本次操作,并且可以设置默认的响应应用程序。设置之后不会再显示选择框,默认使用用户选择的应用程序响应我们的操作。

但是如果希望每次都可以选择不同的应用程序选择框来选择哪个应用程序响应我们的操作,则可以通过createChooser()明确显示一个选择对话框.

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

Intent 匹配

Intent Filters 声明

Intent Filters是意图过滤器,可以通过在清单文件中使用 <intent-filter> 元素为每个应用组件声明一个或多个 Intent 过滤器来声明应用可以接收哪些隐式Intent。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 Intent 类型。 仅当隐式 Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。(显式 Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器如何均是如此。)

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

  • <action>

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

  • <data>

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

  • <category>

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

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

    <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> 元素的过滤器。创建时,必须确定组件能够处理这些过滤器元素的所有组合即可。

<activity android:name="ShareActivity">
    <!-- 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>

声明多个Intent Filters

如需仅以操作、数据和类别类型的特定组合来处理多种 Intent,则需创建多个 Intent 过滤器。只需要匹配其中任何一个Intent Filters即可。

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

Intent Filters 解析

解析包含多个Intent Filters

如果应用组件的声明中包含多个 Intent Filters,只需要匹配其中任何一个即可。

解析包含单个Intent Filters

当系统收到隐式 Intent 以启动 Activity 时,它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较,(必须每一个条件都匹配)搜索该 Intent 的最佳 Activity:

  • Action.

    要指定接受的 Intent 操作,Intent 过滤器既可以不声明任何 <action> 元素,也可以声明多个此类元素。

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

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

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

  • Category.

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

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

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

注: Android 会自动将 CATEGORY_DEFAULT 类别应用于传递给 startActivity()startActivityForResult() 的所有隐式 Intent。因此,如需 Activity 接收隐式 Intent,则必须将 "android.intent.category.DEFAULT" 的类别包括在其 Intent 过滤器中(如上文的 <intent-filter> 示例所示)。

  • Data (both URI and data type).

要指定接受的 Intent 数据, Intent 过滤器既可以不声明任何 <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 属性:

<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: 数据。
最后一条规则,即规则 (d),反映了期望组件能够从文件中或内容提供程序获得本地数据。因此,其过滤器可以仅列出数据类型,而不必显式命名 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 Filters 查询

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

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

PendingIntent

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容