Intent和IntentFilter
Intent是一个信息载体,是组件间的一个桥梁。
Intent三种常用用法:
1.打开Activity
使用Context. startActivity 打开一个Activity,Intent中含有描述Activity的描述和数据。从一个ActivityA跳转到另一个ActivityB并且想要从B返回一些数据给A,此时可以使用Context.startActivityForResult 。当B finish之前可以使用setResult设置数据,当返回到A时并回调onActivityResult (int requestCode, int resultCode, Intent data)时便可以从data中获取B返回的数据。
2.开启Service
可以使用Context.startService打开一个Service,或者使用Context.bindService绑定一个Service。
3.发送广播
使用sendBroadcast, sendOrderedBroadcast或者 sendStickyBroadcast()发送一个广播。
Intent类型
Intent可以分为显式Intent和隐式Intent。
1.显式Intent
指定具体将要打开的Activity或者Service,一般用于自己APP之间的跳转和逻辑处理。如一个邮件应用从邮件列表界面跳转到阅读邮件界面,开启同步数据Service同步数据等。
2.隐式Intent
与显式Intent不同,隐式Intent不指定具体的组件,只是描述了一个操作(打算干什么)。如打算打开一个地图应用、拍照等。隐式Intent多于打开另一个应用的组件。
当使用显示Intent打开Activity或者Service时,系统会马上打开相应的组件。当使用隐式Intent打开一个组件时,Android系统会用Intent中所包含的内容与设备中所有的应用组件的IntentFilter(在清单文件中声明)去匹配。系统挑选一个最合适组件打开;如果有多个组件可以匹配,会弹出一个对话框让用户选择打开哪个组件。
此图为使用隐式Intent打开Activity的过程图。
1.ActivityA创建一个隐式Intent
2.系统查找可以处理此Intent的Activity
3.系统打开对应的ActivityB
IntentFilter就是在应用清单文件中声明的指定某个组件可以接收此类型Intent的一个表达式。例如给一个Activity声明一个IntentFilter,其他应用便可以使用某种Intent打开此Activity。如果不给Activity声明IntentFilter,就无法使用隐式Intent打开此Activity。
注意:从安全方面考虑,请不要使用隐式Intent打开Service ,也不要给你的Service声明IntentFilter。使用隐式Intent打卡Service是不安全的,因为用户看不到打开的Service也不知道给用户相应的Service是哪一个。从Android5.0(API 21)开始,如果使用隐式Intent bindService时会抛异常。
创建Intent
Intent可以包含动作、数据、组件名字、组件分类等信息,Android系统会根据这些信息打开相应的组件。
主要信息有:
1.组件名(将要打开组件的名字)
这是一个可选信息。如果包含组件名此Intent是一个显示Intent,不包含则为隐式Intent。如果你打算打开一个特定的组件,你就必须指定组件名。使用隐式Intent打开组件,系统会根据Intent的动作、数据、分类打开。当开始服务时,你通常需要指定组件的名字。因为使用隐式Intent打开Service,你不知道你打开的哪个Service用户也看不到。
此信息保存在Intent的一个ComponentName类型的域中,你可以指定将要打开组件的包名和类名。可以使用setComponent()、setClass()、setClassName(),或者使用Intent构造方法指定。
2.Action(动作)
指定要执行动作的一个字符串(例如查看、编辑等)。
如果是一个广播Intent的action,action表达的就是正在发生的和广播(如Intent.ACTION_BOOT_COMPLETED的意思就是系统已经重启完成)。action在很大程度上绝定了Intent的结构,如携带什么数据、什么类型。
你可以自己编写action供自己的或者其他APP使用,通常是使用Intent和framework中提供的那些action。
几种常用的action:
ACTION_VIEW
当有信息显示给用户时使用此action,例如显示一个图片、显示一个位置。
ACTION_SEND
当有数据需要分享时使用,例如通过邮件、信息分享图片。
Intent类中和framework中还有很多action,请自己查阅。
可以使用setAction()或者使用Intent的构造方法设置action。
如果你需要定义自己的action时,你最好加上自己应用的包名,例如static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"。
3.Data(数据)
Data包括数据(如引用数据的URI)和类型(MIME)。提供的数据类型通常取决于Intent的action。例如,一个ACTION_EDIT action,数据通常会包含将要被编辑文档的URI。
创建Intent时除了包含URI之外,通常会包含数据类型(MIME)。例如,一个可以展示图片不能播放声音文件Activity,如果不传类型很可能会导致错误(由于URI格式都类似,无法区别)。所以指定类型能更好地帮助Android系统找到最合适的组件。然而有些时候从URI中便可以识别出数据类型(MIME),例如一个content:开头的URI说明此文件是由ContentProvider控制的本地文件,此种数据的MIME系统是知道的。
给Intent只设置数据时请用setData(),如果只设置类型(MIME)时请用setType()。如果都设置时必须使用setDataAndType(),因为setData()和setType()会相互清空对方的属性(setData时会将MIME清掉,setType会将数据清掉)。
4. Category(类型)
可以携带额外的信息,指明哪种类型的组件可以处理此Intent。一个Intent可以包含多种类型,但是通常情况下并不需要类型。
几种常用类型:
CATEGORY_BROWSABLE
可以被浏览器安全打开的Activity,显示链接所引用的数据,例如显示一张图片、一封邮件信息。
CATEGORY_LAUNCHER
表示Activity的icon(如果没有就用应用icon)可以显示在桌面上(为什么用可以是因为可以disable掉此Activity)。
可以使用addCategory()给Intent添加类型。
以上罗列的属性(组件名、动作、数据【URI和MIME】、类型)是描述Intent特性的属性。通过读取这些属性Android系统便可以决定打开哪个组件。除了这些属性,还有一些不影响绝定组件的属性。
5.Extras
为了完成某些需求,Intent可能会携带一些额外的信息(以键值对形式存储)。就像某类型的action需要特定类型的数据URI一样,某种类型action需要一些额外的一些信息。你可以使用有多个重载方法的putExtra()添加额外信息,或者使用putExtras()将信息放到一个Bundle对象中。例如当你创建一个发送邮件的Intent时,使用EXTRA_EMAIL关键字设置收件人,用EXTRA_SUBJECT关键字设置邮件主题。
在Intent类中还有一些EXTRA_*标准的数据类型常用供我们使用。当我们需要自己定义key值时,最好加上报名。如static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";。
6.Flags(标识)
在Intent类型定义的Flag是Intent的元数据。Flag可以指示Android系统以怎样的方式启动一个Activity(例如此Activity归属哪个任务栈),以怎样的方式对待已启动的Activity(此Activity是否会出现的最近Activity中)。可以查看setFlag()的官方文档了解更多Flag知识。
显式Intent示例
显式Intent就是指定了具体组件的Intent,例如指定了应用中一个具体的Activity或者Service。创建一个显式Intent时组件名是必须指定的,除组件名之外其他信息都是可有可无的(根据需求)。
现在有一个可以从网络下载文件名为DownloadService的Service,你可以使用如下方式开启DownloadService。
// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as //"http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
隐式Intent示例
一个隐式Intent指定了一个动作(action),可以调用设备上所有可以完成此动作(action)的APP。当你打算完成一个你的应用不能完成而其他应用可能可以完成的动作时,使用隐式Intent是非常有用的,并且可以让用户选择使用哪个APP完成此动作。例如,当你有内容需要跟其他人分享,你便可以发起一个ACTION_SEND动作并且携带将要分享内容的隐式Intent。当你用此Intent调用startActivity()时,用户便可以选择一个APP去分享内容。注意:当你使用隐式Intent开启一个Activity时,可能没有可以完成此action的Activity。当出现这种情况,开启会失败并且会crash。可以使用Intent.resolveActivity ()检查是否有Activity处理此action。Intent.resolveActivity ()返回结果非空时,说明至少有一个Activity可以处理此Intent;如果返回结果为空,说明没有Activity处理此Intent,此时需要根据产品需求处理此情况。
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
一旦调用startActivity(),Android系统会在全设备上查找可以处理此Intent的应用(Activity)。如果只有一个应用可以处理此Intent,系统便会打开应用;如果有多个应用可以处理此Intent,系统会弹出一个对话框让用户选择使用哪个应用处理此Intent。
强制弹出选择器
当设备上有多个应用可以处理一个隐式Intent,用户可以选择某应用使用并且可以将此应用设置为处理此action的默认应用。使用同一个应用操作同一个action(已设置默认)是非常合理的,例如拍照用某第三方拍照APP(非系统内置)。然而在某些情况下,处理同一个action(对此action不能设置默认)时(每一次)用户都想用不同的APP,此时就需要弹出选择对话框。例如用户会根据需要选择不同的应用去分享一个内容,选择QQ或者微信什么的。
使用Intent.createChooser()创建Intent。
Intent sendIntent = new Intent(Intent.ACTION_SEND);
...
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);
// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
发起Intent后会弹出一列可以处理此Intent的应用供用户选择。
接收隐式Intent
通过给应用组件(在manifest文件中)添加一个或多个<intent-filter>元素声明你的应用可以接收哪些隐式Intent。根据Intent的action、data、category每一个IntentFilter可以声明自己可以接收哪类Intent。当有一个IntentFilter匹配上,系统才会将隐式Intent传递给你的应用组件。注意:显式Intent不会走匹配流程,直接传递给目标组件。
应用组件应该为不同的工作提供不通的IntentFilter(过滤器)。例如一个图片应用(如美图秀秀)的Activity有两个过滤器,一个显示图片过滤器,一个编辑图片过滤器。然后根据Intent中的信息表现不同的行为(是否显示编辑控制器)。
过滤器使用<intent-filter> 标签定义在清单文件的组件下。<intent-filter> 可以使用下面的一个或者多个元素来声明自己可以接收的Intent。
<action>
在name属性中声明可以接收的Intent动作(action)。value值必须是文字字符串非Java类中定义的常量。
<data>
用一个或者多个属性(scheme, host, port, path)指定数据URI和MIME声明可以接收的数据类型。
<category>
在name属性中声明可以接收的Intent类型。value值必须是文字字符串非Java类中定义的常量。
为了接收隐式Intent,在过滤器中必须包含CATEGORY_DEFAULT(android.intent.category.DEFAULT)类型。如果不包含此类型,你的应用接收不到任何的隐式Intent。
下面是一个可以接收ACTION_SEND(android.intent.action.SEND)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>。如果需要处理多种类型的Intent,需要定义多个过滤器。
Android系统根据action、data、category决定一个组件的过滤器是否可以接收某Intent,当且仅当三个全部匹配才算匹配成功。即使有一个不匹配,Android系统也不会将此隐式Intent传递给该组件。当然一个组件可以配置多个过滤器,只要与一个过滤器匹配便会将Intent传递给它。
注意:使用过滤器来无法阻止其他应用调用你的组件,如果打算禁止其实APP调用你的组件请将对应组件的标签exported 属性设置为false。对于所有的Activity,你必须在清单文件中申明你的过滤器。对于BroadcastReceiver你可以根据需求使用registerReceiver()动态注册,使用unregisterReceiver()注销。
过滤器示例:
<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>
MainActivity是应用的主入口,此Activity的icon将显示在桌面应用。
ACTION_MAIN(android.intent.action.MAIN)声明这是程序的主入口并不期望任何数据。CATEGORY_LAUNCHER(android.intent.category.LAUNCHER)说明你的Activity图标将显示在桌面应用,如果Activity没有指定图标则使用应用图标(<applica>中指定的)。
使用PengdingIntent
一个PendingIntent包裹了一个Intent对象。PendingIntent的主要作用是允许其他应用使用被包裹的Intent(你的应用提供),(执行Intent)是在你的应用进程中被执行。
主要使用场景:
通知栏通知
APP widget
AlarmManager
由于每一个Intent对象被设计用来处理一类特定的应用组件(Activity、Service、BroadcastReceiver)(例如某“类型”的专供Activity服务),所以在创建Intent时要考虑这点。PendingIntent.getActivity()返回一个可以打开一个Activity的Intent;PendingIntent.getService()返回一个一个可以开启服务的Intent;PendingIntent.getBroadcast()返回一个可以开启BroadcastReceiver的Intent。如果你的应用不需要接受来自其他的应用的PendingIntent,这三个方法就可满足日常开发。
Intent解析
当系统收到一个打开一个Activity的隐式Intent时,系统会根据action、data(URI和MIME)、category三方面来匹配Intent和IntentFilter为此Intent找到一个最合适的Activity。
action匹配
<action>指定可以接收的action,一个过滤器中一个包含0至多个<action>。例如:
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.VIEW" />
...
</intent-filter>
Intent中至少有一个与过滤器中的action相匹配,则通过action匹配。如果过滤器中没有action,则所有的Intent均与此过滤器不匹配;如果Intent中没有action,过滤中只要有action则均通过action匹配。
<category>指定可接收的分类,一个过滤器中一个包含0至多个<category>。例如:
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
...
</intent-filter>
Intent中的每一个category都与过滤器中的相匹配则通过category匹配,反过来不是必须的(如Intent中包含c1或者c2,过滤器为c1、c2;此Intent便可以通过category匹配),过滤器中的category数量可以比Intent中包含的多。所以没有包含category的Intent而过滤器有category的均可以通过category匹配。
注意:Android系统会自动为传递给startActivity()和startActivityForResult()的隐式Intent添加“android.intent.category.DEFAULT”category。所以如果你的Activity打算接收隐式Intent,所以你必须要为此Activity添加一个过滤器并添加android.intent.category.DEFAULT分类。
Data匹配
<data>指定可接收的数据,一个过滤器中一个包含0至多个<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。
结构:<scheme>://<host>:<port>/<path>
例子:content://com.example.project:200/folder/subfolder/etc
在<data>元素中的每一个属性都是可选的,但是有线性相关性:
1.如果scheme没有指定,host则被忽略。
2.如果host没有指定,port则被忽略。
3.如果scheme和host都没有指定,则path被忽略。
当Intent的URI和过滤器中的规则相匹配时,值匹配Intent中包含的部分.
1.如果过滤器中只包含scheme,则所有与scheme相同的URI均通过此过滤器。
2.如果过滤器包含scheme和host没有包含path,则所有与scheme和host相同的URI均可通过此过滤器,无视path。
3.如果过滤器中包含scheme、host、path,则只有同时满足scheme、host、path的URI才能通过此过滤器。
注意:path路径可能会包含*通配符,(检查path匹配时)只需要path的部分匹配即可。
Data匹配包括数据URI和MIME匹配,规则:
1.没有URI和MIME的Intent可以通过没有指定URI和MIME的过滤器。
2.包含URI没有MIME的Intent可以通过URI匹配没有指定MIME的过滤器。
3.没有包含URI但是有MIME的Intent可以通过没有URI并且列有相同MIME的过滤器。
4.一个包含URI和MIME的Intent只要MIME与过滤器列中的type相匹配便可以通过MIME部分的检测。如果URI匹配通过或者URI以content:或file开头并且过滤器没有指定URI则通过URI部分的检测。换句话说,如果一个组件的过滤器只指定了MIME,则推定此组件支持content:和file:数据。
从规则4中可以反映出,组件可以从文件或者ContentProvider中获取本地数据。因此组件的过滤器只是指定了MIME并没有明确说明是file:还是content: scheme。下面是一个可以从ContentProvider获取图片展示组件的过滤器例子:
<intent-filter>
<data android:mimeType="image/*" />
...
</intent-filter>
由于大部分的数据由ContentProvider分配,所以只指定数据不指定scheme的过滤器可能比较常见。另一种常见的过滤器是指定scheme和类型,下面是一个可以播放网络视频组件的过滤器例子:
<intent-filter>
<data android:scheme="http" android:type="video/*" />
...
</intent-filter>
Intent匹配
Intent与IntentFilter匹配不仅是找到并激活一个最合适的组件,而且还可以从设备上找到一系列的组件。例如桌面应用会将所有action为ACTION_MAIN (“android.intent.action.MAIN”)category为CATEGORY_LAUNCHER(“android.intent.category.LAUNCHER”)组件找到并且将其组件icon罗列在的应用中。你的应用可能会以类似的方式使用Intent匹配。在android.content.pm.PackageManager类中有根据Intent返回所有组件的一系列的query...()方法,还有根据Intent决定哪个组件处理此Intent的一些resolve...()方法。