概述

Intent本意为目的、意向、意图。在Android中,Intent是系统各组件(或应用程序)之间进行数据传递的数据附载者,Intent不仅可以用于应用程序之间的交互,也可以用于应用程序内部的activity、service和broadcast receiver之间的交互。
Android的三个基本组件——Activity,Service和Broadcast Receiver都是通过Intent机制启动的,不同类型的组件有不同的传递Intent机制:

  • 启动Activity
    使用Context的startActivity()或Activity的startActivityForRestult()去启动一个Activity或使一个已存在的Activity去做新的事情。
    使用Activity的setRestult()传入一intent来从activity中返回结果。
    具体详见本人之前的翻译:解读Android之Activity(1)基础知识。
  • 启动Service
    使用Context的startService()去初始化一个service或传递消息给正在运行的service。
    使用Context的bindService()去建立调用组件和目标服务之间的连接(即绑定service)。
    具体详见本人之前的翻译:解读Android之Service(1) 和
    解读Android之Service(2)Bound Service

  • 启动broadcast receiver
    将intent对象传递给Context的sendBroadcast()sendOrderedBroadcast()sendStickyBroadcast(),都将发送一个broadcast。

Intent类型

Intent有两种类型:

  • 显式Intent,指定具体将要启动的组件
    指定了component属性的Intent(调用setComponent(ComponentName)或者setClass(Context, Class)来指定)或者直接用构造器Intent(Context,Class)。通过指定具体的组件类,通知应用启动对应的组件。

    通常,在启动我们自己程序中的组件需要使用显示Intent对象,这样能够减少Intent对象解析过程,提高性能。关于解析后续介绍。

  • 隐式Intent,即通过Intent Filter过滤匹配
    没有指定comonent属性的Intent。这些Intent需要包含其它足够的信息,这样系统才能根据这些信息,在所有的可用组件中,确定满足此Intent的组件。

当创建显示intent启动activity或service时,系统将立刻启动指定activity或service。

当创建隐式intent时,系统通过manifest文件中的intent filters寻找合适的组件来启动。若有多个组件满足intent的条件,则系统会显示一个对话框呈现这些满足条件的组件让用户自己选择。

Intent Filter描述了一个组件愿意接收什么样的Intent对象,Android将其抽象为android.content.IntentFilter类。在Android的AndroidManifest.xml配置文件中的<activity>标签中可以通过<intent-filter>标签为一个Activity指定其Intent Filter,以便告诉系统该Activity 可以响应什么类型的Intent对象。若组件在manifest中没有设置<intent-filter>,则该组件只能通过显示intent启动。

注意:为了确保我们的应用程序的安全性,不声明service的intent filters,当启动Service时,总是使用显示intent。因为若我们 使用隐式intent启动service的话会造成安全隐患,我们不能确定哪个service去响应intent,用户也无法看到。从android5.0(API21)开始,若要通过隐式intent绑定一个service的话,系统会抛出异常。
解读Android之Intents和Intent Filters_第1张图片
上图描述了隐式intent如何启动另一个activity:

  1. activity A创建一个带有action的intent,然后传递给startActivity()
  2. 系统寻找所有程序的intent filters是否匹配该intent。
  3. 当发现一个匹配时,系统启动该匹配的activity B,并将intent传递给B。

创建Intent对象

Intent对象携带必要的信息,使得系统能够使用这些信息去启动相应的组件。

intent中主要的信息包括:

Component name

组件名字是可选的,指定Intent对象的目标组件的类名称(包括包名)。指定component的话,将直接使用并启动指定的组件,Intent的其它所有属性都是可选的。此为显式Intent,即直接启动指定的组件。如果设置了,intent对象传递到指定类的实例;如果没有设置,Android使用intent中的其它属性来定位合适的目标组件。

组件的名字通过setComponent()setClass()setClassName()或Intent构造器设置,通过getComponent()读取。其中,setComponent()接收一个ComponentName类实例,ComponentName构造器源码如下:

/** * @param pkg 包名,不能为null * @param cls 类名,不能为null */ public ComponentName(String pkg, String cls) {    if (pkg == null) throw new NullPointerException("package name is null");    if (cls == null) throw new NullPointerException("class name is null");    mPackage = pkg;    mClass = cls;  }/** * @param pkg Context对象 * @param cls 类名,不能为null */public ComponentName(Context pkg, String cls) {    if (cls == null) throw new NullPointerException("class name is null");    mPackage = pkg.getPackageName();    mClass = cls;}/** * @param pkg Context对象 * @param cls 类 */    public ComponentName(Context pkg, Class<?> cls) {        mPackage = pkg.getPackageName();        mClass = cls.getName();    }

其实,这三种构造器本质都一样。

行为Action

String字符串,表示intent的动作。例如日常生活中描述一个意愿时经常带有某种动作,比如我想看爱情动作片,这里看就是一个动作。在Intent中,Action就是描述intent的动作。当指明一个action时,目标组件就会依照这个动作的指示,表现对应的行为。在Intent中,定义了很多动作,如ACTION_VIEW等等,基本上涵盖了常用的动作。下面介绍两个:

  • ACTION_VIEW
    当要通过activity(startActivity()启动的)显示信息给用户时可以设置该动作,例如:查看图片,查看地图等。
  • ACTION_SEND
    这种也可以称为shareintent,当要通过activity(startActivity()启动的)和其它应用程序共享数据时可以使用该动作,例如email程序和社交程序等。

一个intent Filter可以包含多个Action。但是每个Intent只能设置一个动作,通过setAction()或Intent构造器设置动作。

除了使用系统提供的动作,我们还可以自定义动作。若要自定义动作的话,必须要保证该动作是以包名为前缀的,例如:

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

动作很大程度上决定了剩下的intent如何构建,特别是数据(data和type)和类别(category)属性,就像一个方法名决定了参数和返回值。正是这个原因,应该尽可能明确指定动作,并紧密关联到其它Intent属性。换句话说,应该定义你的组件能够处理的Intent对象的整个协议,而不仅仅是单独地定义一个动作。

数据Data

data表示动作要操作的数据,更精确地指定当前活动能够响应什么类型的数据。data属性的声明中要指定访问数据的Uri和MIME类型(如text/html,text/xml,image/jpg等)。
关于更多MIME类型的介绍请参考之前翻译文章:MIME类型。

当创建intent时,在URI中添加MIME类型是非常重要的。例如一个显示图片的activity无法播放视频文件,但是它们的URI格式却差不多。因此,指定数据的MIME类型能够帮助系统选择最佳的组件响应intent。在许多情况下,数据类型能够从URI中推测,特别是content:URIs,它表示位于设备上的数据且被内容提供者(content provider)控制。

只设置数据URI的话,可以使用setData()(设置这个会将MIME类型设置为null),只数据MIME类型的话,可以调用setType()(设置这个会将数据设置为null),若两个都设置的话,可以调用setDataAndType()

类别category

字符串,用于表现动作的类别,相当于把动作分类(如增删改(三个动作)一篇论文,这三个动作都属于编辑类别),Intent和Intent Filter中都可以添加多个。系统提供了很多类别,如CATEGORY_BROWSABLE(允许使用浏览器打开activity),CATEGORY_LAUNCHER(系统应用程序启动入口activity)等,我们也可以自定义类别,方法和自定义Action一样。在Intent中可以通过setCategory()添加。
注:

  • 必须和动作一块设置,否则报错。
  • 若当前组件是Activity则,必须在intent-filter标签中再增加一个添加默认的类别:
    <category android:name="android.intent.category.DEFAULT"/>。PS:多说一句看准IDE自动补全的是不是android.intent.category.DEFAULT,提示NO Activity。我在这纠结了半天。
  • 在主Activity中设置android.intent.action.MAINandroid.intent.category.LAUNCHER,它们分别标记活动开始新的任务和带到启动列表界面。该Activity可以包含android.intent.category.DEFAULT,也可以不包含。

通过上面这四个属性,系统就能够知道应该由哪个组件响应intent。但是,除了上面的几个属性,intent还可以携带不会影响系统响应组件的其它信息。如下:

Extras

附加信息,一般用来携带一些数据信息。通过额外的键值对(key-value)把数据保存在Intent对象中。系统提供了一些额外信息的key如:EXTRA_EMAILEXTRA_SUBJECT等,当然我们也可以添加自己的额外信息。

Intent对象有一系列的putExtra(String,XXX)方法用于插入各种附加数据和一系列的getXXXExtra()用于读取数据。这些方法与Bundle对象的方法类似,实际上,附加信息可以作为一个Bundle使用putBundleExtras()和getBundleExtras()添加和读取。

intent指定许多EXTRA_*常量用于标准化的数据类型,若我们需要声明自己的额外信息的key,需要确保该key要包含程序包名,如:

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

关于更多Extras,请参考: Activity之间通信。

Flags

期望意图的运行模式。Intent类定义了各种各样的标志,许多指示Android系统如何去启动一个活动(例如,活动应该属于那个任务)和启动之后如何对待它(例如,它是否属于最近的活动列表)。

这一部分的详细信息,请参考我之前的翻译:解读Android之任务和Back栈。

显式Intent例子

通过component属性设置intent对象为显示启动方法,其它intent属性为可选项。

代码如下:

/** * 使用component属性设置Intent对象,启动一个Activity * */Intent intent = new Intent();//构造器有多种,也可以为new ComponentName(this,MainActivity2.class);//同样也有启动其它包中activity的构造器ComponentName componentName = new ComponentName(this,"com.sywyg.intent_test.MainActivity2");intent.setComponent(componentName);startActivity(intent);

需要注意的是,如果我们在Intent中指定了component属性,系统将不会再对action、data/type、category进行匹配。

隐式Intent例子

不是通过component属性方式启动的intent都是隐式启动,因此需要指定Action/Category/Data/Type信息。

注意:隐式intent方式启动的acitivity可能不存在,因此为避免程序无法响应intent而造成崩溃,可以先通过resolveActivity()判断是否有满足的组件,如下:

// Create the text message with a stringIntent sendIntent = new Intent();sendIntent.setAction(Intent.ACTION_SEND);sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type// Verify that the intent will resolve to an activityif (sendIntent.resolveActivity(getPackageManager()) != null) {    startActivity(sendIntent);}

注意:这个例子中,没有使用Data,但是声明了intent数据类型,用于指定extras携带的内容。

当调用startActivity()时,系统检测所有安装的程序确定哪些程序能够处理这种intent对象(带有ACTION_SENDtext/plain)。若只有一个程序满足条件的话,则该程序立刻响应;若多个响应的话,由用户选择响应哪个。

强制使用程序选择器

当有多个程序能够响应隐式intent时,用户可以选择使用哪个,也能够使那个程序成为默认的选择。

然而,用户可能每次都有不同的选择,我们可以使用一个对话框的形式显示给用户,让用户自己选择。

为了显示选择器对话框,可以使用createChooser(),然后传递给startActivity(),如下:

Intent sendIntent = new Intent(Intent.ACTION_SEND);// 创建选择器对话框Intent chooser = Intent.createChooser(sendIntent, "选择器名称");// Verify the original intent will resolve to at least one activityif (sendIntent.resolveActivity(getPackageManager()) != null) {    startActivity(chooser);}

上述方法将显示一个带有满足条件的程序列表对话框,并且使用”选择器名称”作为对话框标题。

接收隐式intent

为了声明我们的activity可以响应哪些隐式intent,我们应该在manifest文件中,在<activity>标签或其它组件标签的子标签中设置<intent-filter>标签,可以设置intent的action,data和category。只有满足条件的intent filters才能响应。而对于显示intent对象则不需要intent filters。

一个组件应该为每个独立的任务设置不同的intent filters。我们可以为每个intent filter设置如下标签:

  • <action>
    使用name属性声明intent行为。该值必须是字面字符串(literal string),不能是类常量。
  • <data>
    使用一个或多个属性声明数据和MIME类型。
  • <category>
    使用name属性声明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>

我们可以在一个intent filter中创建多个<action><data><category>。若设置了多个,则表明该组件能够处理这些标签的任何组合。

当我们想要处理多个intents时,但是只有特定的action/data/category组合,因此我们需要创建多个intent filters。

隐式intent需要通过intent filter检测这三个标签(action/data/category)是否匹配。为了能够将intent传递给该组件,intent必须能够同时通过这三种检测。若有一个检测失败的话,则系统不会将intent传递给该组件。然而,若一个组件有多个intent filters的话,则只要intent能通过这些filters中的一个检测,该intent就会传递给该组件。详细请参考下面的Intent解析部分。

注意:为了避免不经意间运行其它程序的Service,我们总是使用显示Intent启动我们自己的Service,而不应在service中声明intent filters。

对于所有的activities,我们必须声明intent filters。然而,对于broadcast receivers能够通过registerReceiver()被动态地注册,然后通过unregisterReceiver()解除。这样做的好处是,我们能够使程序只在指定的时间内响应broadcasts。

限制获得组件

使用intent filter不能够阻止其他程序启动我们的组件。另一个程序能够通过满足intent filter隐式启动intent,和当知道我们的组件类名时通过显示启动intent。若我们不想让其他应用程序启动我们的组件时,可以在<activity>中设置exported属性为false阻止外界启动我们的组件。

filters例子

代码如下:

<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>

第一个activity,MainActivity,是入口activity——当用户使用启动图标初始化启动程序时该activity将被启动:

  • ACTION_MAIN行为表名这是程序入口,并且不希望接收任何intent数据。
  • CATEGORY_LAUNCHER类别表名该activity图标将替换程序的启动图标,也就是首先启动该activity。若<activity没有指定图标的话,则程序使用<application>标签的图标。

这两个必须成对出现作为程序的入口。

使用延迟Intent

PendingIntent对象是一个Intent对象的包装类。PendingIntent主要目的是为了保障外部程序使用intent,好像是我们自己的程序进程中执行。

pending intent主要使用场景如下:

  • 当用户执行Notification时(系统的NotificationManager执行intent),声明intent被执行。
  • 当用户执行APP控件时(Home屏幕执行intent),声明intent被执行。
  • 在指定的未来时间执行该intent(例如系统的AlarmManager执行该intent)。

因为每个intent对象被设计成被指定的组件(Activity,Service,BroadcastReceiver)处理。同样,PendingIntent也需要这样的考虑。当使用pending intent时,我们不能再使用类似startActivity()执行intent,而是使用下列方法:

  • PendingIntent.getActivity()启动Activity。
  • PendingIntent.getService()启动Service。
  • PendingIntent.getBroadcast()启动BroadcastReceiver。

除非我们的应用程序需要pending intent,上面创建PendingIntent的方法是我们可能需要的唯一创建PendingIntent方法。

上面的每个方法参数为:当前程序的Context,Intent,flags。

intent解析

当系统接收到隐式intent要启动activity时,系统会基于下面三个方面比较接收到的intent和系统中的intent filters,然后选择最合适的activity响应:

  • intent行为
  • intent数据(URI和数据类型)
  • intent类别

当我们使用startActivity()startActivityForRestult()来启动另外一个Activity时,如果直接指定了Intent对象的Component属性(多种方式,下面有详细介绍),那么系统将启动其Component属性指定的Activity。否则系统将通过Intent的其它属性从安装在系统中的所有Activity中查找与之最匹配的一个启动(或当优先级相同时通过列表让用户自己选择,intent filter优先级设置:使用android:priority属性设置(-1000到1000,必须有个负值才生效)。),如果没有找到合适的Activity,应用程序会得到一个系统抛出的异常。这个匹配的过程如下:

解读Android之Intents和Intent Filters_第2张图片

注:

  • URI数据即为属性data,此处意思表示必须匹配data或type,若同时出现则必须两者都匹配。
  • 三种检测:动作检测(Action),种类检测(Category),数据检测(包括data和type)。

下面我们根据intent filter在manifest文件中的声明来描述intent如何匹配合适的组件。

行为检测

为了指定能接受的intent行为,intent filter可以声明0个或多个行为,如下:

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

为了能够匹配该filter,指定的intent必须要匹配上述行为中的一个。若filter不包含任何行为,则没有intent能够匹配该filter。但是若intent不指定行为的话,该intent能够通过检测(只要filter至少包含一个行为)。

类别检测

为了指定能接受的intent类别,intent filter可以声明0个或多个类别,如下:

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

为了能够匹配该filter,指定的intent必须要匹配上述类别中的一个。反过来则没必要,即filter中指定的类别可能多余intent中设置的类别,但是只要filter中包含所有intent设置的类别的话,该intent就能够通过类别检测。特殊情况,当intent不包含类别时,该intent肯定能通过类别检测。

注:

  • 必须和动作一块设置,否则报错。
  • 若当前组件是Activity则,必须在intent-filter标签中再增加一个添加默认的类别:
    PS:多说一句看准IDE自动补全的是不是android.intent.category.DEFAULT,提示NO Activity,我在这纠结了半天。
  • 在主Activity中设置android.intent.action.MAINandroid.intent.category.LAUNCHER,它们分别标记活动开始新的任务和带到启动列表界面。该Activity可以包含android.intent.category.DEFAULT,也可以不包含。

数据检测

为了指定能接受的intent数据,intent filter可以声明0个或多个数据,如下:

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

每个数据能够指定URI和数据类型(MIME类型)。URI由多个属性构成,格式如下:

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

例如:

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

而在标签中的中有如下主要属性:

  • android:scheme
    指定数据的协议部分,如http、https、tel…,还可以定义自己的前缀
  • android:host
    用于指定数据的主机名部分,如www.google.com,如果定义为*则表示任意主机名
  • android:port
    用于指定数据的端口部分,一般紧随在主机名之后。
  • android:path
    用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
  • android:mimeType
    用于指定可以处理的数据类型,允许使用通配符(模糊说明,如audio/*)的方式指定。

host和port一起构成URI的权限(authority),如果host没有指定,port也被忽略。这四个属性都是可选的,但它们之间并不都是完全独立的。要让authority有意义,scheme必须也要指定。要让path有意义,scheme和authority也都必须要指定。即:

  • 若scheme没有指定,则host可以省略;
  • 若host没有指定,则port可以省略;
  • 若host和port都没有指定,则path可以省略。

当比较intent的URI和filter的URI时,仅仅比较filter中出现的URI属性。例如:

  • 如果一个filter仅指定了scheme,所有与此scheme的URIs都匹配过滤器;
  • 如果一个filter指定了scheme和authority(host和port),但没有指定path,所有匹配scheme和authority的URIs都通过检测,而不管paths;
  • 如果四个属性都指定了,必须都匹配才能通过检测。

注意:filter的path指定可以包含通配符(*)来要求匹配path中的一部分。

数据检测既要检测URI,也要检测MIME类型。规则如下:

  • 若intent既不包含URI,也不包含MIME类型,则仅当filter也不指定任何URIs和MIME类型时,才能通过检测。
  • 若intent包含URI,但不包含MIME类型(既不是显示指定也不能通过URI获得),则仅当它们的URI匹配,同时filter也不指定MIME类型,才能通过检测。
  • 若intent包含MIME类型,但不包含URI,则仅当filter也包含相同的MIME类型,同时不包含URI数据,才通过检测。
  • 若intent既包含URI,也包含MIME类型(显示指定或从URI推断),则只有与filter中MIME类型的一个匹配才能通过MIME类型匹配。URI检测若想通过则需满足下列情况之一:
    1. URI要匹配filter中的URI;
    2. URI包含content:file:,同时filter没有指定URI。换句话说,若filter仅包含MIME类型,则对应的组件假定支持content:file:

若不指定URI的话,则组件假定支持content:file:。这说明该组件期望从文件或content provider中获取本地数据,例如:

<intent-filter>    <data android:mimeType="image/*" />    ...</intent-filter>

上面的内容表示:组件能够从content provider中获取图片信息。

同时,当我们设置scheme和数据类别时,系统就能够知道如何获取数据,而不用再指定其它的数据属性,例如:

<intent-filter>    <data android:scheme="http" android:type="video/*" />    ...</intent-filter>

上面内容表示:组件从网络中获取视频数据。

intent匹配

intent和intent filters匹配不仅寻找处于激活状态的组件,也会寻找设备上的其它组件。PackageManager提供了一系列的query...()能够返回可以接受指定intent的所有组件(但是不激活组件),resolve...()方法能够确定最佳的组件。

更多相关文章

  1. android入门教程(十六)之-- 使用Intent传递数据
  2. Android中的数据绑定框架DataBinding(对比AngularJS双向数据绑定
  3. Android数据存储方式
  4. Android简单数据存储类SharedPreferences详解及实例(通过“记住密

随机推荐

  1. 自己动手做android热更新框架
  2. android 下载文件(支持多任务,支持断点..
  3. 2012-7-18 Android(安卓)的Paint(画笔)及Ca
  4. Android(安卓)Training - 支持不同的语言
  5. android init.rc语法标准 .
  6. 8款开源的Android(安卓)游戏引擎
  7. Android的xml-Rpc实现
  8. 致Android开发者的Kotlin入门
  9. Android中 AsyncTask和Handler对比(特别有
  10. Intent的简介以及属性详解