一、Intent
1、什么是Intent?
在Android中Intent机制是用来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。
2、Intent与组件
(1)Intent启动Activity:Activity可以简单的理解为手机屏幕中的一个页面,你可以通过将Intent传入startActivity方法来启动一个Activity的实例,也就是一个页面,同时,Intent也可以携带数据,传递给新的Activity。如果想要获取新建的Activity执行结果,可以通过onActivityResult()方法去启动Activity。
(2)Intent启动Service:Service是一个不呈现交互画面的后台执行操作组件,可以通过将Intent穿入startService()方法来启动一个Service来启动服务。
(3)Intent传递广播BroadCast:广播是任何应用都可以接收到的消息,通过将Intent传递给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast()方法,可以将广播传递接收方。
3、Intent属性
(1)Action,使用android:name特性来指定对响应的动作名。动作名必须是独一无二的字符串,所以,一个好的习惯是使用基于Java包的命名方式的命名系统。
onstant | Target component | Action |
---|---|---|
ACTION_CALL | activity | 拨打电话 |
ACTION_EDIT | activity | 显示用户要编辑的数据 |
ACTION_MAIN | activity | 作为任务的初始活动启动,没有数据输入,也没有返回的输出 |
ACTION_SYNC | activity | 将服务器上的数据与移动设备上的数据同步 |
ACTION_BATTERY_LOW | broadcast receiver | 电池电量低的警告 |
ACTION_HEADSET_PLUG | broadcast receiver | 耳机已插入或拔出设备 |
ACTION_SCREEN_ON | broadcast receiver | 屏幕已打开 |
ACTION_TIMEZONE_CHANGED | broadcast receiver | 时区设置已更改 |
也可以自定义动作(自定义的动作在使用时,需要加上包名作为前缀,如"com.example.project.SHOW_COLOR”),并可定义相应的Activity来处理我们的自定义动作。
(2)Data,也就是执行动作要操作的数据
Android中采用指向数据的一个URI来表示,如在联系人应用中,一个指向某联系人的URI可能为:content://contacts/1。对于不同的动作,其URI数据的类型是不同的(可以设置type属性指定特定类型数据),如ACTION_EDIT指定Data为文件URI,打电话为tel:URI,访问网络为http:URI,而由content provider提供的数据则为content: URIs。
(3)type(数据类型),显式指定Intent的数据类型(MIME)。一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。
(4)category(类别),被执行动作的附加信息。例如 LAUNCHER_CATEGORY 表示Intent 的接受者应该在Launcher中作为顶级应用出现;而ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一个,这些动作可以在同一块数据上执行。还有其他的为
Constant | Meaning |
---|---|
CATEGORY_BROWSABLE | 浏览器可以安全地调用目标活动,以显示链接引用的数据,例如图像或电子邮件。 |
CATEGORY_GADGET | 该活动可以嵌入托管小工具的另一个活动中。 |
CATEGORY_HOME | 该活动显示主屏幕,即当设备打开或按下home键时用户看到的第一个屏幕。 |
CATEGORY_LAUNCHER | 该活动可以是任务的初始活动,并列在顶级应用程序启动器中。 |
CATEGORY_PREFERENCE | 目标活动是首选项面板。 |
(5)component(组件),指定Intent的的目标组件的类名称。通常 Android会根据Intent 中包含的其它属性的信息,比如action、data/type、category进行查找,最终找到一个与之匹配的目标组件。但是,如果 component这个属性有指定的话,将直接使用它指定的组件,而不再执行上述查找过程。指定了这个属性以后,Intent的其它所有属性都是可选的。
(6)extras(附加信息),是其它所有附加信息的集合。使用extras可以为组件提供扩展信息,比如,如果要执行“发送电子邮件”这个动作,可以将电子邮件的标题、正文等保存在extras里,传给电子邮件发送组件。
4、Intent类型
在Android中,Intent分为两种类型:显式和隐式。
(1)显式Intent,可以通过类名来找到相应的组件,在应用中用显式Intent去启动一个组件,通常是因为我们知道这个组件(Activity或者Service)的名字。如下代码,我们知道具体的Activity的名字,要启动一个新的Activity,下面就是用的显示Intent:
Intent intent = new Intent(context,XXActivity.class);
startActivity(intent);
直接指明了接收者:XXActivity.class
(2)隐式Intent,不指定具体的组件,但是它会声明将要执行的操作,从而匹配到相应的组件。有利于降低发送者和接收者之间的耦合,它一般用在没有明确指出目标组件名称的前提下,一般是用于在不同应用程序之间,下面就是用的隐式Intent:
Intent intent = new Intent();
intent.setAction("com.google.test");
startActivity(intent);
没有指明接收者, 只是给了一个action作为接收者的过滤条件。
对于显式Intent,Android不需要去做解析,因为目标组件已经很明确,Android需要解析的是那些隐式Intent,通过解析,将Intent映射给可以处理此Intent的Activity、IntentReceiver或Service。
使用隐式Intent的时候,系统通过将Intent对象中的IntentFilter与组件在AndroidManifest.xml或者代码中动态声明的IntentFilter进行比较,从而找到要启动的相应组件。如果组件的IntentFilter与Intent中的IntentFilter正好匹配,系统就会启动该组件,并把Intent传递给它。如果有多个组件同时匹配到了,系统则会弹出一个选择框,让用户选择使用哪个应用去处理这个Intent,比如有时候点击一个网页链接,会弹出多个应用,让用户选择用哪个浏览器去打开该链接。
注意,为了确保系统的稳定性,官方建议使用显示Intent来启动Service,同时也不要为Service设置IntentFilter,因为如果使用隐式Intent去启动Service,我们并不知道那些服务会响应Intent,而且由于服务大多是不可见的,我们也不知道那些服务被启动了,这是非常危险的。在Android 5.0(API 21)以后,如果使用隐式的Intent去调用bindService()方法,系统会抛出异常。
5、IntentFilter
(1)IntentFilter通常是定义在AndroidManifest.xml文件中,也可以动态设置,通常是用来声明组件想要接受哪种Intent。例如,你如果为一个Activity设置了IntentFilter,你就可以在应用内或者其他应用中,用特定的隐式Intent来启动这个Activity,如果没有为Activity设置IntentFilter,那么你就只能通过显示Intent来启动这个Activity。
(2)Intent解析机制主要是通过查找已注册在AndroidManifest.xml中的所有IntentFilter及其中定义的Intent,最终找到匹配的Intent。在这个解析过程中,Android是通过Intent的action、type、category这三个属性来进行判断的,判断方法如下:
-
如果Intent指明定了action,则目标组件的IntentFilter的action列表中就必须包含有这个action,否则不能匹配;
-
如果Intent没有提供type,系统将从data中得到数据类型。和action一样,目标组件的数据类型列表中必须包含Intent的数据类型,否则不能匹配。
-
如果Intent中的数据不是content: 类型的URI,而且Intent也没有明确指定它的type,将根据Intent中数据的scheme (比如 http: 或者[mailto] 进行匹配。同上,Intent 的scheme必须出现在目标组件的scheme列表中。
-
如果Intent指定了一个或多个category,这些类别必须全部出现在组建的类别列表中。比如Intent中包含了两个类别:LAUNCHER_CATEGORY 和 ALTERNATIVE_CATEGORY,解析得到的目标组件必须至少包含这两个类别。
(3)关于IntentFilter的几点注意事项:
android.intent.action.MAIN 与 android.intent.category.LAUNCHER
android.intent.action.MAIN决定一个应用程序最先启动那个组件
android.intent.category.LAUNCHER决定应用程序是否显示在程序列表里(说白了就是是否在桌面上显示一个图标)
这两个属性组合情况:
第一种情况:有MAIN,无LAUNCHER,程序列表中无图标
原因:android.intent.category.LAUNCHER决定应用程序是否显示在程序列表里 。
第二种情况:无MAIN,有LAUNCHER,程序列表中无图标
原因:android.intent.action.MAIN决定应用程序最先启动的Activity,如果没有Main,则不知启动哪个Activity,故也不会有图标出现,所以这两个属性一般成对出现;
如果一个应用中有两个组件intent-filter都添加了android.intent.action.MAIN和android.intent.category.LAUNCHER这两个属性, 则这个应用将会显示两个图标, 写在前面的组件先运行。
6、显式Intent示例
(1)无参数Activity跳转
Intent it = new Intent(Activity.Main.this, Activity2.class);
startActivity(it);
(2)向下一个Activity传递数据(使用Bundle和Intent.putExtras)
Intent intent = new Intent(Activity.Main.this, Activity2.class);
Bundle bundle=new Bundle();
bundle.putString("name", "This is from MainActivity!");
intent.putExtras(bundle); // intent.putExtra(“test”, "shuju”);
startActivity(intent); //
startActivityForResult(intent,REQUEST_CODE);
对于数据的获取可以采用:
Bundle bundle=getIntent().getExtras();
String name=bundle.getString("name");
(3)向上一个Activity返回结果(使用setResult,针对
startActivityForResult(it,REQUEST_CODE)启动的Activity)
Intent intent=getIntent();
Bundle bundle2=new Bundle();
bundle2.putString("name", "This is from ShowMsg!");
intent.putExtras(bundle2);
setResult(RESULT_OK, intent);
(4)回调上一个Activity的结果处理函数(onActivityResult)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
if (requestCode==REQUEST_CODE){
if(resultCode==RESULT_CANCELED)
setTitle("cancle");
else if (resultCode==RESULT_OK) {
String temp=null;
Bundle bundle=data.getExtras();
if(bundle!=null) temp=bundle.getString("name");
setTitle(temp);
}
}
}
7、隐式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,那么系统会弹出一个选择框,让用户选择唤起哪个应用。
8. 强制唤起选择框
上文说了,如果多个应用可以处理同一个隐式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);
}
9、接受隐式Intent
想配置你的应用可以处理哪些隐式的Intent,需要在AndroidManifest.xml文件中使用<intent-filter>标签为组件设置一个或者多个过滤器。每一个过滤器基于Action、Data、Category来指定自身可以处理的Intent类型。如果隐式Intent的能够匹配到用户设置的其中一个过滤器,系统才能唤起这个应用相应的组件并将Intent传递给这个组件。
组件应该为为一个它可以处理的操作单独设置一个处理器。例如,相册中的Activity可能有两个过滤器,一个过滤器对应浏览照片的操作,另一个过滤器对应编辑照片的操作。当这个Activity被启动的时候,根据Intent中携带的信息来决定执行哪种操作。
每一个过滤器是在AndroidManifest.xml使用<intent-filter>标签来定义的,嵌套在组件标签中,例如<activity>、<service>标签。在<intent-filter>标签中,用户可以使用一下三个属性中的一个或者多个来指定可以接受的Intent。
- <action>,在这个属性中,声明该组件可以执行的操作。该值必须是关于操作的一个字符串,并不是类常量
- <data>,使用一个或者多个数据URI(scheme、host、port、path等等)和数据的MIME类型来指定接受的数据类型
- <category>,声明接受的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()方法动态注销。
关于intent-filter匹配优先级
首先查看Intent的过滤器(intent-filter),按照以下优先关系查找:action->data->category
10、 过滤器声明示例
<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会被启动。
android.intent.action.MAIN,表示该Activity是应用的启动入口,且不需要任何Intent携带的数据。
android.intent.category.LAUNCHER,表示将该Activity的图标设为手机主屏幕上的应用图标,如果它没有图标,就用Application的图标。
第二个名为ShareActivity的组件,能够处理两种隐式Intent,可以接受文本和媒体内容的分享操作,也就是说如果一个隐式Intent能够匹配到任意一个过滤器都可以唤起该Activity。当然,也可以直接通过显示Intent指定启动它。
11、Intent与Intent-Filter的匹配规则
上文中提到了,当发送一个隐式Intent后,系统会将它与设备中的每一个组件的过滤器进行匹配,匹配属性有Action、Category、Data三个,需要这三个属性都匹配成功才能唤起相应的组件,匹配规则详细如下:
-
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才能匹配成功。
-
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匹配。
-
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中,每个组成部分都是可选的,但是有线性的依赖关系
-
如果没有scheme部分,那么host部分会被忽略
-
如果没有host部分,那么port部分会被忽略
-
如果host部分和port部分都没有,那么path部分会被忽略
当进行URI匹配时候,并不是比较全部,而是局部对比,以下是URI匹配规则。 -
如果一个URI仅声明了scheme部分,那么所有拥有与其相同的scheme的URI都会通过匹配,其他部分不做匹配
-
如果一个URI声明了scheme部分和authority部分,那么拥有与其相同scheme和authority的URI才能匹配成功,path部分不做匹配
-
如果一个URI所有的部分都声明了,那么只有所有部分都相同的URI才能匹配成功。
注意:path部分可以使用通配符(*),也就是path其中的一部分进行匹配。
Data匹配时候,MIME类型和URI两者都会进行匹配,匹配规则如下:
- 如果过滤器未声明URI和MIME类型,则只有不含URI和MIME类型的隐形Intent才能匹配成功
- 如果过滤器中声明URI但是未声明MIME类型(也不能从URI中分析出MIME类型),则只有URI与过滤器URI相同且不包含IME类型的隐式Intent才能匹配成功
- 如果过滤器声明MIME类型但是未声明URI,只有包含相同MIME类型但是不包含URI的隐式Intent才能匹配成功
- 如果过滤器声明了URI和MIME类型(既可以是直接设置,也可以是从URI分析出来),只有包含相同的URI和MIME类型的隐式Intent才能匹配成功
注意:进行匹配时候必须以过滤器为单位进行匹配,不能跨过滤器匹配。如果一个过滤器声明了多个Action、Category、Data,隐式Intent包含的Action、Category、Data都能在过滤器中匹配到相应的属性即可,也就是说过滤器中声明的属性是大于或者等于Intent中包含的属性,Intent才能匹配成功
其他
系统通过过滤器去匹配Intent,启动相应组件,在PackageManager类中提供了一系列的查询(queryIntentActivities()/queryIntentServices()/queryBroadcastReceivers())方法去查询可以处理某个Intent的组件,也提供了一系列的解析(resolveActivity()/resolveService())方法来确定最佳启动组件。这些方法在某些场景下是非常有用的,也可以帮助我们降低程序crash风险。
参考:https://blog.csdn.net/xiaohanluo/article/details/52637520
网友评论