三、Intent 和 Intent过滤器(IntentFilter)

Intent(意图):

    Intent 是一个消息传递对象,是我们要执行操作的一个抽象描述。我们可以使用它在相应的组件中传递消息和请求。下面是它的主要使用场景:

    1. 启动Activity

    通过 startActivity() 或者 startActivityForResult() 。参数 intent 描述了要启动的 activity 以及可以携带希望传递给新 activity 相关的数据。

public void startActivity(Intent intent) {
    ...
}

public void startActivityForResult(Intent intent, int requestCode) {
    startActivityForResult(intent, requestCode, null);
}

    2. 启动服务

    通过 startService() bindService() 分别启动和绑定 Service。同理,参数 Intent 描述了要启动的服务并可以携带我们想要传递的数据。

public ComponentName startService(Intent service) {
    ...
}

public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    ...
}

    3. 传递广播

    通过 sendBroadcast() 等发送广播。

 

Intent 类型:

    Intent 分为两种类型:

    a. 显式 Intent:

    通过明确指定被启动对象的组件信息(包括包名和类名)显式的指定要启动的组件。通常,我们会在自己的应用中使用显式 Intent 来启动组件,这是因为我们知道要启动的 Activity 或 Service 的详细包名和类名。例如,我们使用显式 Intent 启动新的Activity 以及启动 Service 在后台下载文件。

    b. 隐式 Intent:

    不明确指定要启动的组件,而是声明要执行的常规操作,通过系统来选择并允许其它应用中的组件处理它。例如,如果我们需要在地图上向用户显示位置,则可以使用隐式 Intent,请求手机中具有此功能的应用在地图上显示指定的位置。

    当我们使用显式 Intent 启动 Activity 或 Service 时,系统将立即启动 Intent 对象中指定的应用组件;当我们使用隐式 Intent 时,Android 系统会通过将隐式 Intent 中的内容与在设备上其它应用的清单文件(AndroidManifest.xml)中声明的 Intent 过滤器(IntentFilter)进行比较,从而找到要启动的相应组件。如果只有一个 IntentFilter 与之相匹配,则系统直接启动该组件,如果有多个 IntentFilter 与之相匹配,则系统会显示一个对话框,然后由用户来选择要启动的组件。

    IntentFilter 是应用清单文件中的一个表达式,它可以指定该组件要接收的 Intent 类型。如下所示:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

    我们可以通过为 Activity 声明 IntentFilter,让其它应用能够直接通过特定类型的 Intent 来启动该 Activity;如果我们没有为 Activity 声明任何的 IntentFilter 过滤器,则该 Activity 只能通过显式 Intent 来启动。

    上面这幅图为隐式 Intent 如何通过 Android System 传递以启动其它 Activity 的图。分为以下三步:

  1. 1. Activity A 创建包含操作描述的 Intent,并将其传递给 startActivity()。

  2. 2. Android System 搜索所有应用中与该 Intent 匹配的 IntentFilter。

  3. 3. 找到匹配项之后,Android System 通过调用匹配的 Activity(Activity B)的 onCreate() 方法,并将 Intent 作为其参数传递进去,从而启动了该 Activity。

    注意:为了确保应用的安全性,我们在启动 Service 时要使用显式 Intent,并且不要为 Service 声明 IntentFilter。因为我们使用隐式 Intent 来启动 Service 时,无法确定设备上的哪些 Service 将匹配该 Intent,并且我们无法看到哪些 Service 已启动,所以会存在安全隐患。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),系统会引发异常。

 

构建 Intent:

    Intent 对象携带了 Android 系统用来确定要启动哪个组件的信息(例如组件的名称等)以及可能想要传递给组件的相关数据。

    组件名称(Component):

    要启动的组件名称。这是可选项,但也是构建显式 Intent 的一项重要信息,表示要启动特定的组件。如果没有为 Intent 明确指定组件名称,则 Intent 是隐式的,Android 系统会根据 Intent 相关的其它信息(比如操作(action)、数据(data)、类别(category))决定哪个组件应该接收 Intent。因此,如果我们需要在应用中启动特定的组件(也就是使用显式 Intent),则应指定该组件的名称。

    注意:启动 Service 时,您应始终使用显式 Intent 方式指定好要启动的 Service 名称。否则,我们无法确定哪项服务会响应 Intent,且用户也无法看到哪项 Service 已经启动。

    Intent 的 Component 属性接受一个 ComponentName 对象。使用如下:

    先看一下 ComponentName 对象的构造方法:

// 创建 pkg 所在包下的 cls 类所对应的组件
ComponentName(String pkg, String cls)
// 创建上下文 pkg 所对应的包下所对应的组件
ComponentName(Context pkg, String cls)
// 创建上下文 pkg 所对应的包下的 cls 所对应的组件
ComponentName(Context pkg, Class<?> cls)
// 显式 Intent 的用法
Intent intent = new Intent();

//  方法 1
intent.setComponent(new ComponentName("com.cfm.intenttest", "com.cfm.intenttest.SecondActivity"));

// 方法 2
intent.setComponent(new ComponentName(FirstActivity.this, "com.cfm.intenttest.SecondActivity"));

// 方法 3
intent.setComponent(new ComponentName(FirstActivity.this, SecondActivity.class));

Log.d("cfm", intent.getComponent().getPackageName()); // output: cfm: com.cfm.intenttest
Log.d("cfm", intent.getComponent().getClassName());   // output: com.cfm.intenttest.SecondActivity
Log.d("cfm", intent.getComponent().getShortClassName()); // output: .SecondActivity

// 方法 4
intent.setClass(FirstActivity.this, SecondActivity.class);

// 方法 5
intent.setClassName("com.cfm.intenttest", "com.cfm.intenttest.SecondActivity");

// 方法 6
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);

    操作(Action):

    指定要执行的通用操作的字符串。它在很大程度上决定了其余 Intent 的构成,特别是数据和 extra 中包含的内容。

    通常我们都使用 Intent 类或者其它框架定义的操作常量,当然,我们也可以自定义指定自己的操作,以满足相应的 Intent 要求。下面是常见的用于启动 Activity 的操作:

    ACTION_VIEW:

    用于可向用户显示信息的 Activity。

    ACTION_SEND:

    用于可共享数据的 Activity。

    在使用中,我们可以通过 setAction() 或 Intent 构造函数为 Intent 指定操作。

eg:
Intent intent1 = new Intent(Intent.ACTION_SEND);

Intent intent2 = new Intent();
intent2.setAction(Intent.ACTION_VIEW);

    注意:一般当我们自定义操作时,通常使用该应用的包名作为前缀:

static final String ACTION_TEST = "com.cfm.intenttest.TEST";

    数据(Data):

    data 的语法如下所示:

<data
    android:scheme="string"
    android:host="string"
    android:port="string"
    android:path="string"
    android:pathPattern="string"
    android:pathPrefix="string"
    android:mimeType="string"/>

    data 由两部分组成,mimeType URI。mimeType 指媒体类型,比如 image/jpeg、audio/mpeg4-generic 和 video/* 等,可以表示图片、文本、视频等不同的媒体格式,而 URI 中包含的数据就比较多了,下面是 URI 的结构:

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

eg:

content://com.cfm.test:200/folder/subfolder/etc
http://www.baidu.com:80/search/info

    具体参数分析:

    schemeURI 的模式,比如 http、file、content 等,如果 URI 中没有指定 scheme,那么整个 URI 的其它参数无效,这也意味着 URI 是无效的。

    hostURI 的主机名,比如 www.baidu.com,如果 host 未指定,那么整个 URI 的其它参数也是无效的,这也意味着 URI 是无效的。

    port URI 中的端口号。比如 80,仅当 URI 中指定了 scheme 和 host 参数的时候 port 参数才是有意义的。

    path 表示完整的路径信息。

    pathPattern 也表示完整的路径信息,但是它里面可以包含通配符“*”,“*”表示 0 个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“*”要写成“\\*”,“\”要写成“\\\\”。

    pathPrefix 表示路径的前缀信息。

    一般情况下,提供的数据类型通常是由 Intent 的操作决定。比如:如果操作时 ACTION_EDIT,则数据应包含待编辑文档的 URI。

    在指定数据类型的时候,除了指定 URI 之外,指定 mimeType 往往也很重要。比如,能够显示图像的 Activity 可能无法播放音频格式的文件。因此在我们指定了媒体类型之后,有助于 Android System 找到匹配 Intent 的最佳组件。但有时,mimeType 可以通过 URI 推断出来,比如当 URI 为 content 时,表明数据位于设备中,且由 ContentProvider 控制。

    如果我们仅设置数据 URI 时,可以调用 setData()。如果仅设置 mimeType,则调用 setType()。如果同时设置 URI 和 mimeType,则调用 setDataAndType(),而不能同时调用前面两个方法,具体原因可以看下面源码,因为 setData() 和 setType() 可以相互抵消彼此的值。

public Intent setData(Uri data) {
    mData = data;
    mType = null; // 将 mimeType 置为 null
    return this;
}

public Intent setType(String type) {
    mData = null; // 将 URI 置为 null
    mType = type; 
    return this;
}

    类别(Category):

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

    CATEGORY_BROWSABLE:

    目标 Activity 允许通过网络浏览器启动,以显示链接引用的数据,如图像或电子邮件。

    CATEGORY_LAUCHER:

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

    我们可以使用 addCategory() 指定类别。

    上面列出的这些属性(Component、Action、Data、Category)表示 Intent 的既定特征。通过读取这些属性,Android System 能够解析应当启动哪个应用组件。但是,Intent 还可以携带不影响其如何解析为应用组件的信息,如下所示:

    Extra:

    通过键值对的方式,携带相应的数据信息。

    标志:

    在 Intent 类中定义的、充当 Intent 元数据的标志。标志可以指示 Android System 如何启动 Activity(比如 Activity 应属于哪个任务栈等),以及启动之后如何处理。(具体请结合 Activity 启动模式那篇最后的分析)

 

隐式 Intent 的使用:

    通过隐式 Intent 我们可以调用设备上其它能够响应该 Intent 的 Activity。如果我们的应用无法执行该操作而其它应用可以,且您希望用户选取要使用的引用,则使用隐式 Intent 非常有用。

    注意,当设备上没有任何应用能处理我们发送到 startActivity() 的隐式 Intent 时,应用将会崩溃,所以我们在使用隐式 Intent 之前通常会先判断设备上有没有能够响应该 Intent 的 Activity。

eg:

// 创建一个隐式 Intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, textMessage);
intent.setType("text/plain");

// 先检查有没有能够响应该 intent 的 Activity,然后再调用 startActivity()
if( intent.resolveActivity(getPackageManger()) != null ){
    startActivity(intent);
}

    这时,如果只有一个应用能够处理该 Intent,则应用将直接被打开;但是如果多个 Activity 能接受 Intent,则系统将显示一个对话框,使用户能够自己选取要使用的应用。

    我们也可以强制调用应用选择器:

    使用 createChooser() 创建 Intent,并将其传递给 startActivity()。

Intent intent = new Intent(Intent.ACTION_SEND);

// 应用选择器的标题
String title = getResources().getString(R.string.chooser_title);

// 创建一个应用选择器
Intent chooser = Intent.createChooser(intent, title);

if( intent.resolveActivity(getPackageManager()) != null ){
    startActivity(chooser);
}

 

接收隐式 Intent:

    我们可以在 AndroidManifest.xml 文件中使用 <intent-filter> 元素为每个应用组件声明一个或多个 Intent 过滤器。每个 Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接收的 Intent 类型。仅当隐式 Intent 可以匹配 Intent 过滤器的其中一个时,系统才会将该 Intent 传递给应用组件。

    注意:只要明确指定了 Component,按照显式 Intent 启动应用组件,则不需要匹配该应用组件声明的 Intent 过滤器。

eg:
<activity android:name="TestActivity">
    <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,必须将 CATEGORY_DEFAULT 类别包括在 Intent 过滤器中。因为 startActivity() 和 startActivityForResult() 会自动为其 Intent 参数增加 CATEGORY_DEFAULT 类别。如果未在 Intent 过滤器中声明此类别,则隐式 Intent 不会匹配我们的应用组件。

 

限制对组件的访问:

    当我们使用 Intent 过滤器时,无法安全的防止其它应用启动组件。虽然 Intent 过滤器可以将组件限制为仅响应特定类型的隐式 Intent,但是如果开发者知道该组件的具体名称,则依然有可能通过显式 Intent 启动它。所以,为了确保该组件只能该应用可以启动,我们可以将该组件的 exported 属性设置为 false。

    注:对于 Activity,我们只能在清单文件中声明 Intent 过滤器。但是 BroadcastReceiver 的过滤器可以通过调用 registerReceievr() 动态注册,然后通过 unregisterReceiver() 注销该接收器。这样一来,应用便可以仅在应用运行时的某一指定时间段内侦听特定的广播。

 

Intent 过滤器匹配规则:

    1. action 的匹配规则

    action 是一个字符串,系统预定义了一些 action,同时我们也可以在应用中自定义 action。action 的匹配规则是 Intent 中的 action 必须能和过滤器规则中的 action 之一完全匹配(action 区分大小写,所以要一模一样才能匹配成功)。一个 IntentFilter 中可以有多个 action,只要 Intent 中的 action 能够和过滤器规则中的任何一个 action 相同即可匹配成功。需要注意的是,Intent 中如果没有指定 action,那么匹配失败。综上,action 的匹配规则要求 Intent 中的 action 存在且必须和过滤规则中的其中一个 action 完全相同,才算是匹配成功。

    2. category 的匹配规则

    category 也是一个字符串,系统预定义了一些 category,同时我们也可以在应用中自定义 category。category 的匹配规则是如果 Intent 中含有 category,那么必须和过滤器规则中的 category 完全匹配才可以;如果 Intent 中不含有 category,那么过滤器规则中必须包含“android.intent.category.DEFAULT”才能匹配成功,因为当我们使用 startActivity() 或者 startActivityForResult() 时会默认为 Intent 加上 “android.intent.category.DEFAULT”这个 category。(这里要注意区分和 action 的匹配规则不一样)

    3. data 的匹配规则

    data 的匹配规则和 action 的类似,它也要求 Intent 中必须含有 data 数据,并且 data 数据能够完全匹配过滤规则中的某一个 data(同时匹配 URI 和 mimeType)。如果过滤规则中没有指定 URI,默认值为 content 或者 file,所以即使 IntentFilter 中没有指定 data 数据,我们也必须为隐式 Intent 调用 setData( Uri.parse("file://abc") ) 或者 setData( Uri.parse("content://abc") ) 才能匹配。

 

最后补充两个知识点:

    1. 判断设备中是否有 Activity 能够匹配我们的隐式 Intent。有两种方法,一种是前面使用过的通过 Intent 的 resolveActivity() 方法;另一种是通过 PackageManager 的 resolveActivity() 方法。另外 PackageManager 中还提供了 queryIntentActivitys(),它和 resolveActivity() 的区别是后者返回的是最佳匹配的 Activity 信息,而前者返回的是所有成功匹配的 Activity 信息。

// 原型
public abstract ResolveInfo resolveActivity(Intent intent, int flags);
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags);
// 第一个参数是传入的隐式 Intent。

// 第二个参数是附加的标志信息。我们常常使用 MATCH_DEFAULT_ONLY。这个标志信息的含义是仅仅匹配那些在 intent-filter 中声明了 <category android:name="android.intent.category.DEFAULT"/> 这个类别的 activity。因为在我们使用 startActivity() 或者 startActivityForResult() 是会默认为 intent 添加这个类别,所以一定程度上就可以减少由于缺少这个类别而导致的启动 activity 异常。

     2. 

<action android:name="android .intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>

    ACTION_MAIN:操作指示这是主要入口点,且不要求输入任何 intent 数据。

    CATEGORY_LAUNCHER:类别指示此 Activity 的图标应放入系统的应用启动器。如果 <activity> 元素中未使用 icon 指定图标,则系统将使用 <application> 元素中的图标。

    这二者匹配使用用来表示这是一个入口 Activity 并且会出现在系统的应用列表中。少了任何一个都没有实际意义,也无法出现在系统的应用列表中,也就是二者缺一不可。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值