Android应用框架鼓励开发者在开发应用时重用组件,本文将阐述如何用组件构建应用程序以及如何用intent将组件联系起来。如需阅读官方原文,请您访问链接:https://developer.android.com/guide/components/index.html
Intent 与 Intent Filters(Intents and Intent Filters)
Intent是一个传递消息的对象,您可以为Intent指定action来启动其他应用组件,Intent使组件之间通信更加便利,并且通信方式有很多,这里列举了主要的三点:
启动Activity:
您可以将intent作为参数调用startActivity()方法启动一个activity。该intent描述了将要启动的目标activity的特性并携带必要的数据信息。您还可以调用startActivityForResult()方法回传信息。
启动Service:
Service用于在后台执行任务,不与用户交互。您可以使用startService()方法执行一次性的操作(比如后在台下载一个文件),这需要Intent参数。 如果组件之间需要向CS结构一样通讯,您可以把Service想成一端,并调用bindService(),这同样需要Intent参数。
传递一个broadcast:
broadcast是一种可以被任何应用程序截获的广播机制。系统会基于当前发生的事件发出各式broadcast(比如设备开机时、开始充电时等),您可以调用sendBroadcast()、sendOrderedBroadcast()、 sendStickyBroadcast()方法发送一条广播,这需要传入Intent参数。
Intent的种类(Intent Types)
显式Intent:
通过指定具体类名启动一个组件。显式Intent一般用于同一应用程序内,因为您可以确定地知道要启动的组件名。另外,Android 5.0以后规定必须显式启动Service。
隐式Intent:
当希望启动具备某种特性的组件时,可以使用隐式Intent,隐式Intent无需指定类名,通常用于启动其他应用程序的组件,比如您打算启动一个地图定位的activity。
当您隐式地启动一个service或activity时,Intent会根据其中的内容,匹配其他组件中manifest文件的Intent-filter,启动符合条件的组件,并把Intent中的参数传过去,如果有多个intent-filter满足条件,那么系统会弹出一个对话框,由用户决定启动哪个组件。下面是intent与intent-filters配合启动组件的示意图:
见上图:
1、首先Activity A利用传入的Intent调用startActivity();
2、系统会根据该Intent的条件搜索Android系统中所有匹配的组件;
3、若找到了匹配intent的intent-filters所属的组件(Activity B),则启动该组件,并回调onCreate()方法,同时将Intent传递过去。
intent-filters是manifest文件中组件内部的一个标签,该标签描述了组件具备什么特性,如果您未配置intent-filters,那个该组件只能被显式启动。
创建Intent对象(Building an Intent)
Intent中包含了目标组件需满足的特性。Intent中应包含以下信息:
Component name:
目标组件的名字。对于显式启动,这是不可缺省的,您可以使用Intent的构造方法传入组件名称,也可以调用setComponent(), setClass(), setClassName()这些方法传入组件名;若是隐式启动,这是可选的,但intent应包含其他信息(action、category、data);
Action:
是一个可以指明目标组件行为的字符串。action很大程度上决定了category和data中应传入的信息;您也可以在自己的应用程序组件中指定action,以便让其他应用程序启动自己的组件。对应action中字符串,不建议使用硬编码的形式,而应在所属组件的类中设置为常量。
常见的action有:
ACTION_VIEW:用ACTION _VIEW启动的activity一般可以向用户展示一些信息,比如启动一个相册APP中展示图片的activity,或是启动一个地图APP中展示地址信息的activity。
ACTION_SEND:一般需要向通过ACTION _SEND启动的activity 附带着发送一些信息,这些信息由由目标activity决定该发送给谁,比如社交类APP或是发送邮件的APP。
您可以将action作为参数传入Intent的构造方法或setAction()方法中。
如需定义在自己的组件中定义action,应以应用的包名作为前缀,比如:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
Data:
一个URI对象是一个引用的data的表现形式,或是data的MIME类型;data的类型由Intent的action决定,比如说若action是ACTION_EDIT,那么data的URI应指向一个可编辑的文件。
当创建一个Intent时,除了为data指定URI以外,还应该指定data的MIME类型,比如说,一个用于展示图片的activity是不能用来放音乐的,如果您要启动这个activity,就需要将data的MIME类型指定为"image/png"、"image/jpeg"等。有些时候,从data的URI中就能推断出MIME的类型,比如当一个URI的schema是"content://"时,表明该URI指向了设备内部的一个文件并由ContentProvider管理着,系统可以根据该文件推断出data的MIME类型。
您可以调用setData()方法设置URI,调用setType()方法设置MIME类型,或调用setDataAndType()方法同时设置URI和MIME类型。
请注意:如果您需要同时设置URI和MIME类型,只能调用setDataAndType()方法,而不能分别调用setData()和setType(),因为调用setData()时会首先将setType()中的内容置空,反之亦然( they each nullify the value of the other)
Category:
是一个字符串,表示目标组件的附加信息,大部分intent不需要category。以下是依稀而常用的category:
CATEGORY_BROWSABLE:表示目标activity可以被网页上的某个链接启动,如图片activity或e-mail信息activity。
CATEGORY_LAUNCHER:目标activity是任务栈的第一个activity,也就是应用程序的启示activity。
您可以将category参数传入addCategory()方法中。
上述的参数(component name, action, data, and category)代表了intent的属性,通过这些参数,系统可以筛选出符合条件的目标组件。除此之外,intent还可以包含下列参数,与上面的参数不同的是,系统不会使用这些参数来筛选目标组件:
Extras:
一些intent可以携带的附加信息,以键值对的形式存储。可以使用putExtra()方法将键值对信息传入,也可以将键值对信息放在Bundle对象中,再通过将Bundle对象传入putExtra()中。
Intent类中封装了许多 " EXTRA_* "形式的标准extra,如果想封装自己的extra键,请您以应用程序的报名作为前缀,比如:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
Flags:
该参数可以为intent添加元数据(meta-data),flag可以指导系统以何种方式启动一个activity、是否将启动的activity放在该应用的任务栈中,等等。
隐式Intent的例子(Example implicit intent)
请注意:若系统中没有满足隐式Intent的目标组件,则应用将崩溃(crash),所以首先应判断,再调用startActivity()。
以下是一个通过隐式intent启动一个“发送信息的Activity”的例子:
// 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); }
如果有一个目标组件满足intent,则启动该组件;若有多个满足intent的目标组件,则系统弹出一个列表以供选择。
使用应用选择器(Forcing an app chooser)
正如向上面说,系统中可能存在多个目标组件满足隐式intent,这时会弹出一个列表供用户选择,有些时候,用户希望每次都启动一个相同的组件(比如用户每次都想启动chrome浏览器而不是系统自带的浏览器),这时只需要勾选“不再询问”选项就行了,下次再启动时,列表将不再弹出;还有些时候,用户每次都需要对列表中的Activity进行筛选,比如启动用于分享的Activity,用户希望每次分享到不同的平台,这时需要调用Intent.createChooser()方法以保证每次都弹出选择列表,如下所示:
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(Receiving an Implicit Intent)
通过在manifest文件中配置intent-filter标签中的action, data, and category,可以设置筛选信息,只有同时符合上述三个标签设置的筛选信息,Intent才能开启您的应用程序组件:
action标签:
可匹配Intent中的action参数。
data标签:
可匹配Intent中的data参数(URI地址以及MIME 类型)。
category标签:
可匹配Intent中的category 参数。
请注意:如组件需要被隐式启动,必须配置CATEGORY_DEFAULT
如想隐式启动一个分享的Activity,则目标Activity如下配置:
<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>
一个intent-filter中可以包含多个 action, data, category 标签。
若组件仅希望通过本应用启动,可将组件中的exported属性设为false。
请注意:为了避免隐式intent匹配上了您的Service组件,请不要在service中配置intent-filter(Service必须显式启动)
intent-filter举例(Example filters)
下面是一个社交APP的manifest文件示例:
<activity android:name="MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> <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> <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>
action为 “android.intent.action.MAIN”表示该Activity是应用的主入口,且无需配置data。
category为 “android.intent.category.LAUNCHER”表示该activity的启动图标(通过icon属性配置)应添加到系统的launcher中,若未配置icon,则会使用application标签下的icon。
以上两个属性应成对出现。
如需隐式启动ShareActivity,仅需匹配一个intent-filter就行了。
使用Pending Intent(Using a Pending Intent)
PendingIntent是一个包装Intent的类,主要用于实现Intent的延时启动,PendingIntent的主要使用场合:
包装一个Notification的启动Intent;
包装一个App Widget的Intent(按Home键启动的activity);
包装一个延时启动的activity(如AlarmManager)。
使用PendingIntent启动的activity无需使用startActivity()就能启动,您应当使用对应组件的方法启动相应组件:
通过PendingIntent.getActivity()启动一个activity;
通过PendingIntent.getService()启动一个Service;
通过PendingIntent.getBroadcast()启动一个BroadcastReceiver;
解析Intent(Intent Resolution)
目标组件通过以下三点匹配相应的Intent:
The intent action;
The intent data (both URI and data type);
The intent category。
匹配Action(Action test)
intent filter可定义零到多个action标签:
<intent-filter> <action android:name="android.intent.action.EDIT" /> <action android:name="android.intent.action.VIEW" /> ... intent-filter>
intent需要匹配上其中一个action标签。如果intent-filter中没有action标签,则intent无需action就能匹配。
匹配Category(Category test)
intent filter可定义零到多个category标签:
<intent-filter> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> ... intent-filter>
intent中的定义的每一个category都需要匹配上intent-filter中的category标签,反之不成立(intent-filter中的category标签可能比intent中的定义的category多)。所以无论intent-filter中是否定义了category标签,未添加category的intent总能匹配上该intent-filter。
请注意:通过startActivity()或startActivityForResult()方法隐式启动的intent中,将自动被添加一个CATEGORY_DEFAULT的category,所以若您希望自己的activity能够被隐式启动,则需要在intent-filter中添加一个android.intent.category.DEFAULT的category标签。
匹配Data(Data test)
intent filter可定义零到多个data标签:
<intent-filter> <data android:mimeType="video/mpeg" android:scheme="http" ... /> <data android:mimeType="audio/mpeg" android:scheme="http" ... /> ... intent-filter>
每个data标签都能设置mimeType和URI 结构,其中URI可分成四部分:scheme, host, port 和 path;其结构如下:
<scheme>://:/
比如:
content://com.example.project:200/folder/subfolder/etc
其中:
scheme为content;
host为com.example.project;
port为200;
path为folder/subfolder/etc。
每一部分在data标签中都不是必须定义的,但存在一个线性依赖:
若scheme 未指定,则host被忽略;
若host未指定,则port被忽略;
若scheme和host均未指定,则path被忽略;
在intent中添加的data只需要匹配一部分intent-filter中的data(URI匹配):
若filter只定义了scheme,则intent的data定义的URI中只要包含了相同的scheme,就能匹配;
若filter只定义了scheme和host,则intent的data定义的URI中只要包含了相同的scheme和host,就能匹配;
若filter只定义了scheme、host和port,则intent的data定义的URI中只要包含了相同的scheme、host和port,就能匹配;
intent-filter匹配intent的data中URI和mimeType类型的规则如下:
1. 如果intent-filter中未指定data,则未添加data的intent可以匹配;
2. 如果intent-filter中指定了URI,但未指定mimeType,则按照上一段的规则匹配(intent中也应未指定mimeType);
3. 如果intent-filter中指定了mimeType,而未指定URI,则可以匹配intent中指定了相同mimeType,而未指定URI的组件;
4. 如果intent-filter中同时指定了mimeType和URI,则:
intent中添加的mimeType只要能匹配上intent-filter中的某一个mimeType,就能匹配上mimeType部分;
intent中添加的URI则按照上一段中的URI匹配规则,就能该匹配上URI部分;特别地:若intent中的URI的scheme 指定为content: 或者 file:,那么即便intent-filter中未定义URI,也能匹配成功。换句话说:包含content: 或者 file: 的URI总是能匹配上只定义了mimeType的intent-filter