Android 消息传递之Intent和IntentFilter的匹配规则

1.Intent概述及作用

Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。
详见官方文档

主要功能如下:

  1. 启动Activity: 通过将 Intent 传递给 startActivity(),您可以启动新的 Activity 实例。Intent 描述了要启动的 Activity,并携带了任何必要的数据。
    如果您希望在 Activity 完成后收到结果,请调用 startActivityForResult()。在 Activity 的 onActivityResult() 回调中,您的 Activity 将结果作为单独的 Intent 对象接收。
  2. 启动服务: 通过将 Intent 传递给 startService(),您可以启动服务执行一次性操作(例如,下载文件)。Intent 描述了要启动的服务,并携带了任何必要的数据。
    如果服务旨在使用客户端-服务器接口,则通过将 Intent 传递给 bindService(),您可以从其他组件绑定到此服务。
注意:为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 Intent,且用户无法看到哪些服务已启动。
  1. 传递广播: 通过将 Intent 传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast(),您可以将广播传递给其他应用。

2.Intent分类

  • 显式Intent: 按名称(完全限定类名)指定要启动的组件。
  • 隐式Intent: 无需明确指定组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。

两种Intent在启动Activity或Service时的区别:

  1. 创建显式Intent启动 Activity 或服务时,系统将立即启动 Intent 对象中指定的应用组件。
  2. 创建隐式Intent启动Activity,Android 系统通过将 Intent 的内容与在设备上其他应用的清单文件中声明的 Intent 过滤器进行比较,从而找到要启动的相应组件。 如果 Intent 与 Intent 过滤器匹配,则系统将启动该组件,并向其传递 Intent 对象。 如果多个 Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。
    在这里插入图片描述
    图 1. 隐式 Intent 如何通过系统传递以启动其他 Activity 的图解:[1] Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。[2] Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。 找到匹配项之后,[3] 该系统通过调用匹配 Activity(Activity B)的 onCreate() 方法并将其传递给 Intent,以此启动匹配 Activity。

3.构建Intent

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

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

  • 操作(action): 指定要执行的通用操作(例如,“查看”或“选取”)的字符串。您可以使用 setAction() 或 Intent 构造函数为 Intent 指定操作。

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

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
  • 数据(data): 引用待操作数据和/或该数据 MIME 类型的 URI(Uri 对象)。提供的数据类型通常由 Intent 的操作决定。创建 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): 您可以使用 addCategory() 指定类别。

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

以上列出的这些属性(组件名称、操作、数据和类别)表示 Intent 的既定特征。 通过读取这些属性,Android
系统能够解析应当启动哪个应用组件。

但是,Intent 也有可能会携带一些不影响其如何解析为应用组件的信息。 Intent 还可以提供:

  • Extra: 携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 URI 一样,有些操作也使用特定的 extra。您可以使用各种 putExtra() 方法添加 extra 数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有 extra 数据的 Bundle 对象,然后使用 putExtras() 将Bundle 插入 Intent 中。
  • 标志: 在 Intent 类中定义的、充当 Intent 元数据的标志。 标志可以指示 Android 系统如何启动 Activity(例如,Activity 应属于哪个任务),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。

4.解析Intent(IntentFliter匹配规则)

Intent 过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent 类型。

匹配原则: Intent需要匹配多组intent-fliter中的任意一组,每一组包含action、data、category,即Intent同时满足这三者的过滤规则。

  1. action的匹配原则:
    Intent中的action存在且必须和过滤规则中的其中一个action相同。(区分大小写)如果该过滤器未列出任何action,则 Intent 没有任何匹配项,因此所有 Intent 均无法通过测试。 但是,如果 Intent 未指定操作,则会通过测试(只要过滤器至少包含一个操作)。
  2. category的匹配原则:
    Intent中如果存在categary,那么所有的category都必须和intent-filter中相同。如果不含category,也可以匹配成功,因为在startActivity()中会默认给intent添加“android.intent.category.DEFAULT”。因此,要想acitivity接受隐式Intent,则必须在intent-filter中添加此category。

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

  1. data的匹配原则:
    与action类似,如果过滤规则中定义了data,那么Intent中则必须也要定义可匹配的data。data的语法结构如下:
 <data
    android:scheme="string"
    android:host="string"
    android:port="string"
    android:path="string"
    android:pathPrefix="string"
    android:pathPattern="string"
    android:mimeType="string"/>

data由两部分组成:

  • mimeType: 指媒体类型
  • URI: 路径

结构< scheme >://< host >:< port >/[< path >|< pathPrefix >|< pathPattern >]

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

介绍一下每个数据的含义:

  • Scheme: URI的模式,如http、file、content(必须指定否则整个URI无效)
  • Host:URI的主机名(必须指定否则整个URI无效)
  • Port:URI的端口号
  • Path/pathPattern/pathPrefix:表述路径信息,Path表示完整的路径信息;pathPattern也表示完整的路径信息,可包含通配符“*”,表示0到多个任意字符,pathPrefix表示路径的前缀信息

原则:要求Intent中必须含有data数据,并且能完全匹配过滤规则中的都一个data。完全匹配指过滤规则中出现的data部分也出现在了Intent中的data。intent-filter中可以不指定URI,但是有默认值,URI的默认值为content和file。因此要匹配URI必须为content和file。

在这里插入图片描述

测试

如下是一个过滤规则的实例:

<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>
  1. action测试

添加intent-filter:

<activity android:name=".Main2Activity">
            <intent-filter>
                <action android:name="android.intent.action.EDIT"/>
                <action android:name="android.intent.action.CALL"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
</activity>

MainActivity中添加:

btnAction.setOnClickListener {
            val intent = Intent()
            intent.action = Intent.ACTION_EDIT
            startActivity(intent)
        }

点击按钮成功跳转。
在这里插入图片描述

  1. category测试
    添加intent-filter:
        <activity android:name=".Main2Activity">
            <intent-filter>
                <action android:name="android.intent.action.EDIT"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="com.wdl.intentfliter.category.a"/>
                <category android:name="com.wdl.intentfliter.category.b"/>
                <category android:name="com.wdl.intentfliter.category.c"/>
            </intent-filter>
        </activity>

MainActivity中添加:

 btnAction.setOnClickListener {
            val intent = Intent()
            intent.action = Intent.ACTION_EDIT
            intent.addCategory("com.wdl.intentfliter.category.b")
            intent.addCategory("com.wdl.intentfliter.category.a")
            intent.addCategory("com.wdl.intentfliter.category.c")
            startActivity(intent)
        }

点击按钮成功跳转。
在这里插入图片描述

  1. data测试
    添加intent-filter:
<activity android:name=".Main2Activity">
            <intent-filter>
                <action android:name="android.intent.action.EDIT"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="com.wdl.intentfliter.category.a"/>
                <category android:name="com.wdl.intentfliter.category.b"/>
                <category android:name="com.wdl.intentfliter.category.c"/>
                <data android:mimeType="text/plain"/>
            </intent-filter>
        </activity>

MainActivity中:

 btnAction.setOnClickListener {
            val intent = Intent()
            intent.action = Intent.ACTION_EDIT
            intent.setDataAndType(Uri.parse("file://test"),"text/plain")
            intent.addCategory("com.wdl.intentfliter.category.b")
            intent.addCategory("com.wdl.intentfliter.category.a")
            intent.addCategory("com.wdl.intentfliter.category.c")
            startActivity(intent)
        }

上面的例子中存在Android 7.0 FileProvider适配问题,请自行解决。

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

为了避免在intent-fliter匹配过程中出现找不到Activity导致的异常闪退问题,提供了2中判断方法:

  1. PackageManager的resolveActivity方法
  2. Intent中的resolveActivity方法
    如果以上两种方法找不到匹配的Activity,就会返回null。防止出现异常。
 btnAction.setOnClickListener {
            val intent = Intent()
            intent.action = Intent.ACTION_EDIT
            intent.type = "text/plain"
            intent.addCategory("com.wdl.intentfliter.category.b")
            intent.addCategory("com.wdl.intentfliter.category.a")
            intent.addCategory("com.wdl.intentfliter.category.c")
            packageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY)?.let {
                startActivity(intent)
            }
        }

MATCH_DEFAULT_ONLY:
这个标记位含义是仅仅匹配那些声明了 < category android:name=“android.intent.category.DEFAULT”/>的Activity。

另外PackageManager还提供了queryIntentActivities方法,返回所有匹配成功的Activity。

猜你喜欢

转载自blog.csdn.net/qq_34341338/article/details/82997517