三种应用程序基本组件——activity, service和broadcast receiver——是使用称为intent的消息来激活的。Intent消息传递是一种组件间运行时绑定的机制. intent是Intent对象, 它包含了需要做的操作的描述, 或者, 对于广播来说, 包含了正在通知的消息内容. 对于向这三种组件发送intent有不同的机制:
使用Context.startActivity() 或 Activity.startActivityForResult(), 传入一个intent来启动一个activity. 使用 Activity.setResult() , 传入一个intent来从activity中返回结果.将intent对象传给Context.startService() 来启动一个service或者传消息给一个运行的service. 将intent对象传给 Context.bindService() 来绑定一个service.将intent对象传给 Context.sendBroadcast() , Context.sendOrderedBroadcast() ,或者Context.sendStickyBroadcast() 等广播方法,则它们被传给 broadcast receiver.在上述三种情况下, android系统会自己找到合适的activity, service, 或者 broadcast receivers来响应intent. 三者的intent相互独立互不干扰.
一个intent对象包含了接受该intent的组件的信息(例如需要的动作和该动作需要的数据)和android系统所需要的信息(例如该组件的类别,以及如何启动它). 具体的说:
组件名称 为一个 ComponentName 对象. 它是目标组件的完整名(例如" com.example.project.app.FreneticActivity ")和应用程序manifest文件设定的包名(例如" com.example.project ")的组合.前者的包名部分和后者不一定一样.组件名称是可选的. 如果设定了的话, Intent对象会被传给指定的类的一个实例. 如果不设定, 则android使用其它信息来定位合适的目标.
组件名称是使用setComponent() , setClass() ,或 setClassName() 来设定, 使用 getComponent() 来获取.
Action 一个字符串, 为请求的动作命名, 或者, 对于broadcast intent, 发生的并且正在被报告的动作. 例如: 常量目标组件 动作ACTION_CALL activity 发起一个电话呼叫.ACTION_EDIT activity 显示数据给用户来编辑.ACTION_MAIN activity 将该activity作为一个task的第一个activity启动,不传入参数也不期望返回值.ACTION_SYNC activity 将设备上的数据和一个服务器同步.ACTION_BATTERY_LOW broadcast receiver 发出电量不足的警告.ACTION_HEADSET_PLUG broadcast receiver 一个耳机正被插入或者拔出. ACTION_SCREEN_ON broadcast receiver 屏幕被点亮.ACTION_TIMEZONE_CHANGED broadcast receiver 时区设置改变.你也可以定义自己的action字符串用来启动你的应用程序. 自定义的action应该包含应用程序的包名.例如"com.example.project.SHOW_COLOR ".
action很大程度上决定了intent的另外部分的结构, 就像一个方法名决定了它接受的参数和返回值一样. 因此, 最好给action一个最能反映其作用的名字.
一个intent对象中的action是使用getAction()和setAction()来读写的.
Data 需要操作的数据的URI和它的 MIME(多用途互联网邮件扩展,Multipurpose Internet Mail Extensions)类型. 例如, 如果action为ACTION_EDIT, 那么Data将包含待编辑的数据URI. 如果action为ACTION_CALL, Data将为tel:电话号码的URI. 如果action为ACTION_VIEW, 则Data为http:网络地址的URI.当将一个intent和一个组件相匹配时, 除了URI外数据类型也很重要. 例如, 一个显示图片的程序不应该用来处理声音文件.
数据类型常常可以从URI推断, 特别是content:URI, 它表示该数据属于一个content provider. 但数据类型也可以被intent对象显示声明. setData()方法设置URI, 而setType()方法指定MIME类型, setDataAndType()设置数据URI和MIME类型. 它们可以使用getData()和getType()来读取.
Category 一个字符串,包含了关于处理该intent的组件的种类的信息. 一个intent对象可以有任意个category. intent类定义了许多category常数, 例如: 常量含义CATEGORY_BROWSABLE 目标activity可以使用浏览器来显示-例如图片或电子邮件消息.CATEGORY_GADGET 该activity可以被包含在另外一个装载小工具的activity中.CATEGORY_HOME 该activity显示主屏幕,也就是用户按下Home键看到的界面.CATEGORY_LAUNCHER 该activity可以作为一个任务的第一个activity,并且列在应用程序启动器中.CATEGORY_PREFERENCE 该activity是一个选项面板.addCategory()方法为一个intent对象增加一个category, removeCategory删除一个category, getCategories()获取intent所有的category.
Extras 为键-值对形式的附加信息. 例如 ACTION_TIMEZONE_CHANGED 的intent有一个"time-zone"附加信息来指明新的时区, 而 ACTION_HEADSET_PLUG 有一个"state"附加信息来指示耳机是被插入还是被拔出.
intent对象有一系列put...()和set...()方法来设定和获取附加信息. 这些方法和Bundle对象很像. 事实上附加信息可以使用putExtras()和getExtras()作为Bundle来读和写.
Flags各种标志. 很多标志指示android系统如何启动一个activity(例如该activity属于哪个任务)和启动后如何处理它(例如, 它是否属于最近activity列表中).
android系统和应用程序使用intent对象来送出系统广播和激活系统定义的组件.
intent有两种:
显式 intent使用名字来指定目标组件. 由于组件名称一般不会被其它开发者所熟知, 这种intent一般用于应用程序内部消息-- 例如一个activity启动一个附属的service或者另一个activity.隐式 intent不指定目标的名称. 一般用于启动其它应用程序的组件.
Android将显式intent发送给指定的类. intent对象中名字唯一决定接受intent的对象.
对于隐式intent, android系统必须找到最合适的组件来处理它. 它比较intent的内容和intent filter. intent filter是组件的一个相关结构, 表示其接受intent的能力. android系统根据intent filter打开可以接受intent的组件. 如果一个组件没有intent filter, 那么它只能接受显式intent. 如果有, 则能同时接受二者.
当一个intent和intent filter比较时, 只考虑三个属性: action, data, category.
extra和flag在intent解析中没有用.
activity, service和broadcast receiver可以有多个intent filter来告知系统它们能接受什么样的隐式intent. intent filter的名字很形象: 它过滤掉不想接受的intent, 留下想接受的intent. 显式intent无视intent filter.
一个组件对能做的每件事有单独的filter. 例如, 记事本程序的NoteEditor activity有两个filter -- 一个启动并显示一个特定的记录给用户查看或编辑, 另一个启动一个空的记录给用户编辑.
一个intent filter不一定安全可靠. 一个应用程序可以让它的某个组件去接受隐式intent, 但是它没法防止这个组件接受显示intent. 其它的程序总是可以使用自定义的数据加上显式的程序名称来调用该组件.
一个intent filter是IntentFilter类的实例, 但是它一般不出现在代码中,而是出现在android Manifest文件中, 以<intent-filter>的形式. (有一个例外是broadcast receiver的intent filter是使用 Context.registerReceiver() 来动态设定的, 其intent filter也是在代码中创建的.)
一个filter有action, data, category等字段. 一个隐式intent为了能被某个intent filter接受, 必须通过3个测试. 一个intent为了被某个组件接受, 则必须通过它所有的intent filter中的一个.
Action 测试一个intent对象只能指定一个action, 而一个intent filter可以指定多个action. action列表不能为空, 否则它将组织所有的intent.
一个intent对象的action必须和intent filter中的某一个action匹配, 才能通过.
如果intent filter的action列表为空, 则不通过.
如果intent对象不指定action, 并且intent filter的action列表不为空, 则通过.
Category 测试
<intent-filter . . . >
<category android:name = "android.intent.category.DEFAULT" /> <category android:name = "android.intent.category.BROWSABLE" /> . . . </intent-filter>注意前面说到的对于action和category的常数是在代码中用的,而不是manifest文件中用的. 例如, CATEGORY_BROWSABLE 常数对应xml中的表示为"android.intent.category.BROWSABLE ".
一个intent要通过category测试, 那么该intent对象中的每个category都必须和filter中的某一个匹配.
理论上来说, 一个intent对象如果没有指定category的话, 它应该能通过任意的category 测试. 有一个例外: android把所有的传给startActivity()的隐式intent看做至少有一个category: "android.intent.category.DEFAULT ". 因此, 想要接受隐式intent的activity必须在intent filter中加入"android.intent.category.DEFAULT ". ("android.intent.action.MAIN " 和"android.intent.category.LAUNCHER "的intent filter例外. 它们不需要"android.intent.category.DEFAULT ".)
Data test每个<data>元素指定了一个URI和一个数据类型. URI每个部分为不同的属性 -- scheme, host, port, path:
scheme://host:port/path
例如, 在如下的URI中:
content://com.example.project:200/folder/subfolder/etc
scheme为"content", host为"com.example.project", port为"200", path为"folder/subfolder/etc". host和port一起组成了URI authority. 如果host未指定,则port被忽略.
这些属性都是可选的,但它们并非相互独立: 要使一个authority有意义,必须指定一个scheme. 要使一个path有意义, 必须指定一个scheme和一个authority.
当intent对象中的URI和intent filter中相比较时, 它只和filter中定义了的部分比较. 例如, 如果filter中之定义了scheme,那么所有包含该scheme的URI的intent对象都通过测试.对于path来说,可以使用通配符来进行部 分匹配.
<data>元素的type属性指定了数据类型. 它在filter中比在URI中更常见. intent对象和filter都可以使用"*"通配符作为子类型. 例如"text/*" "audio/*"表示所有的子类型都匹配.
data测试的规则如下:
一个不含uri也不含数据类型的intent对象只通过两者都不包含的filter.一个含uri但不含数据类型的intent对象(并且不能从uri推断数据类型的)只能通过这样的filter: uri匹配, 并且不指定类型. 这种情况限于类似mailto:和tel:这样的不指定实际数据的uri.一个只包含数据类型但不包含URI的intent只通过这样的filter: 该filter只列出相同的数据类型, 并且不指定uri.一个既包含uri又包含数据类型的intent对象只通过这样的filter: intent对象的数据类型和filter中的一个类型匹配, intent对象的uri要么和filter的uri匹配, 要么intent对象的uri为content:或者file:, 并且filter不指定uri.如果一个intent可以通过多于一个activity或者service的filter, 那么用户可能会被询问需要启动哪一个. 如果一个都没有的话, 那么会抛出异常.
上述的最后一个规则(d)说明了组件通常可以从文件和content provider中获取数据. 因此, 它们的filter可以只列出数据类型不列scheme. 这是个特殊情况. 下列<data>元素告诉android该组件可以从一个content provider取得图像数据并显示之:
<data android:mimeType = "image/*" />
由于大部分可用的数据由content provider提供, 指定数据类型但不指定uri的filter是最常见的情况.
另外一个常见的配置是filter具有一个scheme和一个数据类型. 例如, 下列<data>元素告诉android该component可以从网络获取图像数据并显示之:
<data android:scheme = "http" android:type = "video/*" />
考虑用户点击一个网页时浏览器的动作. 它首先试图显示这个数据(当做一个html页来处理). 如果无法显示, 则创建一个隐式intent, 并启动一个可以处理它的activity. 如果没有这样的activity, 那么它请求下载管理器来下载该数据. 然后它将数据置于一个content provider的控制之下, 这样有很多activity(拥有只有数据类型的filter)可以处理这些数据.
大部分应用程序还有一种方法来单独启动, 不需要引用任何特定的数据. 这些能启动应用程序的activity具有action为"android.intent.action.MAIN " 的filter. 如果它们需要在应用程序启动器中显示, 它们必须指定"android.intent.category.LAUNCHER " 的category.
<intent-filter . . . > <action android:name = "code android.intent.action.MAIN" /> <category android:name = "code android.intent.category.LAUNCHER" /> </intent-filter>
intent和intent filter相匹配, 不仅为了寻找并启动一个目标组件, 也是为了寻找设备上组件的信息. 例如, android系统启动了应用程序启动器, 该程序位于屏幕的顶层, 显示了用户可以启动的程序, 这是通过查找设备上所有的action为"android.intent.action.MAIN " ,category为"android.intent.category.LAUNCHER "的intent filter所在的activity实现的. 然后它显示了这些activity的图标和标题. 类似的, 它通过寻找 "android.intent.category.HOME "的filter来定位主屏幕程序.
应用程序可以用相同的方式来使用intent匹配. PackageManager 有一组query...()方法来寻找接受某个特定intent的所有组件, 还有一系列resolve...()方法来决定响应一个intent的最佳组件. 例如, queryIntentActivities() 返回一个activity列表, 这些activity可以执行传入的intent. 类似的还有queryIntentServices()和queryIntentBroadcastReceivers().
记事本示例程序让用户可以浏览一个笔记列表, 查看, 编辑, 删除和增加笔记. 这一节关注该程序定义的intent filter.
在其manifest文件中, 记事本程序定义了三个activity, 每个有至少一个intent filter. 它还定义了一个content provider来管理笔记数据. manifest 文件如下:
<manifest xmlns:android = "http://schemas.android.com/apk/res/android" package = "com.example.android.notepad" > <application android:icon = "@drawable/app_notes" android:label = "@string/app_name" > <provider android:name = "NotePadProvider" android:authorities = "com.google.provider.NotePad" /> <activity android:name = "NotesList" android:label = "@string/title_notes_list" > <intent-filter> <action android:name = "android.intent.action.MAIN" /> <category android:name = "android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name = "android.intent.action.VIEW" /> <action android:name = "android.intent.action.EDIT" /> <action android:name = "android.intent.action.PICK" /> <category android:name = "android.intent.category.DEFAULT" /> <data android:mimeType = "vnd.android.cursor.dir/vnd.google.note" /> </intent-filter> <intent-filter> <action android:name = "android.intent.action.GET_CONTENT" /> <category android:name = "android.intent.category.DEFAULT" /> <data android:mimeType = "vnd.android.cursor.item/vnd.google.note" /> </intent-filter> </activity> <activity android:name = "NoteEditor" android:theme = "@android:style/Theme.Light" android:label = "@string/title_note" > <intent-filter android:label = "@string/resolve_edit" > <action android:name = "android.intent.action.VIEW" /> <action android:name = "android.intent.action.EDIT" /> <action android:name = "com.android.notepad.action.EDIT_NOTE" /> <category android:name = "android.intent.category.DEFAULT" /> <data android:mimeType = "vnd.android.cursor.item/vnd.google.note" /> </intent-filter> <intent-filter> <action android:name = "android.intent.action.INSERT" /> <category android:name = "android.intent.category.DEFAULT" /> <data android:mimeType = "vnd.android.cursor.dir/vnd.google.note" /> </intent-filter> </activity> <activity android:name = "TitleEditor" android:label = "@string/title_edit_title" android:theme = "@android:style/Theme.Dialog" > <intent-filter android:label = "@string/resolve_title" > <action android:name = "com.android.notepad.action.EDIT_TITLE" /> <category android:name = "android.intent.category.DEFAULT" /> <category android:name = "android.intent.category.ALTERNATIVE" /> <category android:name = "android.intent.category.SELECTED_ALTERNATIVE" /> <data android:mimeType = "vnd.android.cursor.item/vnd.google.note" /> </intent-filter> </activity> </application> </manifest>
第一个activity, NoteList, 和其它activity不同, 因为它操作一个笔记的目录(笔记列表), 而不是一个单独的笔记. 它一般作为该程序的初始界面. 它可以做以下三件事:
该filter声明了记事本应用程序的主入口. 标准的MAIN action是一个不需要任何其它信息(例如数据等)的程序入口, LAUNCHER category表示该入口应该在应用程序启动器中列出.
该filter声明了改activity可以对一个笔记目录做的事情. 它允许用户查看或编辑该目录(使用VIEW和EDIT action), 或者选取特定的笔记(使用PICK action).
<data>元素的mimeType指定了这些action可以操作的数据类型. 它表明该activity可以从一个持有记事本数据的content provider(vnd.google.note)取得一个或多个数据项的Cursor(vnd.android.cursor.dir).
注意该filter提供了一个DEFAULT category. 这是因为 Context.startActivity() 和 Activity.startActivityForResult()方法将所有的intent都作为作为包含了DEFAULT category来处理, 只有两个例外:
显式指明目标activity名称的intent.包含MAIN action 和LAUNCHER category的intent.因此, 除了MAIN和LAUNCHER的filter之外, DEFAULT category是必须的.
这个filter描述了该activity能够在不需要知道目录的情况下返回用户选择的一个笔记的能力. GET_CONTENT action和PICK action相类似. 在这两者中, activity都返回用户选择的笔记的URI. (返回给调用startActivityForResult()来启动NoteList activity的activity.) 在这里, 调用者指定了用户选择的数据类型而不是数据的目录.
这个数据类型, vnd.android.cursor.item/vnd.google.note , 表示了该activity可以返回的数据类型 -- 一个笔记的URI. 从返回的URI, 调用者可以从持有笔记数据的content provider(vnd.google.note)得到一个项目(vnd.android.cursor.item)的Cursor.
也就是说, 对于PICK来说, 数据类型表示activity可以给用户显式的数据类型.对于GET_CONTENT filter, 它表示activity可以返回给调用者的数据类型.
下列intent可以被NoteList activity接受:
action: android.intent.action.MAIN 不指定任何数据直接启动activity. action: android.intent.action.MAIN category: android.intent.category.LAUNCHER 不指定任何数据直接启动activity. 这是程序启动器使用的intent. 所有使用该组合的filter的activity被加到启动器中. action: android.intent.action.VIEW data: content://com.google.provider.NotePad/notes 要求activity显示一个笔记列表,这个列表位于content://com.google.provider.NotePad/notes . 用户可以浏览这个列表并获取列表项的信息. action: android.intent.action.PICK data: content://com.google.provider.NotePad/notes 请求activity显示content://com.google.provider.NotePad/notes 下的笔记列表. 用户可以选取一个笔记, activity将返回笔记的URI给启动NoteList的activity. action: android.intent.action.GET_CONTENT data type: vnd.android.cursor.item/vnd.google.note 请求activity提供记事本数据的一项.
第二个activity, NoteEditor, 为用户显示一个笔记并允许他们编辑它. 它可以做以下两件事:
这个activity的主要目的是使用户编辑一个笔记--VIEW或者EDIT一个笔记. (在category中,EDIT_NOTE是EDIT的同义词.) intent包含匹配MIME类型vnd.android.cursor.item/vnd.google.note 的URI--也就是某一个特定的笔记的URI. 它一般来说是NoteList activity中的PICK或者GET_CONTENT action返回的.
像以前一样,该filter列出了DEFAULT category.
该activity的第二个目的是使用户能够创建一个新的笔记, 并插入到已存在的笔记目录中. 该intent包含了匹配vnd.android.cursor.dir/vnd.google.note 的URI, 也就是笔
有了这些能力, NoteEditor就可以接受以下intent:
action: android.intent.action.VIEW data: content://com.google.provider.NotePad/notes/ID 要求activity显示给定ID的笔记. action: android.intent.action.EDIT data: content://com.google.provider.NotePad/notes/ID 要求activity显示指定ID的笔记,然后让用户来编辑它. 如果用户保存了更改,则activity更新该content provider的数据. action: android.intent.action.INSERT data: content://com.google.provider.NotePad/notes 要求activity创建一个新的空笔记在content://com.google.provider.NotePad/notes , 并允许用户编辑它, 如果用户保存了更改,则该URI被返回给调用者.
最后一个activity, TitleEditor, 允许用户编辑笔记的标题. 这可以通过直接调用activity(在intent中设置组件名称)的方式来实现. 但是这里我们用这个机会来展示如何在已有数据上进行另外的操作(类似于windows中的打开方式->程序列表 -- 译者注):
<intent-filter android:label = "@string/resolve_title" > <action android:name = "com.android.notepad.action.EDIT_TITLE" /> <category android:name = "android.intent.category.DEFAULT" /> <category android:name = "android.intent.category.ALTERNATIVE" /> <category android:name = "android.intent.category.SELECTED_ALTERNATIVE" /> <data android:mimeType = "vnd.android.cursor.item/vnd.google.note" /> </intent-filter>
它必须在一个特定的笔记上调用(data type vnd.android.cursor.item/vnd.google.note ), 就像之前的VIEW 和EDIT action一样. 然而, 这里activity显示笔记数据中包含的标题, 而不是内容.
除了支持DEFAULT category之外,title编辑器还支持了另外两个category: ALTERNATIVE 和SELECTED_ALTERNATIVE . 这些category标志着activity可以在选项菜单中呈现给用户(就像LAUNCHER category表示activity可以在程序启动器中一样). 注意filter还提供了一个显示标签(android:label="@string/resolve_title" )来更好的控制用户在选项菜单中看到的内容.
有了这些能力, 以下的intent就可以被TitleEditor接受:
action: com.android.notepad.action.EDIT_TITLE data: content://com.google.provider.NotePad/notes/ID 要求activity显示给定笔记ID的标题, 并允许用户编辑该标题.