Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Android Intent与IntentFilter

Android Intent与IntentFilter案例详解

作者:王三的猫阿德

这篇文章主要介绍了Android Intent与IntentFilter案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

1. 前言

       在Android中有四大组件,这些组件中有三个组件与Intent相关,可见Intent在Android整个生态中的地位高度。Intent是信息的载体,用它可以去请求组件做相应的操作,但是相对于这个功能,Intent本身的结构更值得我们去研究。

2. Intent与组件

       Intent促进了组件之间的交互,这对于开发者非常重要,而且它还能做为消息的载体,去指导组件做出相应的行为,也就是说Intent可以携带数据,传递给Activity/Service/BroadcastReceiver。

3. Intent类型

在Android中,Intent分为两种类型,显式和隐式。

Intent intent = new Intent(context,XXActivity.class);
startActivity(intent);
Intent intent = new Intent(Intent.ACTION_DIAL);
Uri data = Uri.parse("tel:" + "135xxxxxxxx");
intent.setData(data);
startActivity(intent);

使用显示Intent去启动Activity或者Service的时候,系统将会立即启动Intent对象中指定的组件。

       使用隐式Intent的时候,系统通过将Intent对象中的IntentFilter与组件在AndroidManifest.xml或者代码中动态声明的IntentFilter进行比较,从而找到要启动的相应组件。如果组件的IntentFilter与Intent中的IntentFilter正好匹配,系统就会启动该组件,并把Intent传递给它。如果有多个组件同时匹配到了,系统则会弹出一个选择框,让用户选择使用哪个应用去处理这个Intent,比如有时候点击一个网页链接,会弹出多个应用,让用户选择用哪个浏览器去打开该链接,就是这种情况。

       IntentFilter通常是定义在AndroidManifest.xml文件中,也可以动态设置,通常是用来声明组件想要接受哪种Intent。例如,你如果为一个Activity设置了IntentFilter,你就可以在应用内或者其他应用中,用特定的隐式Intent来启动这个Activity,如果没有为Activity设置IntentFilter,那么你就只能通过显示Intent来启动这个Activity。

注意,为了确保系统的稳定性,官方建议使用显示Intent来启动Service,同时也不要为Service设置IntentFilter,因为如果使用隐式Intent去启动Service,我们并不知道那些服务会响应Intent,而且由于服务大多是不可见的,我们也不知道那些服务被启动了,这是非常危险的。在Android 5.0(API 21)以后,如果使用隐式的Intent去调用bindService()方法,系统会抛出异常。

4. Intent的属性

       Intent作为消息的载体,系统根据它去决定启动哪个具体的组件同时将组件执行中需要的信息传递过去。Intent能够包含的属性有Component、Action、Data、Category、Extras、Flags,关于这些属性的更详细信息可查看这里

以上列出的这些关于Intent的属性(Component、Action、Data、Category)可以帮助系统来确定具体的组件,但是有一些Intent的属性,不会影响到组件的确定。

5. 显式Intent示例

       上文说到,显式Intent是用于启动某个特定的组件(Activity或者Service)的Intent,穿创建显式的Intent的时候需要设置组件名称(Component)属性,其他的属性都是可选属性。

// fileUrl是一个URL字符串,例如 "http://www.example.com/image.png"
Intent downloadIntent = new Intent(context, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

这里的Intent的构造函数传入了两个参数,context和组件名(Component),调用了startService()方法后,会在当前的应用中启动DownloadService这个服务。
显示Intent中设置的组件名(Component)需要在AndroidManifest.xml进行注册,所以它一般用来启动当前应用内的组件。

6. 隐式Intent示例

       隐式Intent比显示的Intent会复杂一些,它既可以启动当前应用内的组件,也可以启动当前应用外的组件。如果当前应用无法处理隐式Intent,但是其他应用中的组件可以处理,那么系统会弹框让用户选择启动哪个应用中的组件。

       例如,如果用户有内容想分享给其他应用,就创建一个Intent,将它的Action属性设置为ACTION_SEND,然后将要分享的内容设置到Extras属性中,然后调用startActivity()方法,用户就可以选择将内容分享到哪一个应用。

注意,如果没有任何应用能处理用户发送的隐形Intent,调用组件失败,应用可能会崩溃。调用resolveActivity()方法可以确认是否有Activity能够处理这个Intent,如果返回为非空,那么至少有一个组件能够处理这个Intent,调用startActivity()就很安全了;如果返回的是空(null),那么说明没有组件能够处理这个Intent,这个时候就不应该使用这个隐式的Intent了。

// 要将textMessage信息分享出去
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// 确认是否有组件能够处理这个隐式Intent
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

       调用startActivity()传入一个隐式Intent时候,系统会检查设备中所有的应用,确定哪些应用可以处理这个隐式的Intent(含有startActivity()操作并携带text/plain类型的Intent),如果只有一个应用可以处理这个Intent,那么直接唤起这个应用,并将Intent传给它;如果有多个应用可以处理这个Intent,那么系统会弹出一个选择框,让用户选择唤起哪个应用。

7. 强制唤起选择框

       上文说了,如果多个应用可以处理同一个隐式Intent,系统会弹出选择框,让用户选择唤起哪个应用,并设置该应用为默认的打开方式,以后就不会弹出选择框了。如果用户希望以后一直使用该用户处理这个隐式Intent(比如打开网页,用户通常会倾向于使用同一个web浏览器),那么十分方便。

       但是如果用户想每一次都用不同的应用去处理这个隐式的Intent的,就应该每次弹出选择框,用户可以在选择框中选择唤起的应用,但是无法设置默认的打开方式。例如,当用户想根据当前的位置将内容分享到不同的应用,所以每次都需要弹出选择框。

用户需要通过Intent.createChooser()创建一个Intent,然后调用startActivity()。

Intent sendIntent = new Intent(Intent.ACTION_SEND);
...

// 分享的标题
String title = getResources().getString(R.string.chooser_title);
// 创建一个调用选择框的Intent
Intent chooser = Intent.createChooser(sendIntent, title);

// 确认是否有应用可以处理这个Intent
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

8. 接受隐式Intent

       想配置你的应用可以处理哪些隐式的Intent,需要在AndroidManifest.xml文件中使用<intent-filter>标签为组件设置一个或者多个过滤器。每一个过滤器基于Action、Data、Category来指定自身可以处理的Intent类型。如果隐式Intent的能够匹配到用户设置的其中一个过滤器,系统才能唤起这个应用相应的组件并将Intent传递给这个组件。

       组件应该为为一个它可以处理的操作单独设置一个处理器。例如,相册中的Activity可能有两个过滤器,一个过滤器对应浏览照片的操作,另一个过滤器对应编辑照片的操作。当这个Activity被启动的时候,根据Intent中携带的信息来决定执行哪种操作。

       每一个过滤器是在AndroidManifest.xml使用<intent-filter>标签来定义的,嵌套在组件标签中,例如<activity>、<service>标签。在<intent-filter>标签中,用户可以使用一下三个属性中的一个或者多个来指定可以接受的Intent。

       Activity组件要接受隐式Intent,它必须有一个<category>属性为CATEGORY_DEFAULT的过滤器,因为startActivity()和startActivityForResult()方法处理Intent时候,默认的认为接受组件有一个<category>属性为CATEGORY_DEFAULT的过滤器。如果一个Activity组件不声明这样一个过滤器,它就接收不到隐式Intent。

例如,以下代码声明了一个Activity组件,这个组件可以处理action属性为ACTION_SEND,数据类型是文本(text/plain)的隐式Intent。

<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>标签的过滤器,创建时候仅需要确定该组件能够处理过滤器定义的操作即可。

如果是根据<action>、<data>、<category>标签的组合来处理多个Intent,那么需要为这个组件声明多个过滤器。

系统会以这三个属性将隐式Intent与所有组件声明的过滤器进行对比,如果这三个属性全部能够匹配上,系统才有可能将这个隐式Intent传递给这个组件,因为如果多个应用的组件都能匹配上会弹出选择框,让用户选择一个应用去处理这个隐式Intent。

为了避免无意中启动了其他的Service,所以在应用内,建议一直使用显示的Intent去启动服务,这样就不必再AndroidManifest.xml文件中为Service声明过滤器了。

对于Activity的过滤器,必须在AndroidManifest.xml文件中声明,也可以不声明,直接使用显示Intent唤起Activity组件。

广播接收器的过滤器声明可以在AndroidManifest.xml文件中声明,也可以使用registerReceiver()方法动态注册,使用完毕后,使用unregisterReceiver()方法动态注销。

9. 过滤器声明示例

下面一些过滤器的声明能够帮助你更好的理解。

<activity android:name="MainActivity">
    <!-- 该Activity是该应用的启动入口页面,它会被储存在系统的launcher列表中 -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">
    <!-- 该Activity能够处理ACTION_SEND行为且数据类型为text/plain的隐式Intent -->
    <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_SEND行为且数据类型是媒体内容的隐式Intent -->
    <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="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

第一个名为MainActivity的组件,是应用的启动入口页面,当用户点击应用图标,该Activity会被启动。

第二个名为ShareActivity的组件,能够处理两种隐式Intent,可以接受文本和媒体内容的分享操作,也就是说如果一个隐式Intent能够匹配到任意一个过滤器都可以唤起该Activity。当然,也可以直接通过显示Intent指定启动它。

9. PendingIntent

PendingIntent是对Intent的一种封装。它主要作用在于,让外部的应用执行内部的Intent时候,就好像是在你的应用中还行一样。

通常在以下场景中会使用PendingIntent。

因为每一个Intent对象都是针对具体的组件类别(Activity/Service/BroadcastReceiver)进行实例化,因此在创建PendingIntent的时候,也要基于相同的因素去实例化,使用以下方法实例化PendingIntent。

当然官方还有一些其他获取PendingIntent对象的方法,不过内部也是使用上面三个方法来获取实例化对象的。

这三个方法都需要当前应用的context,需要封装的Intent,以及一个或者多个该如何使用该Intent的标志(例如,是否可以多次使用该Intent)。

关于Pending的具体使用也不再这里展开,需要了解具体使用的可以查看Notification中PendingIntent的使用悬浮工具栏中PendingIntent的使用

10. Intent匹配规则

       上文中提到了,当发送一个隐式Intent后,系统会将它与设备中的每一个组件的过滤器进行匹配,匹配属性有Action、Category、Data三个,需要这三个属性都匹配成功才能唤起相应的组件。

10.1 Action匹配规则

一个过滤器可以一个Action属性也可以声明多个Action属性。如下:

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

隐式Intent中的Action属性,与组件中的某一个过滤器的Action能够匹配(如果一个过滤器声明了多个Action属性,只需要匹配其中一个就行),那么就算是匹配成功。

如果过滤器没有声明Action属性,那么只有没有设置Action属性的隐式Intent才能匹配成功。

10.2 Category匹配规则

一个过滤器可以不声明Category属性也可以声明多个Category属性,如下:

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

       隐式Intent中声明的Category必须全部能够与某一个过滤器中的Category匹配才算匹配成功。比如说一个Category属性设为CATEGORY_BROWSABLE的隐式Intent也可以通过上面的过滤器,也就是说,过滤器的Category属性内容必须是大于或者等于隐式Intent的Category属性时候,隐式Intent才能匹配成功。

如果一个隐式Intent没有设置Category属性,那么它可以通过任何一个过滤器的Category匹配。

10.3 Data匹配规则

一个过滤器可以不声明Data属性也可以声明多个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四个部分,host和port合起来也成authority(host:port)部分。

<scheme>://<host>:<port>/<path>

例如:

content://192.168.0.1:8080/folder/subfolder/etc

在这个URI中,scheme是content,host是192.168.0.1,port是8080,path是folder/subfolder/etc。我们平时使用的网络url就是这种格式。

在URI中,每个组成部分都是可选的,但是有线性的依赖关系

当进行URI匹配时候,并不是比较全部,而是局部对比,以下是URI匹配规则。

注意:path部分可以使用通配符(*),也就是path其中的一部分进行匹配。

Data匹配时候,MIME类型和URI两者都会进行匹配,匹配规则如下:

注意:进行匹配时候必须以过滤器为单位进行匹配,不能跨过滤器匹配。如果一个过滤器声明了多个Action、Category、Data,隐式Intent包含的Action、Category、Data都能在过滤器中匹配到相应的属性即可,也就是说过滤器中声明的属性是大于或者等于Intent中包含的属性,Intent才能匹配成功

11. 其他

       系统通过过滤器去匹配Intent,启动相应组件,在PackageManager类中提供了一系列的查询(queryIntentActivities()/queryIntentServices()/queryBroadcastReceivers())方法去查询可以处理某个Intent的组件,也提供了一系列的解析(resolveActivity()/resolveService())方法来确定最佳启动组件。这些方法在某些场景下是非常有用的,也可以帮助我们降低程序crash风险。

12. 思考

       上文中对Intent以及IntentFilter进行了详细的讲解,大多都是系统级别的处理过程。但是Intent作为一个官方已经封装好的信息携带者,我们可以用它来做很多事情。比如可以写自己的一套匹配规则,Intent仅仅作为数据携带者,通过它去传递一些信息,实现Fragment/Activity的页面跳转逻辑。关于Intent的使用,我相信还有更多的用处,需要用户一步一步的去探索。

到此这篇关于Android Intent与IntentFilter案例详解的文章就介绍到这了,更多相关Android Intent与IntentFilter内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文