Android应用框架鼓励开发者在开发应用时重用组件,本文将阐述如何用组件构建应用程序以及如何用intent将组件联系起来。
如需阅读官方原文,请您点击这个链接:
《App Components》。
您还可以参考这些博文:
《Using DialogFragments》
《Fragments For All》
《Multithreading For Performance》
以及这些Training:
《Managing the Activity Lifecycle》
《Building a Dynamic UI with Fragments》
《Sharing Content》
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一般用于同一应用程序内,因为您可以确定地知道要启动的组件名。另外,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中包含了目标组件需满足的特性。Intent中应包含以下信息:
Component name:目标组件的名字。对于显式启动,这是不可缺省的,您可以使用Intent的构造方法传入组件名称,也可以调用setComponent(), setClass(), setClassName()这些方法传入组件名;若是隐式启动,这是可选的,但intent应包含其他信息(action、category、data);
Action:是一个可以指明目标组件行为的字符串。action很大程度上决定了category和data中应传入的信息;您也可以在自己的应用程序组件中指定action,以便让其他应用程序启动自己的组件。对应action中字符串,不建议使用硬编码的形式,而应在所属组件的类中设置为常量。
常见的action有:
您可以将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参数传入addCategory()方法中。
上述的参数(component name, action, data, and category)代表了intent的属性,通过这些参数,系统可以筛选出符合条件的目标组件。除此之外,intent还可以包含下列参数,与上面的参数不同的是,系统不会使用这些参数来筛选目标组件:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
!请注意:若系统中没有满足隐式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的目标组件,则系统弹出一个列表以供选择。
正如向上面说,系统中可能存在多个目标组件满足隐式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);
}
通过在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必须显式启动)
下面是一个社交APP的manifest文件示例:
<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>
action为 “android.intent.action.MAIN”表示该Activity是应用的主入口,且无需配置data。
category为 “android.intent.category.LAUNCHER”表示该activity的启动图标(通过icon属性配置)应添加到系统的launcher中,若未配置icon,则会使用application标签下的icon。
以上两个属性应成对出现。
如需隐式启动ShareActivity,仅需匹配一个intent-filter就行了。
PendingIntent是一个包装Intent的类,主要用于实现Intent的延时启动,PendingIntent的主要使用场合:
使用PendingIntent启动的activity无需使用startActivity()就能启动,您应当使用对应组件的方法启动相应组件:
目标组件通过以下三点匹配相应的Intent:
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就能匹配。
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标签。
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>://<host>:<port>/<path>
比如:
content://com.example.project:200/folder/subfolder/etc
其中
每一部分在data标签中都不是必须定义的,但存在一个线性依赖:
在intent中添加的data只需要匹配一部分intent-filter中的data(URI匹配):
intent-filter匹配intent的data中URI和mimeType类型的规则如下: