一个Intent是一个消息对象,可以用来请求其余的组件。虽然intent以多种方式促进组件之间的通信,但有三个基本用例:
启动一个activity
一个activity表示你APP的一个界面。你可以启动一个新的activity的实例,通过给startActivity传递一个Intent。这个Intent描叙了要启动的activity并携带了需要的参数。
如果你想在activity结束的时候接受到一个结果,调用startActivityForResult。你的activity会在onActivityResult回调方法中接受到一个单独的Intent对象作为结果。启动一个服务
一个service是一个组件,它没有界面,在后台执行一个操作。你可以启动一个service来执行一个操作(例如下载一个文件),通过给startService传递一个intent,这个intent描叙了要启动的service且包含了需要的参数。
如果service被设计成一个CS接口,你可以通过给bindService传递一个intent来从别的组件绑定该service。发送一个广播
一个广播是任何APP都可以接收到的信息。系统会发送许多系统事件的广播,例如系统启动的时候或者开始充电的时候。你可以通过传送一个intent给sendBroadcast,sendOrderedBroadcast或者sendStickBroadcast来给其余APP发送一个广播。
Intent类型
有两种类型的intent
显式intent
显示intent指定了要启动的组件的名字(完整的类名)。你一般用显式intent在你的APP中启动组件,因为你知道你要启动的activity或者service的名字。例如,启动一个activity来回应用户的操作或者启动一个service在后台下载一个文件。
隐式intent
隐式intent没有命名特定的组件,取而代之的是声明了要执行的行为,这允许别的APP的组件去处理它。例如,你想在地图上显示用户的位置,你可以用隐式的intent来请求其他能做这项工作的APP来在地图上显示一个特定的位置。
当你创建一个显式的intent来启动一个service或者activity的时候,系统会立刻启动intent中指定的组件。
当你创建一个隐式的intent的时候,安卓系统通过比较该intent的内容和在这个设备上其余APP在manifest文件中声明的intent filters来找到合适的组件来启动。如果该intent和一个intent filter匹配,系统就会启动这个组件并给这个组件传入该intent。如果有多个组件可以启动,那么系统会显示一个弹窗让用户选择要启动的APP。
intent filter是app的manifest中的表达式,它指定了该组件能接受的intent的类型。例如,通过给一个activity声明intent filter,你就可以让别的APP通过指定的intent来启动它。反之,如果你没有给activity声明任何的intent filter,那么它只能被一个显式的intent启动。
注意:为了你APP的安全,你应该总是使用显式的intent来启动一个service且不给你的service声明intent filter。使用隐式的intent来启动service是不安全的,因为你不能确定哪个service会对intent做出反应,用户也不能看到哪个service启动了。从Android5.0(API 21)开始,当你用一个隐式的intent调用bindService的时候会抛出一个异常。
构造一个Intent
Intent携带Android系统用来决定启动哪个组件(例如应该接受这个intent的组件的完整的名字或者category)的信息,和大量接收端组件可以用来正确的执行操作的信息。
下面是一个intent包含的重要信息:
组件名字
要启动的组件的名字。
这是可选的,但是要创建一个显式的intent这是至关重要的信息,意味着该intent只能传送给组件名字定义的组件。没有组件名字的intent是隐式的,系统会根据其余的信息来决定哪个组件应该接受该intent。所以如果你要启动一个你app里的指定的组件,你应该指定组件名字。
注意:你总是应该用显式的intent来启动service。 否则,你不确定哪个service会对intent做出反应且用户看不见哪个service启动了。
Intent的这个字段是一个ComponentName对象,你可以使用目标组件的全限定类名称(包括应用程序的包名称)指定它。例如,com.example.ExampleActivity
。你可以通过setComponet,setClass,setClassName或者intent的构造方法来设置组件名字。-
Action
一个字符串指定了要执行的通用操作(例如view或者pick)。
在广播intent的情况下,这是发生了并正在报告的行为。 该操作很大程度上决定了intent的其余部分是如何构建的 - 特别是数据和附加内容中包含的内容。
你可以定义自己的action,以供自己的APP的intent使用(或者给其他的APP使用来调用你APP中的组件),但是你通常应该使用定义在intent或者其余框架类里面的action。以下是一些启动activity的常用action:ACTION_VIEW
当你有一些信息要通过一个activity展示给用户的时候,在startActivity的intent中使用这个action。例如一个要展示在相册APP的图片或者一个要展示在地图APP的地址。ACTION_SEND
也被称为share intent,当你有一些用户可以通过其他APP(例如邮件APP或者社交APP)分享的数据的时候,你应该在startActivity的intent使用这个action。
查看Intent类的索引来获取更多的通用action常量。其余的action定义在别的Android框架类中,例如在Settings中有action可以在系统设置APP中打开指定的界面。
你可以通过setAction或者构造方法来指定一个action。
如果你定义了自己的action,请确保包含了你APP的包名作为前缀。例如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"
;
Data
一个URI,引用了要执行的数据或者数据的MIME类型。提供的data类型通常由intent的action决定。例如,如果action是ACTION_EDIT,那么data应该包含要编辑的文件的URI。
在创建一个intent的时候,除了声明data的URI,声明data的类型(MIME类型)也是非常重要的。例如,一个能显示图片的activity可能不能播放音频,即使这两个URI的格式可能一样。所以声明data的MIME类型可以帮助Android系统找到能接受你intent的最好的组件。然而,MIME类型有时可以从URI推断出来-特别是当data是一个content:URI的时候,这意味着该数据是位于本设备且被ContentProvider控制的,这让data的MIME类型是对系统可见的。
仅要设置data的URI的时候,调用setData方法。仅要设置MIME类型的时候,调用setType方法。如果需要的话,可以用setDataAndType来设置它们两。
注意:如果你要设置URI和MIME类型,不要调用setData和setType,因为它们各自取消了对方的值。应该使用setDataAndType来设置URI和MIME类型。-
Category
一个字符串,包含了应该处理该intent的组件的类型的额外信息。任何数量的category描述都可以放在一个intent中,但是大多数intent不需要category。下面是一些常用的category:- CATEGORY_BROWSABLE
目标activity允许自己由Web浏览器启动以显示链接引用的数据 - CATEGORY_LAUNCHER
该activity是任务的初始activity,并列在系统的应用程序启动器中。
在Intent类中查看所有category的描述。
你可以用addCategory来指定一个category。
上面列出的属性(组件名字,action,data,category)表示intent的定义属性。通过读取这些属性,Android系统会决定启动哪个组件。
然而,一个intent还可以携带一些不影响决定启动哪个组件的额外的信息。
- CATEGORY_BROWSABLE
Extras
以键值对的形式来携带一些完成请求行为所需的额外的信息,就像一些行为使用特定种类的数据URI一样,有些行为需要用到extras。
你可以用大量的putExtra方法来添加额外的信息,每次接受两个参数:键的名字和值。你也可以创建一个携带所有额外信息的Bundle对象,然后用putExtras将它插入intent。
例如,当创建一个intent来发送邮件的时候(ACTION_SEND
),可以用EXTRA_EMAIL
键来指定收件人,用EXTRA_SUBJECT
键来指定主题。
Intent类中给标准化数据类声明了很多EXTRA_*
常量。如果你想声明自己的键,确保包含你自己APP包名作为前缀。例如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
Flags
Flags在Intent中的定义是作为intent的元数据。flags可以命令Android系统如何启动一个activity(例如,这个activity属于哪个task),如何对待一个已经启动过的activity(例如,它是否出现在最近任务列表里)。
显式intent的例子
你用一个显式的intent来启动一个特定的组件,例如一个你APP里面特定service或者activity。要创建一个显式的intent,要给intent定义组件的名字-其余的intent属性都是可选的。
例如,你在你的APP中构建了一个名为DownLoadService的service来从网上下载文件,你可以用下面的代码启动它:
// 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(Context,Class)构造方法提供了APP的context和组件的class对象。这样,这个intent就能在你的APP中明确的启动DownLoadService。
隐式intent的例子
隐式的intent声明了一个action,可以调用这个设备上其他能够执行该action的APP。当你的APP不能执行一个action但是其他APP可能可以执行的时候,使用隐式intent是有用的,你可以让用户选择使用哪个APP。
例如,你有一些内容想让用户分享给其他人,你可以用ACTION_SEND
action来创建一个intent并且添加extras来指定要分享的内容。当你用该intent来调用startActivity的时候,用户会选择一个APP来分享内容。
注意:用户可能没有能够处理你发送给startActivity的隐式intent。如果这种情况发生,这个调用就会失败且你的APP会崩溃。为了验证有activity可以接受该intent,在你的intent对象上调用resolveIntent方法。如果结果不为空,那么至少有一个activity可以处理你的intent,这时候调用startActivity是安全的。如果结果为空,你不应该使用这个intent,如果可能的话,你应该禁用发出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的时候,系统会检查所有已安装的APP来决定哪些可以处理这种intent(一个有ACTION_SEND
行为且携带了“text/plain”
数据的intent)。如果只有一个APP可以处理,那么会立刻打开这个APP并将intent传入。如果有多个APP可以接受这个intent,系统会展示一个弹窗来让用户选择使用按个APP。
强制使用APP选择器
当有多个APP可以对你的隐式intent做出反应的时候,用户可以选择使用哪个APP并将这个APP作为默认选项来处理这个action。当用户想从现在开始用同一个APP来执行操作的时候,这是好的,例如打开一个网页(用户通常只喜欢一个网页浏览器)。
然而,当多个APP都可以对你的intent做出反应的时候,用户可能想每次使用不同的APP,你应该明确的显示一个选择弹窗。这个弹窗每次都会询问用户使用按个APP(用户不能选择默认操作)。例如,当你的APP要执行分享操作的时候,用户可能想根据当前的情况分享到不同的APP中,所以你应该总是显示选择弹窗。
要显示一个选择框 ,用createChooser来创建一个intent然后传给startActivity。例如:
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);
}
这显示了一个弹窗,该弹窗含有一个能对传给createChooser方法的intent做出反应的APP并使用提供的文本作为弹窗的标题。
为了宣传你的APP可以接受哪些intent,你可以对你APP的每个组件在manifest中用
说明:显式的intent总是传给它的目标组件,忽略所有声明的intent filter。
一个应用组件应该对它每个能做的工作声明单独的intent filter。例如,一个在相册APP中的activity可能有两个filter:一个用来查看图片,另一个用来编辑图片。当这个activity启动的时候,它检查intent并根据intent中的数据来决定如何表现(例如是否应该显示编辑控制台)。
每个在manifest中被
-
用name属性来声明可以接受的intent action。name的值必须是字面上的字符串,不能是类的常量。 -
声明能接受的data的类型,用一或多个属性来多方面声明data URI(sheme,hos,port,path等)和MIME 类型。 -
用name属性来声明可以接受的category。name的值必须是字面上的字符串,不能是类的常量。
注意:为了能接受隐式intent,你必须在intent filter中包含CATEGORY_DEFAUL
category。startActivity和startActivityForResult方法把所有的intent当做已经声明了CATEGORY_DEFAUL
category。如果你不在你的intent filter里面声明这个category,那么你的activity不会收到任何的隐式intent。
例如,下面是是一个声明了一个intent filter的activity,它能接受数据类型是text的ACTION_SEND
的intent。
你可以创建一个包含多个
,或者
实例的filter。如果你这么做了,你需要确定你的组件可以处理这些元素的所有组合。
如果你想处理大量的intent,但是只处理特定的action,data和category的组合,那么你需要创建大量的intent filter。
一个隐式的intent通过比较三个元素中的每个元素来和一个filter进行测验。为了能将intent传给对应的组件,该intent必须通过所有的三个测验。如果有一个不匹配,那么Android系统就不会把intent传给该组件。然后,因为一个组件可能有多个intent filter,一个intent不匹配组件的一个filter但是能匹配另外一个。
限制对组件的访问
使用intent filter来阻止其他APP启动你的组件是不安全的。即使intent filter限制了组件只能对特定种类的隐式intent做出反应,但是其他的APP可能打开你的APP如果开发者确定你的组件的名字的话。如果你的一个组件只能你自己的APP可以访问,设置该组件的exported属性为false。
对于所有的activity,你都需要在你的manifest中声明你的intent filter。然而,广播接受接受者的filter可以通过调用registerReceiver方法来动态注册,随后你可以用unRegisterReceiver来解绑。这样做可以允许在你APP运行的特定的时间里监听特定的广播。
为了更好的理解一些intent filter的行为,查看下面来自一个社交风向APP的manifest文件的代码片段:
第一个activity,MainActivity,是该APP的主要入口-当user第一次点击启动图标启动该APP的时候,这个activity会启动。
ACTION_MAIN
action表明这是一个主要的入口且不需要任何intent数据。
CATEGORY_LAUNCHER
category表明这个activity的图标应该放在系统应用启动器中。如果该
这两个必须成对出现来让一个activity出现在系统应用启动器中。
第二个activity,ShareActivity,是用来分享文字和多媒体内容的。尽管用户可以从MAinActivity导航到这个activity,他们也可以用一个能匹配一个intent filter的隐式intent来从另外的APP进入ShareActivity。
说明:application/vnd.google.panorama360+jpg
这个MIME类型是一个指定了全景图片的数据类型,你可以用谷歌全景API来处理它。
使用一个Pending Intent
PendingIntent对象是一个Intent对象的包装器。 PendingIntent的主要目的是授予外部应用程序使用包含的Intent的权限,就像它是从应用程序自己的进程执行一样。
主要的pending intent的使用场景包括:
声明一个intent,该intent在用户对你的Notification执行一个操作的时候被执行(Android系统的NotificationManager执行这个intent)。
声明一个intent,该intent在用户对你的应用小部件执行一个操作的时候被执行(Home screen APP 执行这个intent)。
声明一个在将来特定时间会被执行的intent(Android系统的AlarmManager会执行这个intent)。
因为intent对象被设计为处理特定类型的APP组件(activity,service或broadcast),因此,必须以相同的考虑创建PendingIntent。当使用一个pending intent的时候,你不会调用像startActivity这样的方法来执行它。在你创建你的pending intent的时候,你必须调用对应的构建方法来声明你要启动的组件类型。
PendingIntent.getActivity()
PendingIntent.getService()
PendingIntent.getBroadcast()
除非你的APP要从其他APP接受pending intent,否则上述创建PendingIntent的方法是您可能需要的唯一PendingIntent方法。
每个方法都接受一个当前APP的context,一个你要包装的intent和一个或多个flag,声明了该intent应该如何被使用(例如这个intent是否可以被使用多次)。
Intent解析
当系统收到启动activity的隐式intent时,它会根据以下三个方面通过比较intent与intent filter来搜索intent的最佳activity:
- 该intent的action
- 该intent的data(URL和数据类型)
- 该intent的category
下面的部分根据intent filter如何在manifest中被声明来描述一个intent如何和一个正确的组件匹配上。
- Action
要声明能接受的intent action,一个intent filter能声明零或多个元素。例如:
...
为了通过这个filter,对应的intent中声明的action必须要匹配列在上面的一种action。
如果filter没有列出任何的action,那么就没有东西给intent去匹配,所以所有的intent都不能通过测试。然后,如果一个intent没有指定action,那么它会通过测试(只要filter至少包含一个action即可)。
- Data test
要声明能接受的intent data,一个intent filter能声明零或多个元素。例如:
...
每个元素可以指定一个URI的结构和数据类型(MIME数据类型)。有一些分离开的属性-scheme,host,port和path- 对于每个URI:
:// : /
例如:content://com.example.project:200/folder/subfolder/etc
在这个URI中,schme是content,host是com.example.project,端口是200,path是folder/subfolder/etc。
在中,这些属性都是可选的,但是有线性的依赖:
如果scheme没有指定,那么host会被忽略
如果host没有被指定,那么port会被忽略
如果scheme和host都没有被指定,那么path被忽略。
当一个intent中的URI和filter中的URI比较的时候,只比较filter中URI有的部分。例如:
如果filter只指定了scheme,那么所有带有这个scheme的URI都会和这个filter匹配。
如果filter只指定了scheme和authority但是没有path,那么所有带有相同的scheme和authority的URI会通过这个filter,忽略他们的path。
如果filter只指定了scheme,authority和path,那么只有具有相同的scheme,authority和path的URI会通过这个filter。
说明:一个路径规范可以包含一个星号通配符(*
)以仅要求路径名称的部分匹配。
数据测试将intent中的URI和MIME类型与filter中指定的URI和MIME类型进行比较。 规则如下:
a. 仅当filter未指定任何URI和MIME类型时,不包含URI和MIME类型的intent才会通过测试。
b. 如果一个intent仅包一个URI但不包含一个MIME类型的时候(既不明确也不能从URI推出),只有在它的URI和filter的URI匹配且filter也没有指定MIME类的时候才能通过测试。
c. 如果一个intent仅包一个MIME类型但不包含一个URI的时候,只有filter列出了相同的MIME类型且没有指定URI的时候才能通过测试。
d.如果一个intent既包含URI又包含MIME类型(明确的或者从URL推出的),只有在和filter列出的类型匹配的时候才能通过MIME类型部分的测试。只有在它的URI和filter的URI匹配或者它有content:或者file:的URI且filter没有指定URI的时候,它才会通过URL部分的测试。话句话说,如果一个filter只指定了MIME类型,那么假定它支持content:和file:数据。
最后一条规则(d)反映了组件可以从文件或者content provider中获取一个本地数据。因此,他们的filter只需要列出数据类型,不需要指定名为content:和file:的scheme。下面一个元素的例子告诉Android该组件可以从content provider获取图片数据并展示:
...
因为大多数数据是由content provider分发的,所以指定了数据类型但是没有指定URI的filter可能是最常见的。
另一个常见的是带有scheme和数据类型的filter。例如,下面的元素告诉Android系统该组件可以从网络获取video以此来执行操作:
...
Intent匹配
intent和intent filter的匹配不仅仅是为了发现目标组件来激活,还可以发现一些设备上组件的设置。例如,Home APP通过找到所有带指定了ACTION_MAIN
action和CATEGORY_LAUNCHER
category的activity来填充应用启动器。
你的应用也可以用类似的方式来使用intent的匹配。PackageManager有一系列的query...()方法来返回所有能接受一个指定的intent的组件,和一系列类似的resolve...()方法来决定最好的组件来回应指定的intent。例如,queryIntentActivities()返回了一个包含所有能执行当做参数传进来的intent的activity,queryIntentService()返回了一个类似的service列表。这两个方法都不会激活组件,它们只是列出了能做出反应的组件。queryBroastcast()也是类似的方法,用来查找broadcast。