Android必知必会——Intent和Intent Filter

原文转载自 「掘金Android」 ( https://juejin.im/post/5e7c84886fb9a0094365f1ed ) By CrazyCoder

预计阅读时间 0 分钟(共 0 个字, 0 张图片, 0 个链接)

关于Intent

Intent是一个消息传递对象,可以用来从其他应用组件请求操作。基本用例主要包括以下三个:

Intent的类型

通过听目标应用的软件包名称或完全限定的组件类名来指定可处理Intent的应用。

不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理。例如,需要在地图上向用户显示位置等。

使用隐式Intent,Android系统通过将Intent的内容与设备上其他应用的清单文件中声明的Intent过滤器进行比较,从而找到要启动的相应组件。如果只有一个,那么直接启动;如果有多个,那么会显示一个对话框,支持用户选取要使用的应用。

Intent过滤器,是应用清单文件中的一个表达式,用于指定该组件要接收的Intent类型。例如,通过为Activity声明Intent过滤器,可以使其他应用能够直接使用某一特定类型的Intent启动该Activity;如果未给Activity声明任何Intent过滤器,那么该Activity只能显示Intent启动。

需要注意的是:

启动Service时,应始终使用显示Intent,且不要为服务声明Intent过滤器。使用隐式Intent启动服务存在安全隐患,因为无法确定哪些服务将相应Intent,且用户也无法看到哪些服务已启动。从Android5.0开始,如果使用隐式Intent调用bindService,系统会抛出异常。

构建Intent

Intent对象携带Android系统用来确定要启动哪个组件的信息,以及收件人组件为了正确执行操作而使用的信息。也就是说主要分为两个方面:

要启动的组件名称,为可选项。如果没有那么则为隐式Intent。

对应这一属性的字段是ComponentName,可以通过方法setComponent()、setClass()、setClassName(),或Intent构造函数设置组件名称。

指定要执行的通用操作的字符串。

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

可以指定自己的操作,共应用内使用。也可以使用Intent中既定好的操作如:

ACTION_VIEW:通过一些Activity展示某些特定的信息,例如照片或地图

ACTION_SEND:也称为共享Intent。一些用户可通过其他应用共享的数据,如邮件或社交共享应用等。

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

引用待操作数据和/或该数据MIME类型的URI。

创建Intent时,除了指定URI外,指定书库类型即MIME类型往往也很重要,这有助于Android系统找到接收Intent的最佳组件。当URI为content:xxx 类型时,表明数据位于设备中,且有ContentProvider控制,系统可以通过URI获知数据类型。

仅设置数据通过setData(),如果要设置MIME类型则用setType()。需要注意的是,不能既调setData又调setType,因为这两个方法会互相清除对方的设置。同时指定时应使用setDataAndType()。

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

常见类别:

CATEGORY_BROWSABLE:目标Activity允许本身通过网络浏览器启动,以显示连接引用的数据。

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

指定类别通过方法addCategory()。

携带完成请求操作所需的附加信息的键值对。通过Bundle保存键值对信息。

在发给另一个应用接收的Intent时,不要使用Parcelable或Serializable数据,如果某个应用尝试访问Bundle对象中的数据,但没有对打包或序列化的访问权限,系统会触发RuntimeException。

对应方法是putExtra()。

可以指示Android系统如何启动Activity,以及启动之后如何处理。

对应方法是setFlags()。

隐式Intent安全校验

通过隐式Intent可以将指定动作交由可处理的应用执行。但这其中也可能存在用户设备上没有任何应用能够处理此隐式Intent,或者由于配置文件限制或管理员执行了设置,导致该隐式Intent最终执行失败,一旦发生了这种情况,调用失败,发起此行为的应用一会崩溃。

解决此种情况了方式也很简单,就是对Intent对象调用resolveActivity进行安全校验,如果有返回结果,那么至少有一个应用能够处理该Intent,这时使用隐式Intent是安全的。如果结果为空,那么该隐式Intent则不可用。

// 创建一个隐式Intent
val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, textMessage)
    type = "text/plain"
}

// 安全校验后,再使用该隐式Intent
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(sendIntent)
}
复制代码

强制使用应用选择器

通过隐式Intent发送目标意图时,可能存在多个应用均能处理此行为的情况,这时系统会弹出选择框,让用户选择一个应用处理此行为。同时,默认情况下,用户还可以将某款应用设置为此行为的默认处理应用,那么在下次触发时,则不会再显示选择框,直接交由之前用户选择的应用处理。

当然,Android系统也提供了特定的API,可以使发起的隐式Intent每次都弹出应用选择框,用户则无法为该操作选择默认应用。

val sendIntent = Intent(Intent.ACTION_SEND)
...
val title: String = resources.getString(R.string.chooser_title)
// 创建显示应用选择框的隐式Intent
val chooser: Intent = Intent.createChooser(sendIntent, title)
// 安全验证后发起操作
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}
复制代码

接收隐式Intent

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

标签内部,可以使用以下三种元素中的一个或多个指定要接受的Intent类型:

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

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

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

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

Activity组件的Intent过滤器必须在清单文件中声明,但是广播的过滤器可以通过调用registerReceiver()动态注册。Service则应始终通过显式Intent开启。

使用PendingIntent

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

PendingIntent的主要用途:

由于每个Intent对象均设计为由特定类型的应用组件(Activity、Service或BroadcastReceiver)进行处理,因此必须基于相同的考虑因素创建PendingIntent:

调用上诉三个方法时,均需要传入int类型的flag参数,而此参数是被限定在几个特定值中的,这其中就包含PendingIntent的几个常量:

标识此PendingIntent只能被使用一次。

若描述的Intent不存在则返回NULL值。

如果描述的PendingIntent已经存在,则在使用新的Intent之前会先取消掉当前的。

如果所描述的PendingIntent已经存在,请保留该标志,但用此新Intent中的内容替换其额外数据。

指示创建的PendingIntent应该是不可变的标志。

Intent解析

当收到隐式Intent启动Activity时,系统会根据三个方面将该Intent与Intent过滤器进行比较,搜索该Intent的最佳Activity。这三个方面是:action、data、category。

Action匹配


    "android.intent.action.EDIT"
/> "android.intent.action.VIEW" /> ... 复制代码

中可以不声明,也可以声明多个。但如果声明了一个或多个,则匹配时,如果隐式Intent还有action,那么该action必须与过滤器中列出的某一个相匹配。即 1 in N。

如果Activity过滤器中没有指定action,那么所有隐式Intent均无法通过。

但是如果隐式Intent中未指定action,那当Activity过滤器中至少有一项action时,该隐式Intent就可以通过匹配。即 0 in N。

Category匹配


    "android.intent.category.DEFAULT"
/> "android.intent.category.BROWSABLE" /> ... 复制代码

同action类似,在过滤器中可以不声明,也可以同时声明多个。

如果隐式Intent中有category,那其所有category均必须与过滤器中的类别匹配。即 N in N 或 N in M(N < M,但每个都在M中)。

如果隐式Intent中没有category,那么可以通过匹配。即 0 in N。

需要注意的是,Android会自动将CATEGORY_DEFAULT类别应用于传递给startActivity()和startActivityForResult()的所有隐式Intent。

data匹配


    "video/mpeg"
android:scheme="http" ... /> "audio/mpeg" android:scheme="http" ... /> ... 复制代码

同action类似,在过滤器中可以不声明,也可以同时声明多个。

每个元素均可指定URI结构和数据类型(MIME媒体类型)。URI的每个部分都是一个单独的属性:scheme、host、port和path。

URI的每个属性均为可选,但是存在线性依赖关系:

Intent中的URI与过滤器URI的匹配规则:

Intent中的URI和MIME类型,与过滤器中指定的URI和MIME类型匹配规则:

由于大部分数据可用ContentProvider分发,因此指定数据类型而非URI的过滤器也许最为常见。

Intent匹配

通过PackageManager的方法queryIntentActivities()将返回能够执行作为参数传递的Intent中列出的所有Activity,而queryIntentServices()则可返回类似的一系列服务,这两个方法均不会激活组件,只是列出。

同样还有方法resolveActivity()、resolveService()、resolveContentProvider()方法,可以返回一个当前隐式Intent的最佳处理者。

more_vert