当前位置: 首页 > article >正文

Intent的匹配规则

Intent属性

  • Component Name

Intent可以被分为两类,即显性和隐性。如果我们在Intent中特别指定了目标方的“Component Name”,比如:“com.example.project.HelloActivity”;同时指定它所在的PackageName,如“com.example.project”,那么系统就会直接将此Intent发往这个特定的应用,而不需要做额外的匹配工作。

如果一个应用程序通过这种方式显性调用另一个应用进程,多半是这两个应用程序同属于一家研发公司,或者是同一进程中的两个组件。

  • Category

如果说上面的Component Name是某人的名字,那么Category就好比国籍。Android系统中已经预设了一些“国家”,我们摘录其中的部分核心元素。另外,由于Intent的所有属性值实际上都只是一串字符,因而是可以自定义的。
在这里插入图片描述

  • Action

表明要做什么,或者什么事件发生了(常用于广播的情况。比如设备开机时会有系统广播发出,如果应用程序希望实现开机自启动,就可以监听这个广播)。和Category一样,用户也可以自定义一项唯一的Action,如“com.ThinkingInAndroid.action.example”。Android系统
中预定义的部分常见Action。
在这里插入图片描述

  • data

如果上面的Action中表明了某人去公安局出入境处“办理签证”的“动作”,那么这里的Data就作为“签证”业务的补充材料——比如这个人的名字、身份证件等。所以,Action理论上是围绕Data提供的数据来开展业务的。当然也有不需要Data补充信息的情况,如在ACTION_CALL的情况下,电话号码是必须作为Data来传递的;而针对Broadcast(如ACTION_SCREEN_ON)组件的Action,它们本身就蕴含了足够的信息,因而不需要Data的支持。

  • Extras

Extras可以理解为Extra Data,它是对上面Data属性的补充。不过两者在数据的格式上有明显区别。Data采用了类似scheme://uri的表达方式;而Extras则是一种键值对实现。它们在表达不同场景的数据时有各自的优势,使用者应该“具体问题具体分析”。发送方通过一系列putXXX()方法将键值对存入Intent中,然后接收方就可以用相对应的getXXX()来获取到这些Extra数据。这些方法的内部会维护一个Bundle对象来保证进程间数据的准确传输。

  • Flags

Flags和Activity中的LaunchMode功能基本相同,它规定了系统如何去启动一个Activity(比如指定即将启动的Activity应该属于哪一个Task)。举个例子,如果一开始Task栈从下往上的顺序是A-B-C,随后C通过带有Flag为FLAG_ACTIVITY_CLEAR_TOP的Intent来启动另一个Activity。如果该Activity在栈中已经存在,比如说是B,那么启动后的Task栈就变成了A-B;否则该Activity直接入栈,而不会先清理栈顶。

Intent的匹配规则

Intent是和Intent-filter配套使用的。具体而言,Intent-filter是每个组件的属性标签,它们在AndroidManifest.xml声明时就已经“贴上”了。而Intent则是程序运行过程中产生的实时“需求”。系统接收到这些请求后与现有的Intent-filter进行匹配,然后选择最合适的组件元素以响应。

以“婚介所”为例子,Intent代表了女生的择偶意愿,而Intent-filter则是众男士的属性描述——年龄、长相、收入等。

图1描述了广播BroadcastReceiver的匹配流程,其他组件也类似。
在这里插入图片描述

  1. 组件注册
    当应用程序安装到系统中时,Android会通过扫描它的AndroidManifest文件来得到一系列信息——这其中就包括了它的;而且系统还会根据组件的不同进行相应的细化归类。比如Activity和Service,它们响应Intent的场合是不同的(前者是startActivity,后者则是startService)。换句话说,当某人调用了startActivity后,显然系统只需在所有Activity中做匹配即可,而
    不应该再去考虑Service中的Intent-filter。

  2. 发起方主动向系统提供Intent
    这一步是在程序运行过程中发生的,此时系统已经掌握了所有组
    件的信息。发起方根据自己的需求填写Intent,并按
    照目标方是Activity,Service还是BroadcastReceiver来调用对应的
    函数。如下所示:

Activity→对应startActivity();
Service →对应startService();
BroadcastReceiver→对应sendBroadcast();

系统会严格区分对待这些组件分类。所以利用
startActivity()是绝对不可能启动Service的——即便Intent和
intent-filter能成功匹配。

  1. 系统将Intent和对应组件类型(Activity,Service等)里所有的intent-filter进行匹配,以寻找最佳的结果。

隐性Intent的匹配规则

影响Intent匹配规则的只有3个关键因素,即:

  • Category
  • Action
  • Data (URI和数据类型都要同时匹配)

而其余两个属性Extras和Flags则只有在选中的组件运行后才能起作用。
一个组件可以同时声明多个intent-filter。而在匹配过程中,只要它包含的任何一个filter通过了测试,它就会被选中。

在匹配测试中,系统遵循“子集”的概念。

换句话说,Intent中的3个关键因素至少要是该组件所包含的其中一个
intent-filter的子集(Data有点特殊,下面有详细解释),才能通过
验证,如图2所示。

在这里插入图片描述

  • 每个Component(Activity、Service、BroadcastReceiver)都可以有若干个intent-filter。

  • 每个filter里的上述3种属性都可以不是唯一的。

  • 匹配时,Intent中的3种属性都需要通过测试。

Category匹配规则

  • 如果一个Intent中指定了1到N个category项
    则这个intent对象中的1到N个category,在 xxx 中,需要全有,一一匹配则通过,否则失败;

    注意:<intent-filter >中的category可以比intent对象中的category多,只要intent中的全都匹配到,就可以通过

  • 如果一个Intent中没有指定任何category项
    系统会自动为其添加一个“android.intent.category.DEFAULT”。所以,在这种情况下,如果你想让你的activity接收这些intent对象,就必须在其<intent-filter> 中添加 <category android:name="android.intent.category.DEFAULT" />

注意:如果<intent-filter>里已经带有“android.intent.action.MAIN”和“android.intent.category.LAUNCHER”,就可以不用再另外添加DEFAULT了,这是一个例外。

Action 匹配规则

  • 如果一个Intent中指定了action项

    • 如果这个action至少匹配<intent-filter> xxx </intent-filter>中的一个action,则匹配通过,否则失败;
    • 如果<intent-filter> xxx </intent-filter>中没有任何action项,则直接匹配失败;
  • 如果一个Intent中没有指定任何action项

    • 如果<intent-filter> xxx </intent-filter>中至少有一个action,则匹配通过;
    • 如果<intent-filter> xxx </intent-filter>中没有任何action项,则直接匹配失败;

Data 匹配规则

每个<data>项目都可以指定一个URI和一个MimeType(数据类型)。

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

MimeType

MIMEType的目的很简单,就是指明某段数据是什么格式类型,以保证程序能正确解析处理。比如电子邮件中的附件,如果不特别说明,接收方客户端就没办法知道它们是图片、文本还是应用程序。这样的结果就是用户找不到合适的途径来对附件进行解析。国际标准中已经预设了很多常见的文件类型,举例如下。
在这里插入图片描述

注意:type和subtype中以x-开头的属于非标准格式(未向IANA注
册);而subtype中以vnd开头的则是厂商自定义的(vendorspecific)。

例如:

  • application/vnd.oasis.opendocument.text ##OpenDocument文本
  • application/vnd.ms-excel       ##MicrosoftExcel文件
  • application/x-latex          ##LaTeX文件
  • audio/x-caf             ##Apple公司的CAF音频文件
URI

每个URI都可包含scheme, host, port, path这几属性,其格式为

<scheme>://<host>:<port>/<path>
  • 举例:content://com.example.project:200/folder/subfolder/etc

    • <scheme> 为 content

    • <host>为com.example.project

    • <port>为200

    • <path>为 folder/subfolder/etc

其中,scheme不仅包括了传统的“http”等网络协议,还有“content”来表示本地ContentProvider所提供的数据。
“host”是主机的名称,“port”指明通信的端口,它们统称为“Authority”;而且如果host不存在,后面的端口号也会被忽略。
最后一部分是文件的路径,它是该资源在host中的具体位置。

  • 注意:每个<data>项目中的URI,scheme, host, port, path这几个属性,不是全都需要指定,但有一个线性的依赖要求:

    • 如果<scheme> 没有被指定,则后面的<host>被忽略。
    • 如果<host> 没有被指定,则后面的<port>被忽略。
    • 如果<scheme><host> 都没有被指定,则后面的<path>被忽略。
匹配规则

只有intent-filter中存在的那部分属性(比如某filter中只指定了mimeType,那么只匹配mimeType),才需要进行匹配。

(1)Intent中既没有指定数据类型,也没有填写URI

在这种情况下,只有intent-filter中也同样没有指定数据和URI,才可能
通过测试。

(2)Intent中没有指定数据类型(且无法从URI中推断出),但有
URI。

数据类型有可能从URI中推断出来——如果是就属于第四种情况。前面已经说过,“只有filter中存在的那部分属性,才需要进行匹配”,因而这种情况下仅当其 URI 与的 URI 格式匹配、且同样未指定 MIME 类型时,才可能通过测试。

(3) Intent中只指定了数据类型,没有URI。

这种情况下仅当列出相同的 MIME 类型且未指定 URI 格式时,才可能通过测试。

(4)Intent中同时指定了数据类型(或可以从URI推断出)和URI。

这时数据类型和URI都必须通过测试。具体来说,仅当 MIME 类型与<intent-filter>中列出的类型匹配,同时 Intent 的 URI 与<intent-filter>中的 URI也要 匹配,或者如果 Intent 具有 content: 或 file: URI且<intent-filter>未指定 URI,则 Intent 会通过测试的 URI 部分。

URI匹配规则

  • 如果<intent-filter>中的这个URI只有一个<scheme> ,则Intent对象中拥有相同<scheme>的URI,全部通过。
  • 如果<intent-filter>中的这个URI指定了<scheme><authority> ,但是没有指定<path>,则Intent对象中拥有相同<scheme><authority> 的URI,不管其<path>是什么值,全部通过。
  • 如果<intent-filter>中的这个URI指定了<scheme><authority><path>,则Intent对象中拥有相同<scheme><authority><path>的URI,才能通过。
  • 注意:<path>中可以使用 *通配符

实例

Note Pad Example 记事本

在其manifest文件中, 记事本程序定义了三个activity, 每个有至少一个intent filter. 它还定义了一个content provider来管理笔记数据. manifest 文件如下:

<manifest XMLns:android="http://schemas.android.com/apk/res/android"
          package="com.example.android.notepad">
    <application android:icon="@drawable/app_notes"
                 android:label="@string/app_name" >

        <provider android:name="NotePadProvider"
                  android:authoritIEs="com.google.provider.NotePad" />

        <activity android:name="NotesList" android:label="@string/title_notes_list">
            <intent-filter> // NotesList-filter-1
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter> //NotesList-filter-2
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.EDIT" />
                <action android:name="android.intent.action.PICK" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
            </intent-filter>
            <intent-filter> //NotesList-filter-3
                <action android:name="android.intent.action.GET_CONTENT" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
            </intent-filter>
        </activity>
        
        <activity android:name="NoteEditor"
                  android:theme="@android:style/Theme.Light"
                  android:label="@string/title_note" >
            <intent-filter android:label="@string/resolve_edit"> //NoteEditor -filter-1
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.EDIT" />
                <action android:name="com.android.notepad.action.EDIT_NOTE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
            </intent-filter>
            <intent-filter> //NoteEditor -filter-2
                <action android:name="android.intent.action.INSERT" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
            </intent-filter>
        </activity>
        
        <activity android:name="TitleEditor"
                  android:label="@string/title_edit_title"
                  android:theme="@android:style/Theme.Dialog">
            <intent-filter android:label="@string/resolve_title"> //TitleEditor -filter-1
                <action android:name="com.android.notepad.action.EDIT_TITLE" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.ALTERNATIVE" />
                <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
                <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
            </intent-filter>
        </activity>
        
    </application>
</manifest> 

根据上面的AndroidManifest文件,可以看到NotePad共包含了3种Activity,即NotesList(用于显示已经保存的文章),NoteEditor(用于编辑文章)和TitleEditor(用于编辑文章的标题)。

下面我们通过列举几个不同的Intent来分析系统的匹配情况。

  • Intent 1
action: android.intent.action.MAIN
category: android.intent.category.LAUNCHER

Category测试:根据子集原则,只有NotesList-filter-1通过了
检查。
Action测试:依据上面所分析的Action测试第3点,NotesListfilter-1通过了检查。
Data测试:属于第一种情况,即intent里既没有指定类型,也无URI。而另外,filter也是同样的情况,因此根据“只有filter里有的那部分属性才需要检查”的原则,NotesList-filter-1最终通过了匹配。这个Intent1将对应NoteList。

  • Intent 2
action: android.intent.action.VIEW
data: content://com.google.provider.NotePad/notes

Category测试:Intent中没有指定Category,系统会自动为其加上DEFAULT值。因为所有filter都写上了这个默认值,因而全部通过测试。
Action测试:只有NotesList-filter-2和NoteEditor-filter-1通过测试。
Data测试:表面上属于第二种情况,即只指定了URI而没有类型。但实际上从这个例子中的content可以推断出type,因而属于第四种情况。推断出的类型为"vnd.android.cursor.dir/vnd.google.note"(可以参见下一小节对推断过程的源码解析),因而最终通过测试的是NotesList-filter-2。

  • Intent3
action: android.intent.action.GET_CONTENT
data type: vnd.android.cursor.item/vnd.google.note

Category测试:Intent中没有指定Category,系统会自动为其加上DEFAULT值。因为所有filter都写上了这个默认值,因而全部通过测试。

Action测试:只有NotesList-filter-3通过测试。

Data测试:只有Type,而没有URI,属于第三种情况。因为NotesList-filter-3也是同样的情况,而且它们的类型也是匹配的,所以最终通过测试。

  • Intent4
action: android.intent.action.INSERT
data: content://com.google.provider.NotePad/notes

Category测试:同Intent2。

Action测试:只有NotesEditor-filter-2符合要求。

Data测试:同Intent2。

  • Intent5
action: com.android.notepad.action.EDIT_TITLE
data: content://com.google.provider.NotePad/notes/ID

Category测试:同Intent2。

Action测试:只有TitleEditor-filter-1符合要求。

Data测试:同样可以推断出类型,
即"vnd.android.cursor.item/vnd.google.note",因而上面的
TitleEditor-filter-1最终通过测试。

类型推断

在前一节中,我们提到当Intent中只包含URI时,是有可能推断出
Type的。那么这个过程是在什么时候做的,为什么NotePad中的
“content://com.google.provider.NotePad/notes”最终可以得出
Type=“vnd.android.cursor.dir/vnd.google.note”?

实际上对Type的推断在startActivity起始就执行了,具体而言是
在execStartActivity中。

/*frameworks/base/core/java/android/content/Intent.java*/
public String resolveTypeIfNeeded(ContentResolver resolver)
{
if (mComponent != null) {
return mType; /*如果已经指定了Component Name,那就没必要
再解析类型了*/
}return resolveType(resolver);
}

resolveTypeIfNeeded——通过函数
名也可以看出,它负责从Intent中解析出相应的类型。

函数resolveTypeIfNeeded先判断ComponentName是否为空,是就
直接返回结果,否则继续调用如下方法:

public String resolveType(ContentResolver resolver) {
	if (mType != null) {
		return mType; //已经指定了类型,当然不需要再从URI中推断了
	}
	if (mData != null) {
		if ("content".equals(mData.getScheme())) {/*URI中的scheme必须是content才能
推断出类型。言下之意,像“http”这种网络协议是没有办法推测的*/
			return resolver.getType(mData); /*利用ContentResolver来解析*/
		}
	}
	return null;
}

由此可见,最终的类型推断是由ContentResolver来完成的。即:

public final String getType(Uri url) {
        IContentProvider provider =
                acquireExistingProvider(url); /*需要找到一个ContentProvider来完成解析。这给了我们一个提
示,getType有可能是每个Provider自己来完成的,而不是系统统一处理的*/
        if (provider != null) {//provider存在,直接由它完成解析
            try {
                return provider.getType(url);
            } catch (RemoteException e) {
               //异常处理
            }
        }
        if (!SCHEME_CONTENT.equals(url.getScheme())) {//又判断了一次是否为“content”
            return null;
        }
        try {

            String type = ActivityManagerNative.getDefault().getProviderMimeType(url);
            return type;
        }}

如果已经有现成的Provider,那么直接由它来解析类型;如果没
有可用的Provider,那么就要先动用AMS来找到相对应的Provider。这
和startActivity中利用Intent来找到Activity类似——这里是通过
URI来匹配Content Provider。因为Intent中的URI是“content://com.google.provider. NotePad/notes”,Authority是
com.google.provider.NotePad,所以最终会匹配到NotePad这个
Provider。

所以getType的核心步骤有两个。
(1)根据URI在AMS中找到对应的Content Provider,在这个例子中也就是NotePadProvider。
(2)根据provider中的getType()函数来解析类型。

因而可以肯定的是,每个provider都必须实现getType()方法;并且这
个接口还应该是抽象的,验证如下:

public abstract class ContentProvider implements
ComponentCallbacks2 {
…
public abstract String getType(Uri uri); //确实是抽象接口
…
}

最后来看看NotePadProvider是如何解析类型的。

@Override
    public String getType(Uri uri) {
        switch (sUriMatcher.match(uri)) {
            case NOTES:
            case LIVE_FOLDER_NOTES:
                return NotePad.Notes.CONTENT_TYPE;
            case NOTE_ID:
                return NotePad.Notes.CONTENT_ITEM_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URI "
                        + uri);
        }
    }

UriMatcher一共添加了3种模式,
当uri="content://com.google.provider.NotePad/notes"时,匹配的类
型是CONTENT_TYPE =“vnd.android.cursor.dir/vnd.google.note”。

根据之前对MIME类型的讲解,vnd表示用户自定义的类型。

当uri="content://com.google.provider.NotePad/notes/ID"时,匹配
的类型是:CONTENT_ITEM_TYPE=“vnd.android.cursor.item/vnd.google.note”。


http://www.kler.cn/a/445701.html

相关文章:

  • 基于字节大模型的论文翻译(含免费源码)
  • 谷歌浏览器的扩展市场使用指南
  • Linux 文件系统目录结构及其简要介绍
  • Redis的主从集群以及哨兵机制学习总结
  • 省略内容在句子中间
  • 揭秘区块链隐私黑科技:零知识证明如何改变未来
  • Springboo 和ORM整合
  • 基于master开发了一个feature/new_fun分支,但是开发过程中origin/master发生更新了,此时应该怎么做?
  • 《Java核心技术I》Swing中滚动窗格
  • 疾风大模型气象系统:精准预报,引领未来
  • 我不是挂王-用python实现燕双鹰小游戏2
  • 【YOLO 项目实战】(11)YOLO8 数据集与模型训练
  • JS子页面调用父页面函数,监听刷新事件
  • jdk 离线安装脚本
  • DL作业11 LSTM
  • 2024年全球安全护栏行业总体规模、主要企业国内外市场占有率及排名
  • Android上传到Minio本地存储
  • php学习资料分享
  • U-Boot的编译与烧写
  • vue el-dialog实现可拖拉
  • RabbitMQ全局流量控制
  • 基于字节大模型的论文翻译(含免费源码)
  • CPU性能优化-磁盘空间和解析时间
  • 谷歌浏览器的扩展市场使用指南
  • 共享模型之无锁(乐观锁,CAS,原子类,LongAdder)
  • postman-9.12.2–安装包及汉化