<service>

语法

包含于

<application>

可包含

<intent-filter>

<meta-data>

描述

将一个服务(Service 的子类)声明为某个应用程序的组件。与活动不同,服务没有用户界面。它用于处理长时间的后台操作,或实现可被其他应用程序调用的富交互 API。

属性

android:enabled

该服务是否能被系统实例化。“true”表示可以,“false”表示不可。默认值为“true”。

<application> 元素也具有 enabled 属性。这一属性作用于包括服务在内的该应用程序所包含的所有组件。如果要使用某个服务,<application> 与 <service> 的 enabled 属性必须同时为“true”。如果其中有一方为“false”,该服务就将被禁用,无法被系统实例化。

android:exported

该服务能否被其他应用程序的组件调用或与之交互。“true”表示可以,“false”表示不可。如果为“false”,只有同一应用程序,或具有相同用户 ID 的应用程序的组件才能启动或绑定该服务。

该属性的默认值由服务是否包含意图过滤器决定。如果该服务不含任何意图过滤器,则表示要调用它必须明确指定其类名。这意味着该服务被设计为仅供应用程序内部使用(因为其他应用程序无法知晓服务的类名)。此时,exported 属性的默认值为“false”。另一方面,如果存在一个或更多的意图过滤器,则表示该服务可被外部程序调用,于是默认值为“true”。

要限制一个服务被其他应用程序调用,这只是方法之一。我们也可以利用 permission 来限制外部实体与服务发生交互(参见 permission 属性)。

android:icon

代表该服务的图标。该属性必须是某一drawable资源(包含了图像的定义)的引用。如果不为该属性赋值,该服务将默认使用应用程序的图标(参见 <application> 元素的 icon 属性)。

无论通过该属性设置,还是直接沿用 <application> 元素的图标,服务的图标也是该服务所有意图过滤器的默认图标(参见 <intent-filter> 元素的 icon 属性)。

android:isolatedProcess

如果为“true”,该服务将运行于某个特殊进程,与系统其他进程分隔,且不提供任何访问权限。此时,我们只能通过 Service API 与之通信(绑定与启动)。

android:label

用于向用户显示的服务名称。如果不为该属性赋值,该服务将默认使用应用程序的名称(参见 <application> 元素的 label 属性)。

无论通过该属性设置,还是直接沿用 <application> 元素的名称,服务的名称也是该服务所有意图过滤器的默认名称(参见 <intent-filter> 元素的 label 属性)。

该标签应该是某一 string 资源的引用,以便与其他在用户界面中显示的字符串一样进行本地化。不过,在开发过程中,为了方便,该属性也支持直接使用字符串字面量。

android:name

实现了该服务的 Service 子类的名称。它应当为完整的类名(如”com.example.project.RoomService”)。不过,为便于使用,我们可以仅使用包名之后的部分(如”.RoomService”),<manifest> 元素中指定的包名将被自动补全。

一旦我们发布了应用程序,就不该再去修改它的名称(除非 android:exported 被设为“false”)。

该属性没有默认值,但必须被赋值。

android:permission

未启动或绑定该程序所必需的权限名称。如果 startService()、bindService() 或 stopService() 的调用方不具有该权限,就无法访问该服务,Intent 对象也不会被传送。

如果该属性未赋值,<application> 元素的 permission 属性将作为其默认值。如果 <application> 元素同样为对 permission 属性赋值,该服务将不受权限保护。

如需获得更多信息,参见简介中的权限一节,或参见文档《安全与权限》。

android:process

运行该服务的进程名称。通常,一个应用程序的所有组件都运行于为其创建的默认进程。该进程与应用程序的包名同名。我们可以通过 <application> 元素的 process 属性为所有组件设置一个不同的默认进程名称。同时,组件也可以通过改写其 process 属性的值,使应用以多线程的方式运行。

如果该属性的值以冒号(”:”)起始,则会在必要时创建一个应用程序私有的新进程来运行该服务。如果进程名称以小写字母起始,该服务将运行于一个同名的全局进程。此时,该属性表示服务有权限使用该进程。于是,不同应用程序中的组件可以共享一个进程,节约了资源的使用。

参见

<application>

<activity>

引入版本

API Level 1

动作栏(Action Bar)

Action Bar是活动中的一种控件,用以代替传统的屏幕顶端的标题栏。默认,Action Bar包括了左侧的应用程序图标(logo),其右是活动的标题,以及Option Menu中的可选项目。Action Bar提供了多种便利特性,包括:

  • 直接在Action Bar中显示Option Menu中的项目,称之为“动作项目(action item)”——以提供对于关键用户操作的快速选择。没有作为动作项目显示的菜单项目则位在浮动式菜单中,通过Action Bar的下拉列表显示。
  • 提供在不同片段之间的导航标签。
  • 提供导航用的下拉列表。
  • 提供交互式的“动作视图(action view)”以代替动作项目(例如搜索框)。
图1. Email应用程序中Action Bar的一个截图,包含了编写新邮件和刷新收件箱的动作项目。

添加Action Bar

Action Bar在Android 3.0及以上的活动中是默认被包含的。更为具体地说,所有使用了新的“holographic”主题的活动将包含Action Bar,而任何以Android 3.0为目标平台的应用程序将自动使用该主题。当一个应用程序在<uses-sdk>元素中设置了android:minSdkVersionandroid:targetSdkVersion属性为“11”或更高时,被认为是以Android 3.0为目标平台的。例如:

在本范例中,程序请求了最小API等级为4(Android 1.6),而目标API等级为11(Android 3.0)。这样,当程序安装于运行Android 3.0或是更高版本的设备上时,系统将为每一个活动提供holographic主题,因而每个活动都将包含Action Bar。

不过,如果希望使用Action Bar的API,例如添加标签或是修改Action Bar风格,就需要将android:minSdkVersion设为“11”,这样才能使用ActionBar类。

移除Action Bar

如果希望从某个指定的活动中移除Action Bar,只需将该活动的主题设为Theme.Holo.NoActionBar。例如:

提示:如果希望从一个自定义的主题中移除Action Bar,只需将android:windowActionBar样式属性设为false。关于更多Action Bar样式的内容请参见“对Action Bar使用样式”。

还可以在运行时通过调用hide()来隐藏Action Bar,之后通过调用show()来再次显示它。例如:

当Action Bar隐藏时,系统将调整活动的内容来填充可用的屏幕空间。

注意:如果移除了使用主题的Action Bar,那么该窗口将完全禁用Action Bar而无法再在运行时添加——调用getActionBar()将返回null。

添加动作项目

动作项目仅仅是Option Menu中被声明要直接显示在Action Bar上的菜单项目。一个动作项目可以包括一个图标和/或文本。如果一个菜单项目不是动作项目,那么系统将把它放在浮动式菜单中,用户可以通过选择Action Bar右侧的菜单图标打开浮动式菜单。

当活动启动时,系统将通过调用onCreateOptionMenu()来为活动生成Action Bar和浮动式菜单。如同在“菜单”开发者指南中所描述的,这是为活动定义Option Menu的回馈方法。

可以指定某一菜单项目作为动作项目显示——如果有这样的空间的话——通过在菜单资源中为<item>元素声明android:showAsAction=”ifRoom”。这样,该菜单项目仅会在空间足够时显示在Action Bar中以供快速选择。如果空间不足,该项目将被置于浮动式菜单中(通过Action Bar右侧的菜单图标打开)。

可以在应用程序代码中声明一个菜单项目为动作项目,只需对MenuItem调用setShowAsAction()并传递SHOW_AS_ACTION_IF_ROOM。

如果菜单项目同时提供了标题和图标,那么动作项目默认只显示图标。如果希望让动作项目包含文本,需要在XML中添加”with text”旗标、对android:showAsAction属性添加withText,或是在程序代码中调用setShowAsAction()并使用SHOW_AS_ACTION_WITH_TEXT旗标。图2展示了有两个带有文字的动作项目以及浮动式菜单图标的Action Bar。

图2. 有两个动作项目以及浮动式菜单图标的Action Bar的屏幕截图。

下面是一个在菜单资源文件中将菜单项目声明为动作项目的一个范例:

在这时,ifRoomwithText旗标都被设置了,所以当这个项目作为动作项目显示时,它包含了图标以及标题文本。

Action Bar中的菜单项目和Option Menu中的其他项目都会启发相同的回馈方法。当用户选择了一个动作项目时,活动将收到一个onOptionsItemSelected()的调用,并传递项目ID。

注意:如果用片段中添加菜单项目,那么该片段相应的onOptionsItemSelected()方法将被调用。然而活动可以在此之前先对其进行处理,也就是说系统对活动的onOptionsItemSelected()调用要先于对片段的。

可以声明一个项目总是作为动作项目出现,不过这并不被推荐,因为这会在有太多的动作项目时让UI变得混乱,Action Bar中的动作项目将会项目重叠。

关于菜单的更多信息,请参见“菜单”开发者指南。

将应用图标用作菜单项目

应用程序图标默认出现在Action Bar的左侧。它也将响应用户交互操作(当用户点击它时,它会有和其他动作项目一样的视觉响应),但需要手动指定用户点击时的行为。

图 3. Email的Action Bar,左侧有应用程序图标。

通常的行为应当是在用户单击图标时让应用程序回到“主活动”或是(比如,在活动没有发生变化而片段变化了的时候)回到初始状态。如果用户已经处于主活动或是初始状态,则不必进行任何处理。

当用户单击图标时,系统以android.R.id.home的ID调用该活动的onOptionsItemSelected()方法。因此,需要在onOptionsItemSelected()方法中添加一个条件判断来侦听android.R.id.home并执行正确的行为,例如启动主活动或是将最近的片段事务出栈。

如果通过返回主活动来响应应用图标的点击,那么需要在Intent内包含FLAG_ACTIVITY_CLEAR_TOP旗标。这样,如果要启动的活动已经存在于当前任务的话,所有在其上的活动将被销毁,该活动将回到最上层。这种方式是令人满意的,因为回到“主活动”和“返回”是等价的,不应该为主活动创建新的实例。否则,最终在当前任务中会有一个很长的活动栈。

例如,下面是一个onOptionsItemSelected()的实现,它将返回应用程序的“主活动”:

使用应用图标来进行”向上一级“导航

还可以使用应用程序的图标来为用户提供“向上一级”的导航。这在程序中的活动总是以某种固定的顺序出现并期望用户能方便地返回上一级活动的情况下特别有用(不过无所谓用户是怎样进入当前的活动的)。

响应这一事件的方式和返回主活动的方式是相同的(和上文所说的类似,只不过现在是根据当前的活动是哪一个来启动另一个不同的活动)。为了告诉用户这时的情况将有所不同,唯一需要做的就是把Action Bar设为“show home as up”。这通过对活动的ActionBar调用setDisplayHomeAsUpEnabled(true)即可完成。这时,系统将为应用程序的图标增加一个表示向上一层动作的箭头,就像图4这样。

图 4. Email应用的标准图标(上)和“向上一层”图标(下)。

例如,下面是将应用图标表示为“向上一层”动作的方法:

之后,活动应当在用户单击图标时进行响应,在onOptionsItemSelected()中,通过监听ID android.R.id.home(如上所示)。在这种情况下,当向上导航时,在Intent中使用FLAG_ACTIVITY_CLEAR_TOP旗标是更为重要的,这样才能不在上级活动的实例已经存在时还再次创建一个新的。

添加一个动作视图(Action View)

动作视图是在Action Bar上出现的一个控件,作为动作项目的一种替代。例如,如果在Option Menu中有一个项目是“搜索”,那么当该项目作为动作项目使用时可以在Action Bar中为该项目添加一个提供SearchView控件的项目。

当为一个菜单项目添加动作视图时,允许该项目在没有出现于Action Bar时仍能作为一个通常的菜单项目执行指令是很重要的。例如,一个执行搜索工作的菜单项目应该默认弹出Android搜索对话框,但如果该项目被置于Action Bar的话,动作视图将以SearchView控件的形式显示。图4展示了一个动作视图的SearchView控件的范例。

图4. 带有一个SearchView控件的动作视图。

为一个项目声明动作视图最好的方式是在菜单资源中使用android:actionLayout或是android:actionViewClass属性:

  • android:actionLayout的值必须是指向一个布局文件的资源指针。例如:

  • android:actionViewClass的值必须是一个所要使用的View的完整类名。例如:

必须包含android:showAsAction=”ifRoom”以使项目在空间足够时作为动作视图显示。不过,在必要时,可以通过设置android:showAsAction“always”来强制该项目以动作视图显示。

现在,当菜单项目作为一个动作项目显示时,它将是一个动作视图而不是图标和/或标题文本。不过,如果在Action Bar中没有足够的空间的话,该项目将在浮动式菜单中以一个通常菜单项目的形式显示,必须在onOptionsItemSelected()回馈方法中响应该项目。

当活动首次启动时,系统通过调用onCreateOptionsMenu()生成Action Bar和浮动式菜单。当菜单在该方法中被展开之后,可以通过以菜单项目的ID调用findItem()来获取动作视图的元素(比如为了绑定监听器),而后对所返回的MenuItem调用getActionView()。例如,上面的例子里的搜索控件可以像这样获得:

关于使用搜索控件的更多信息,请参见“创建一个搜索接口”。

 添加标签(tab)

动作栏可以显示标签以使用户在活动内的不同片段之间导航。每一个标签可以包含一个标题和/或一个图标。

图 6. 在Honeycomb版本的图库范例程序中的Action Bar中的标签的截图。

首先,布局必须在每一个和显示的表现相关联的Fragment中包含一个View。要确保该视图有一个能在代码中引用的ID。

要添加标签至Action Bar:

1. 创建一个ActionBar.TabListener的实现来处理Action Bar标签的交互事件。比如实现所有的方法:onTabSelected()、onTabUnselected()和onTabReselected()。

每一个回馈方法都将传递收到了事件的ActionBar.Tab和一个FragmentTransaction用以执行片段事务(添加或是移除片段)。

例如:

这种ActionBar.TabListener的实现添加了一个保存与标签关联的Fragment的构造函数以使每一个回馈函数可以添加或是移除片段。

2. 在onCreate()时通过在Activity中调用getActionBar()来获取活动的ActionBar(不过要注意需要在调用了setContentView()之后才这么做)。

3. 调用setNavigationMode(NAVIGATION_MODE_TABS)来开启ActionBar的标签模式。

4. 为Action Bar创建所有的标签:

1. 通过对ActionBar调用newTab()来创建新的ActionBar.Tab。

2. 通过调用setText()和/或setIcon()来为标签添加文字和/或图标。

提示:这些方法返回相同的ActionBar.Tab实例,所以可以将这些调用一起使用。

3. 声明ActionBar.TabListener,并通过向setTabListener()传递其实现的一个实例来使用它。

5. 通过对ActionBar调用addTab()来添加所有的ActionBar.Tab,并传递它们。

例如,下面的代码结合了步骤2-5来创建了两个标签并将它们添加至了Action Bar:

标签被选中时所有需要进行的行为都必须在ActionBar.TabListener回馈方法中被定义。当一个标签被选中,它将收到一个onTabSelected()的调用,应当在这里通过使用add()及所提供的FragmentTransaction来向布局中指定的视图添加正确的片段。类似的,当一个标签被取消选中时(因为另一个标签被选中了),则应当使用remove()来从布局中移除该片段。

注意:不能为这些事务调用commit()——系统会调用它,如果手动调用的话将会抛出一个例外。同时也不能将这些片段事务添加至返回栈。

如果活动被中止,应当保存当前选中的标签,这样当用户返回应用程序时,可以打开该保存的标签。在保存状态时,可以通过getSelectedNavigationIndex()获取当前选中的标签。这将返回所选标签的索引位置。

注意:应该在必要时保存每一个片段的状态,这样用户在切换标签并返回前一个片段时能保持之前的样子。关于保存片段状态的更过信息,请参见“片段”开发者指南。

添加下拉式导航

 

由于官方对文档进行了更新,有较大的改动。因此对于本文的翻译暂时中止,将在之后进行重新翻译。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

片段(Fragment)

一个片段代表了一个Activity的一种行为或是其用户界面的一个区域。可以在一个单独的活动中组合多个分片来组建一个多面板UI,并在不同的活动中多次利用同一个分片。可以把片段理解为一个活动的一个模块化部分,有其自己的生命周期,接收其自己的输入事件,并且可以在活动运行过程中添加或移除一个片段。(译注:网络上也有“碎片”的译法,不过目前还并没有统一的名称。在这里姑且称为“片段”,个人感觉比“碎片”听起来舒服一些 :)  )

一个片段必须被嵌在一个活动中,它的生命周期与该活动的有着紧密联系。例如,当活动被暂停(pause),该活动中所有的片段也会暂停,当活动被销毁(destroy),其中所有的片段也会被销毁。不过,当活动在运行时(处于resumed生命周期状态),可以单独改变每一个片段,如添加或是删除它们。当进行这样的片段处理时,还能将片段加入一个由该活动管理的返回栈——每个活动中的返回栈条目是一段发生过的片段处理的记录。返回栈允许用户通过按下BACK键撤销一个片段事务(反向导航)。

在添加一个片段作为活动布局的一部分时,它将存在于活动的视图层级的ViewGroup中,并定义其自有的视图布局。可以通过在活动布局文件中以<fragment>元素声明片段,或是在程序代码中添加至已有的ViewGroup)来将一个片段插入活动的布局之中。不过,片段并不一定要是活动布局的一部分;还可以将片段作为一个活动的不可见部分使用。

本文档描述了如何使用片段来构建应用程序,包括片段如何在被加入活动的返回栈时维护其状态,与活动及活动内的其他片段共享事件,以及和活动的动作条相结合等。

设计理念

Android在Android 3.0(API级别“Honeycomb”蜂巢)中引入了片段,用以在平板等大屏幕上支持更为动态而灵活的UI设计。由于平板等屏幕比手机的要大许多,因此有更多的空间来交互组合UI组件。片段可以在不必考虑视图层级的复杂操作的情况下实现这种设计。通过把活动的布局分成一个个片段,就可以在运行时改变活动的外观并在该活动所管理的返回栈中保存这些变化。

例如,一个新闻程序可以在左侧用一个片段来展示条目列表,在另一边的片段中显示一条条目——两个片段同时显示在一个活动中的两边,且都有自己的生命周期回馈方法并处理其自有的用户输入事件。因此,相比一个活动选择条目另一个活动阅读条目,现在用户可以在同一个活动中选择条目并阅读,就像图1所示的那样。

图1 一个演示了如何通过片段将两个独立的UI模块并入一个活动的范例

一个片段应该是程序中的一个模块化的可重用的组件。也就是说,因为片段定义了其自有的布局以及使用了自有生命周期回馈方法的自有行为,所以可以将一个片段用在多个活动之中。这是很重要的一点,它使得能够在不同屏幕尺寸上提供不同的用户体验。例如,可以只在屏幕尺寸足够大时才在一个活动中包含多个片段,否则,就将不同片段分在不同的活动中使用。

例如——还是那个新闻程序的例子——程序可以在运行于一个超大屏幕设备(extra large screen,比如平板电脑)时将两个片段嵌入在一个Activity A中。不过,在普通尺寸屏幕的设备(例如,手机)上,就没有足够的空间放下两个片段,所以Activity A仅包含了条目列表功能的片段,而当用户选择了一个条目时,它将启动包含了阅读条目的片段的Activity B。因此,如图1所示程序将同时支持两种设计模式。

创建一个片段

要创建一个片段,必须创建一个Fragment(或它的一个已有的子类)的子类。Fragment类的代码看起来和一个Activity很相似。它包含了和一个活动类似的回馈方法,例如onCreate()、onStart()、onPause()和onStop()。事实上,如果要把一个已有的Android程序改为通过片段来实现,只需简单地把代码中的活动的回馈方法改为相应的片段的回馈方法。

通常,至少需要实现以下的生命周期方法:

onCreate()

系统在创建片段时将调用这个方法。在其实现中,应当初始化那些希望在该片段暂停或停止时被保留的必要组件以供之后继续使用。

onCreateView()

系统在片段第一次绘制其用户界面时将调用这个方法。要绘制片段的UI,就必须从这个方法返回一个片段布局的根View。如果片段不提供UI,可以只返回一个null。

onPause()

系统将调用该方法作为用户将要离开该片段的第一个标志(尽管这不意味着片段一定就会被销毁)。通常应当在这里保存当前用户进行的操作(因为用户之后或许不会返回该片段了)。

大部分的程序应当为每一个片段至少实现以上三个方法,不过还有一些其他的回馈方法可以用来处理片段生命周期的不同阶段。所有的生命周期回馈方法会在之后的“处理片段生命周期”一节再讨论。

图2. 一个片段的生命周期(当其所属的活动处于运行状态时)

除了Fragment基类之外,还有一些可供继承的子类:

DialogFragment

显示一个浮动对话框。可以用该类来创建对话框而不是用Acitivity类中的对话框辅助方法来创建,这样就可以将一个片段对话框加入由活动所管理的片段的返回栈,令用户可以返回到一个已被舍弃的片段。

ListFragment

显示一个由某一适配器(例如SimpleCursorAdapter)管理的项目列表,类似于ListActivity。它提供了好几种管理列表视图的方法,例如onListItemClick()回馈方法以处理点击事件。

PreferenceFragment(偏好设置片段)

以列表方式显示一个Preference对象的层级,类似于PreferenceActivity。当为程序创建“设置”活动时这是很有用的。

添加用户界面

一个片段通常被用于一个活动的用户界面,其自有的布局将成为该活动的一部分。

要为一个片段提供布局,就必须实现onCreateView()回馈方法,Android系统将在该片段绘制其布局时调用它。该方法的实现必须返回一个片段布局的根View。

注意:如果片段是一个ListFragment的子片段,那么默认的实现将从onCreateView()返回一个ListView,因此不必另外去实现该方法。

要从onCreateView()返回一个布局,可以从一个定义于XML中的布局资源中生成它。为此,onCreateView()提供了一个LayoutInflater对象。

例如,这里有一个Fragment的子类从example_fragment.xml文件中读取了一个布局:

被传递给onCreateView()的container参数是上一级的ViewGroup(来自于该活动的布局),片段的布局将被插入其中。savedInstanceState参数是在返回片段时提供之前片段实例数据的一个Bundle(将在“处理片段生命周期”一节中更深入地讨论状态恢复)。

创建一个布局

在上面的范例中,R.layout.example_fragment是指向保存于程序资源中的一个名为example_fragment.xml布局资源的引用。关于如何在XML中创建一个布局的更多信息,请参见“用户界面”文档。

inflate()方法需要三个参数:

  • 希望生成的布局的资源ID
  • 要生成的布局的父级ViewGroup。为了使系统将布局参数传递给生成的布局的根视图,就需要传递该container。这将由当前正在运行的父视图来决定。
  • 一个表明了生成的布局在生成过程中是否应该和ViewGroup(即第二个参数)相关联的布尔值。(在这个例子里,因为系统已经将生成的布局插入到了container内所以值为false——如果是true则会在最终的布局中产生一个冗余的视图组。)

现在已经了解了如何创建一个提供了布局的片段。接下来,需要将该片段添加到活动中去。

向活动添加一个片段

通常,片段作为宿主活动的UI的一部分嵌于该活动整体视图层级之中。有两种方式可以将一个片段添加到活动的布局:

  • 在活动的布局文件里声明该片段。

这种情况下,如果片段是一个视图,可以为其指定布局属性。例如,这里有一个含有两个片段的活动的布局文件:

 <fragment>中的android:name属性指定了用来实例化布局的Fragment类。

当系统创建该活动布局时,将实例化在布局中指定的每一个片段并为其调用onCreateView()方法来检索每一个片段的布局。系统将在<fragment>元素处直接插入由该片段返回的View。

注意:每一个片段需要在系统中有一个唯一的标识用以在活动重启时还原该片段(并且用于获取该片段以执行如移除该片段之类的操作)。有三种方式提供一个片段的ID:

    • android:id属性提供唯一的ID。
    • android:tag属性提供唯一的字符串。
    • 如果没有提供以上两种标识,系统将使用其容器视图的ID。

 

  • 或者,在程序中将片段添加至已有的ViewGroup。

在活动正在运行中的任意时刻,都可以将片段添加至活动的布局。只需要指定这一需要放置片段的ViewGroup即可。

要在活动中进行片段事务(例如添加、移除或替换一个片段),必须使用FragmentTransaction所提供的API。可以像这样在Activity中获取一个FragmentTransaction的实例:

之后可以用add()方法添加一个片段,指定要添加的片段以及要插入该片段的视图。例如:

传递给add()的第一个参数是该片段应当被放置的ViewGroup,通过资源ID来指定,第二个参数则是要添加的片段。

一旦通过FragmentTransaction进行了变更,比如调用commit()来使变更生效。

添加没有UI的片段

上面的范例演示了如何添加提供了UI的片段至活动中。不过,也可以通过使用一个片段来在活动中执行后台行为而不显示额外的UI。

要添加没有UI的片段,需要在活动中通过使用add(Fragment, String)来完成(为片段提供一个唯一的”tag”字符串而非一个视图ID)。这样就能添加该片段,但是,因为它没有和活动布局中的某个视图相关联,所以将不会收到onCreateView()的调用。因此不需要去实现这个方法。

为片段提供一个字符串标签(tag)对于无UI片段来说并不是很严格的要求——也能为包含UI的片段提供标签——但如果片段不含UI,那么字符串标签就是唯一能识别该片段的方式了。如果希望之后能从活动中获取该片段,需要使用findFragmentByTag()。

关于将片段作为后台工作部件而不含UI的活动的范例,请参见FragmentRetainInstance.java范例。

管理片段

要在活动中管理片段,需要使用FragmentManager。可以通过在活动中调用getFragmentManager()来获取它。

借助FragmentManager可以做到包括以下这些事:

  • 通过findFragmentById()(适用于在活动布局中提供了UI的片段)或findFragmentByTag()(对于提供了或没有提供UI的片段都适用)来获取已存在于活动之中的片段。
  • 通过popBackStack()将片段从返回栈中弹出(模拟了一次用户按下BACK键的指令)。
  • 通过addOnBackStackChangedListener()为返回栈的变化注册一个监听器。

关于这些方法的更多信息,请参见FragmentManager类的文档。

正如在之前一节中所描述的,还可以使用FragmentManager来打开一个FragmentTransaction,以能够执行例如添加、移除片段等事务。

执行片段事务(Fragment Transaction)

在活动中使用片段的一大特点是可以根据用户交互对这些片段进行添加、移除、替换或是执行其他操作。对活动进行的每一组改变都被称为是一次事务(transaction),可以通过FragmentTransaction提供的API执行事务。还可以把每一个事务都保存至活动所管理的返回栈,使得用户可以撤销片段的改变(和返回上一个活动类似)。

可以像这样从FragmentManager获取一个FragmentTransaction的实例:

每一个事务都是一组能同时执行的变更。可以通过对给定操作使用如add()、remove()和replace()等方法在设置所有希望执行的变更。之后,必须对活动调用commit()来应用该事务。

不过,在调用commit()之前,需要调用addToBackStack(),以将该事务加入片段事务的返回栈中。该返回栈由活动所管理,允许用户通过按下返回键来返回到之前的片段状态。

例如,下面展示了如何替换一个片段,并将之前的状态保存于返回栈中:

在这个范例里,newFragment替代了当前在由ID R.id.fragment_container定义的布局容器中的(任意的)片段。通过调用addToBackStack(),替换事务被保存于返回栈中,因此用户可以通过按下返回键来回滚事务,回到之前的片段。

如果将多次变更添加至事务(例如第二个add()或是remove())并调用addToBackStack()的话,那么所有在调用commit()之前被应用的变更都将作为一个单独的事务被加入返回栈中,BACK键将会恢复所有这些变更。

向FragmentTransaction中添加变更的顺序是无所谓的,不过:

  • 必须在最后调用commit()
  • 如果将多个片段加入同一个容器中,那么添加的顺序将决定它们在视图层级中的顺序

如果在执行移除片段的事务时没有调用addToBackStack(),那么该片段将在事务被执行(commit)后被销毁,用户无法再次返回它。反之,如果在移除片段时调用了addToBackStack(),那么该片段将被中止(stop),并在用户返回时被继续(resume)。

提示:对于每个片段事务,都可以通过在执行(commit)之前调用setTranstition()以应用一个切换动画。

调用commit()并不会立即执行事务。它只是作了在活动的UI线程(“主”线程)准备好之时运行该事务的调度。不过,如有必要,可以在UI线程中调用executePendingTransactions()来立即执行由commit()提交的事务。只有在该事务是其他线程工作的组成部分时才有必要这么做。

注意:可以仅在活动保存其状态之前(当用户离开活动时)使用commit()来执行一次事务。如果在此之后执行,将抛出一个异常。这是因为如果活动需要被储存的话,执行之后的状态将无法被保存。对于丢失状态也无妨的情况,则需使用commitAllowingStateLoss()。

和活动进行通信

尽管一个Fragment被作为一个独立于Activity的对象使用,且可以被用于多个活动之中,一个给定的片段实例可以与包含它的活动直接关联。

特别要注意的是,片段可以以getActivity()来获取Activity的实例,并能够在活动的布局中很容易地进行寻找视图之类的任务:

类似的,活动可以通过findFragmentById()或findFragmentByTag()从FragmentManager获取一个Fragment的引用来调用片段内的方法。例如:

创建活动接受的事件回馈

在有些情况下,可能会需要片段与活动共享事件。这可以通过在片段定义一个回馈接口并在宿主活动中实现它来实现。当活动通过该接口接收到了一个回馈时,如有需要,它可以与布局中的其他片段共享信息。

例如,如果一个新的应用程序在一个活动中有两个片段——一个用来显示一列文章(片段A)而另一个用来显示某一篇文章(片段B)——那么片段A必须在某一个列表项目被选中时告知活动,使活动能够告知片段B来显示该文章。在这种情况下,OnArticleSelectedListener接口被声明于片段A内:

之后持有该片段的活动实现了OnArticleSelectedListener接口并覆盖了onArticleSelected()来将来自于片段A的事件通知给片段B。为确保宿主活动实现了该接口,片段A的onAttach()回馈方法(当把片段添加至活动时系统将会自动调用该方法)通过将传递过来的Activity强制转换为了onAttach()以实例化了一个OnArticleSelectedListener的实例:

如果该活动没有实现这个接口,那么片段将会抛出一个ClassCastException。一旦成功实现了该接口,mListener成员将持有一个活动的OnArticleSelectedListener实现的引用,因此片段A将可以通过调用由OnArticleSelectedListener接口定义的方法和活动共享事件。例如,如果片段A是ListFragment的一个继承,那么每次用户点击一个列表项目时,系统将调用片段的onListItemClick(),它将之后调用onArticleSelected()来与活动共享该事件:

传递给onListItemClick()的id参数是被点击项目的行ID,它被活动(或其他片段)用来从应用程序的ContentProvider中获取文章。

关于使用内容提供器的更多信息请参见“Content Providers文档”。

添加项目至动作条(Action Bar)

片段可以通过实现onCreateOptionsMenu()方法来和活动的选项菜单(Options Menu)项目相结合使用(因此也就可以和动作条一起使用)。不过,为了使该方法能够接受调用,必须在onCreate()中调用setHasOptionsMenu()来标识该片段将被添加至选项菜单(否则,该片段将不会接收到对onCreateOptionsMenu()的调用)

之后从片段添加至选项菜单的任何项目都将被增加到已有菜单项目之后。该片段还将在某一菜单项目被选中时收到onOptionsItemSelected()的回馈。

也可以在片段布局中注册一个视图,通过调用registerForContextMenu()来提供上下文菜单。当用户打开上下文菜单时,该片段将收到一个对onCreateContextMenu()的调用。当用户选中一个项目时,片段将收到一个对onContextItemSelected()的调用。

注意:尽管片段对每一个添加过的项目都能收到一个项目选中回馈,但活动本身将在用户选择菜单项目时首先收到相应的回馈。如果活动的项目选中回馈的实现没有处理该选中的项目,那么该事件才会被传递给片段的回馈方法。对于选项菜单和上下文菜单来说都是如此。

关于菜单的更多信息,请参见“菜单”和“动作条”开发者指南。

处理片段生命周期

管理一个片段的生命周期和管理一个活动的生命周期十分的相像。如同活动一样,一个片段可以以三种状态存在:

Resumed

该片段在运行中的活动里是可见的。

Paused

另一个活动正处于前台且获得了焦点,不过含有该片段的活动依然是可见的(前台的活动是半透明的或没有覆盖整个屏幕 )。

Stopped

片段不可见。或是宿主活动被停止了,或是片段被从活动中移除后加入了返回栈。一个被停止的片段依然是存在着的(所有的状态和成员信息由系统保持着)。不过,它对于用户不再可见,在活动被杀除后也将被杀除。

和一个活动一样,可以通过一个Bundle以在活动的进程被杀除而又需要在该活动被重建时还原该片段的情况下保存片段的状态。可以在活动的onSaveInstanceState()回馈方法中保存状态,在onCreate()、onCreateView()或onActivityCreated()中还原状态。关于保存状态的更多信息,请参见“活动”文档。

图 3. 活动生命周期对于片段生命周期的影响

一个活动和一个片段的生命周期之间最大的区别在于它们是如何被保存于各自的返回栈中的。一个活动默认为在其被停止时被放入由系统所管理的一个返回栈中(这样用户就能通过BACK键返回该活动,就像“任务和返回栈”中所说的那样)。然而, 一个片段只有在进行片段移除的事务中调用addToBackStack()来显式地请求保存片段的实例时才会被放入由宿主活动所管理的返回栈中。

除此之外,管理片段的生命周期和管理活动的生命周期十分相似。因此,管理活动生命周期时的做法同样可以应用于管理片段。所需要理解的就仅仅是活动的生命周期将会地片段的生命周期有着怎样的影响。

范例

为了总结本文档中所说的,下面有一个范例来演示一个使用了两个片段的活动是如何创建一个双面板布局的。下面的这个活动包含了一个显示一列莎士比亚戏剧标题的片段和另一个在从列表中选择项目后显示该戏剧的摘要的片段。它还演示了如何基于不同的屏幕配置来提供不同的片段配置。

注意:该活动的完整源代码可以在FragmentLayout.java中得到。

主活动按照常规的方式在onCreate()中应用布局:

被应用的布局是fragment_layout.xml

使用该布局,系统将在活动载入该布局时实例化TitlesFragment(它列出了戏剧标题),而FrameLayout(它显示戏剧的摘要)将占用屏幕的右侧(不过最初它是空着的)。可以发现,直到用户从列表中选择了一个项目之后某一个片段才会被放入FrameLayout。

不过,不过所有的屏幕配置都足够宽来在两边显示戏剧列表和摘要。所以上面的布局仅用于横屏模式,需要保存为res/layout-land/fragment_layout.xml

因此,当屏幕是竖向时,系统将应用下面的保存于res/layout/fragment_layout.xml的布局:

该布局仅包含了TitlesFragment。这意味着当设备处于竖向状态时,只有戏剧标题列表是可见的。因此,当用户在这时点击了列表项目的话,程序会启动一个新的活动来显示摘要,而不是载入第二个片段。

接下来,看一下如何在片段类中完成整个过程。首先是TitlesFragment,它显示了一列莎士比亚戏剧标题。该片段继承于ListFragment,通过它来处理大部分的列表视图工作。

正如代码中所表明的,需要注意当用户点击一个列表项目时有两种可能的行为:这取决于两种布局中的哪一种使处于激活状态的。它既可以在同一个活动中创建并表示一个新的片段来显示详细信息(将片段加入FrameLayout),也可以启动一个新的活动(并在那里显示片段)。

第二个片段,DetailsFragment显示了从TitlesFragment列表中所选择的戏剧项目的摘要:

回忆一下TitlesFragment类,如果用户点击了一个列表项目时当前布局没有包含R.id.details视图的话(DetailsFragment属于该视图),应用程序将启动DetailsActivity活动来显示项目的内容。

下面是DetailsActivity,它仅仅内嵌了DetailsFragment以在屏幕处于竖屏模式时显示所选择的戏剧摘要:

注意这个活动在横屏模式时将会自动结束,改为由主活动同时显示DetailsFragmentTitlesFragment。而在用户以竖屏模式启动DetailsActivity之后旋转屏幕至横屏模式的情况下也会如此(此时将重启当前活动)。

关于使用片段的更多范例(以及本范例的完整源文件),请参见ApiDemos里的范例代码(可以从范例SDK组件中下载)。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

构造自定义组件

Android提供了一种高级而强大的自定义模型来构建UI,这一模型基于布局类View和ViewGroup来实现。首先,Android平台包含了一系列的预构建View和ViewGroup子类——被称为控件(widget)和布局(layout)——用以构造UI。

可用的控件包括但不限于Button、TextView、EditText、ListView、CheckBox、RadioButton、Gallery、Spinner及一些特殊用途的AutoCompleteTextView、ImageSwitcher和TextSwitecher。

可用的布局包括LinearLayout、FrameLayout、RelativeLayout等。范例请参见“常见布局对象”。

如果预构建的控件或布局无法满足需要,可以创建自有的View子类。如果只是要对现有的控件或布局作小调整,可以继承该控件或布局并覆盖其方法。

通过创建自有的View子类,将可以对于屏幕元素的外观和功能进行精确控制。要了解能够对自定义视图进行哪些控制,可以参考下面的例子:

  • 可以创建一个完全自定义渲染的View类型,例如使用2D图像渲染的有些类似于模拟电路控制的“音量控制”按键。(自己也不太清楚analog electronic control是什么,总之这个例子是说可以自定义按键的外观)
  • 可以将一组View组件结合为一个新的单独的组件,比如类似于ComboBox(一种弹出式列表和文本输入栏的结合体)的东西,或是双栏选择器(一种有左右两栏的列表,可以重新分配其中的某个项目应该属于哪一栏),等等。
  • 可以覆盖EditText组件在屏幕上的渲染方式(Notepad教程中通过这种方式创建了具有划线的记事本页面,取得了良好效果)
  • 可以捕捉例如按键等的其他事件并以自定义方式处理它们(例如在游戏中)。

下面的小节阐述了如何创建自定义View并将其用于程序之中。更为详细的参考信息,请参见View类。

基本方式

这里是对要创建自有View组件所需了解的内容的一个高度预览:

  •  用自有类继承一个View类或其子类。
  • 覆盖其中的一些方法。需要覆盖的是那些父类中以“on”开头的方法,例如,onDraw(),onMeasure()和onKeyDown()。这和在Activity或是ListActivity中覆盖那些和生命周期与功能相关on…事件相类似。
  • 使用该新类。一旦完成,自有的新类可以被用于其父类能被用于的地方。

 提示:继承的类可以在使用它们的活动中被定义。这样就限制了对其不必要的使用,很有用(因为可能会为了更广泛的用途而需要在程序中创建另一个新的公用View)。

 完全自定义的组件

完全自定义的组件可以被用来创建任何希望呈现的图形组件。也许是一个看起来像是老式类比指示条的音量指示器,或是卡拉OK机上唱歌时会有一个随着歌曲的进度弹跳着的小球的一长条的文本视图。总之,有时无论怎样组合预置组件也无法达到某种效果,因而需要另外的自定义组件。

幸运地,可以轻松地创建具有所希望的外观和行为的组件,仅有的限制只是想象力,屏幕尺寸,可用的处理器能力(要记得最终程序不得不运行于比桌面工作站性能弱很多的设备上)。

要创建一个完全自定义的组件:

  1. 也许有些出乎意料,通常要继承的视图正是View,因此创建新的组件往往以继承View类开始。
  2. 可以提供一个构造函数以从XML中获取属性和参数,也可以自己定义这些属性和参数(比如音量指示器的色彩和范围,指针的宽度和阻尼等。)
  3. 有可能需要为该组件创建自有的事件监听器,属性继承器和修改器,或是其他更为深入的功能。
  4. 通常都需要覆盖onMeasure()方法,如果希望组件显示内容,常常onDraw()方法也要覆盖。这两者都具有其默认行为,默认来说onDraw()什么也不做,而onMeasure()则总是设置尺寸为100×100——这可能不是所希望的属性。
  5. 如有需要其他的on…方法也应该被覆盖。

 继承onDraw()和onMeasure()

onDraw()方法将传递一个Canvas,在该Canvas上可以实现任何所需内容:2D图形、其他自定义组件、个性化的文本内容或其他所能想到的。

注意:这并不适用于3D图形。如果希望使用3D图形,必须继承SurfaceView而不是View,并在另一个线程中绘制。参见GLSurfaceViewActivity范例以了解更多。

onMeasure()则稍微复杂一些。onMeasure()是组件与其容器之间的一种渲染约束。它应该被覆盖以高效而准确地报告其所包含内容的尺寸度量。由于其父级(它被传递给onMeasure()方法)限制的要求,又由于一旦完成计算后需要以测量的宽度和高度来调用setMeasuredDimension(),整件事变得有些复杂。如果没能通过一个被覆盖的onMeasure()方法来调用它的话,结果将会是在测量时抛出一个例外。

从整体上来看,实现onMeasure()大致是这样的:

  1. 被覆盖的onMeasure()方法被以宽度和高度的测量值(即widthMeasureSpec以及heightMeasureSpec参数,它们都是用来表示维度的整型值)调用,这是对于要处理的宽度和高度的测量值的一种限制。这些参数能够要求的所有限制的参考资料请见View.onMeasure(int, int)相关的文档(该参考文档同时很好的解释了整个测量操作)。
  2. 组件的onMeasure()方法应当计算出渲染该组件所需的宽度和高度的测量值。它通常应当和传入的参数相匹配,不过有时也可以选择扩大(这时,父类可以根据不同的测量值选择包括切割、滚动、抛出例外或请求onMeasure()重试等一系列操作。)
  3. 一旦宽度和高度被计算出来,就必须以它们去调用setMeasuredDimension(int width, int height)。否则将导致抛出例外。

下面是关于视图的框架可能调用的其他标准方法的一些总结:

类别 方法 描述
创建 构造函数 在通过代码创建视图时和在视图通过布局文件生成时都会需要一些构造函数。第二种情况下布局文件中定义的任何属性都需要被读取并应用。
onFinishInflate() 当一个视图及其所有子视图都通过XML生成后被调用。
布局 onMeasure(int, int) 调用以决定该视图及其所有子视图所需的尺寸。
onLayout(boolean, int, int, int, int) 当该视图需要指定其所有子视图的尺寸和位置时被调用。
onSizeChanged(int, int, int, int) 当该视图的尺寸发生变化时被调用。
绘制 onDraw(Canvas) 当该视图需要渲染其内容时被调用。
事件处理 onKeyDown(int, KeyEvent) 当一个新的按键事件发生时被调用。
onKeyUp(int, KeyEvent) 当一个按键释放事件发生时被调用。
onTrackballEvent(MotionEvent) 当一个轨迹球移动事件发生时被调用。
onTouchEvent(MotionEvent) 当一个触摸屏动作事件发生时被调用。
焦点 onFocusChanged(boolean, int, Rect) 当视图获得或是失去焦点时被调用。
onWindowFocusChanged(boolean) 当包含了该视图的窗口获得或是失去焦点时被调用。
关联(Attaching) onAttachedToWindow() 当该视图与一个窗口相关联后被调用。
onDetachedFromWindow() 当该视图与窗口接触关联后被调用。
onWindowVisibilityChanged(int) 当包含了该视图的窗口的可见性发生改变时被调用。

 一个自定义视图的范例

在API Demos中的CustomView范例提供了一个自定义View的范例。该自定义View被定义在LabelView类中。

这个LabelView范例对自定义组件的各方面进行了演示:

  • 继承View类来创建一个完全自定义的组件。
  • 支持参数并可以接受视图生成参数(在XML中定义的参数)的构造函数。其中一些参数将被传递至父类View,不过更重要的是,一部分参数将被用于在LabelView中定义的属性。
  • 一些该标签组件所需的标准公有方法,比如setText()setTextSize()setTextColor()等等。
  • 被覆盖的onMeasure()方法以决定并设置组件的渲染尺寸。(注意在LabelView中,实际的工作是由私有的measureWidth()方法完成的。)
  • 被覆盖的onDraw()方法以将标签绘制于所提供的画布(canvas)上。

可以在范例中的custom_view_1.xml里看到一些LaberView自定义视图的用例。通常,可以看到android: namespace参数和自定义的app: namespace参数被混合使用。app: parameters是LabelView进行组织并使用的参数,被定义于该范例的R资源定义类中的风格化内部类中。

复合控制器(Compound Control)

如果不想创建一个完全自定义的组件,而是希望将一些可重用的组件组合在一起以获得一组已存在的控制功能的话,就创建一个复合组件(或称为复合控制器)吧。简言之,就是将一些更为基本的控制器(或是视图)装入一个逻辑组使之被看作为是一个单独的组件。例如,一个Combo Box可以被认为是一条单独的EditText域加上一个具有弹出式列表(PopupList)的调节按钮。如果按下按钮并从列表中进行选择,就会弹出一条EditText域,当然用户也可以直接在EditText中键入一些内容。

在Android中,其实已经有两种View可以做到这点了:Spinner和AutoCompleteTextView,不过Combo Box的概念还是可以用来做为一个简单易懂的范例的。

要创建一个复合组件:

  1. 这通常是从某种类型的Layout开始,所以需要创建一个继承了某种Layout的类。在Combo box的例子中或许应该使用一个水平方向的LinearLayout。请记得其他的布局是可以嵌套于其中的,所以复合组件可以做得相当地结构复杂。需要注意的是还可以像Activity所使用的那种方式一样通过声明式(基于XML)的方式来创建所包含的组件,或是在代码中对其进行嵌套。
  2. 在新类的构造函数中,获取父类所需的所有参数,并首先将其传递给父类的构造函数。之后可以设置其他在新的组件中所要使用的视图;在这里将创建EditText域和PopupList。请注意可能还需要向XML引入一些自有属性和参数用以被提取并使用于构造函数。
  3. 还可以为所包含的视图可能会触发的事件创建监听器,例如,一个当进行了列表选择后对EditText的内容进行更新的列表项目点击监听器(List Item Click Listener)的监听器方法。
  4. 还可以通过访问器(accessor)和修改器(modifier)创建自有的属性,例如,允许EditText的值在组件中被初始化并在需要时可以查询获取其内容。
  5. 在继承一个Layout时,不必覆盖其onDraw()onMeasure()方法,因为该布局的默认行为就能很好的实现效果。不过,如有需要还是可以对其进行覆盖。
  6. 可以覆盖其他的on…方法,例如onKeyDown(),用于某个键被按下时从一个combo box的弹出列表中选择某一个默认值。

总之,使用Layout作为自定义控制器的基础将带来一系列的好处,包括:

  • 可以通过和活动一样的声明式的XML文件来指定布局,或是可以在代码中创建视图并将其嵌入布局中。
  • onDraw()onMeasure() 方法(加上大部分其他的on…方法)的默认行为能正常工作因而不必进行覆盖。
  • 最后,可以很快捷地构建非常复杂的复合视图并重复利用它们。

复合控制器的范例

在SDK附带的API Demos项目中,有两个List范例——Views/Lists下的范例4和范例6演示了一个继承了LinearLayout的SpeechView,它构建了一个显示Speech引用的组件。范例代码中所用的相关的类是List4.javaList6.java

修改一个已有的View类型

在某些情况下可以用一种更简单的创建自定义View的方法。如果已经有了一个和所希望创建的组件非常相似的组件,可以继承该组件并覆盖那些希望改变的行为。可以像对完全自定义的组件那样对这个继承的组件进行任何处理,只不过是需要从View层级中一个更为特殊化的类开始罢了。与此同时还可以额外获得其他很多所需要的功能。

例如,SDK在范例中包含了一个NotePad程序。它演示了使用Android平台的许多方面,包括继承一个EditText View来实现带有划线的记事本。这并不是一个完美的范例,且实现这个效果的API相对这个比较早期的版本可能会发生改变,不过它确实展示了一些使用原则。

如果还没有尝试过,那就将NotePad范例导入到Eclipse(或只是查看所提供的链接中的源代码)。特别是查看NoteEditor.java文件中MyEditText的定义。

其中一些要点如下。

1. 定义

该类由以下代码定义:

public static class MyEditText extends EditText

  • 它被定义为NoteEditor活动的一个内部类,不过因为它是公有的所以可以在需要时从NoteEditor外部以NoteEditor.MyEditText被使用。
  • 它是静态的,意味着它不会生成所谓的“合成方法(synthetic method)”以允许它读取其父类的数据。这同时也意味着它是一个具有独立行为的类,并没有和NoteEditor保持着高度相关性。这是创建不需要读取外部类的数据的内部类时一种比较条理清楚的做法,它使得生成的类较小且能被其他类所使用。
  • 在这里EditText被选作需要自定义的View来被它继承。当完成后,一个新的类可以用来替代通常的EditText视图。

2. 类初始化

和往常一样,super被首先调用。另外,这不是一个默认的构造函数,而是包含了一些的参数。EditText在通过XML布局文件生成时将用到这些参数,因此,新的构造函数也要获取这些参数并将其传递给父类的构造函数。

3. 覆盖方法

在该范例中,只覆盖了一个方法:onDraw() —— 不过当创建自己的自定义组件时很可能需要覆盖更多方法。

对于NotePad范例来书,覆盖onDraw()方法使得能够在EditText视图的画布(canvas)上绘制蓝线(画布被传递给了被覆盖的onDraw()方法)。super.onDraw()方法在该方法结束前会被调用。这一父类方法的调用是必需的,不过在这里,它在完成了所希望的直线的绘制后才被最后执行。

4. 使用自定义组件

现在已经有了自定义的组件,不过该如何使用呢?在NotePad范例中,自定义组件被直接从声明式的布局中使用,所以现在来看下res/layout文件夹下的note_editor.xml

*该自定义组件作为通用视图在XML中被创建,且该类被指定为使用整个包。还要注意所定义的内部类通过NoteEditor$MyEditText标识来引用,这是Java编程语言中引用内部类的标准方式。

如果自定义的View组件没有作为内部类被定义,那么还可以用XML元素名称来声明该View组件而不包括class属性。例如:

注意现在的MyEditText类是一个单独的类文件。当这个类被嵌套于NoteEditor类中时,这样的做法将会有问题。

*定义中的其他属性和参数将被传递至自定义组件的构造函数,之后被传入EditText的构造函数,即这些也是EditText所需要使用的参数。注意还可以增加自有参数,之后还会涉及这一问题。

该范例的说明至此。确实这只是个简单的例子,但它演示了关键点——可以根据需求创建任何复杂的自定义组件。

一个更为复杂的组件可能需要覆盖更多的on…方法并且引入一些自有的方法以完全自定义其属性和行为。唯一的限制就仅仅是想象力和实际需要组件去完成怎样的工作。

 返回用户界面

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

意图和意图过滤器

一个程序的三个核心组件——活动、服务和广播接收者——通过被称为意图(intent)的消息来被激活。意图消息是一种在同一个或不同程序的组件间的运行后绑定机制。意图自身,即一个Intent对象,是包含了对于要执行的操作的抽象描述的被动数据结构——或者,对于广播来说,是对于已发生正在被播报的事件的描述。对于向不同类型的组建传递意图,有不同的机制:

  • 一个Intent对象被传递给Context.startActivity()或Activity.startActivityForResult()以启动一个活动,或是让一个已存在的活动进行新的工作。(它也可以被传递给Activity.setResult()使其向调用了startActivityForResult()的活动返回信息。)
  • 一个Intent对象被传递给Context.startService()以初始化一个服务或向正在运行的服务发出新的指令。同样的,一个意图可以被传递给Context.bindService()以在进行调用的组件和目标服务间建立联系。如果服务不在运行,还可以选择初始化该服务。
  • 被传递给任何广播方法(例如Context.sendBroadcast()、Context.sendOrderedBroadcast()或Context.sendStickyBroadcast())的Intent对象将被发送给所有相关的广播接收者。很多种类的广播在系统代码中被创建。

在任何一种情况下,Android系统将寻找合适的活动、服务或广播接收者来响应意图,在必要时对它们进行初始化。这些消息系统之间没有交集:广播意图仅被发送给广播接收者,而不会给活动或是服务。一个传递给startActivity()的意图将仅被发送给一个活动,而不会是服务或广播接收者,依此类推。

本文档将首先对Intent对象进行描述。之后介绍Android用以映射意图至组建的规则——它如何了解哪一个组建应当接收意图消息。对于没有显式地命名目标组件的意图,这个过程将包括使用与可能对象关联的意图过滤器(intent filter)来测试Intent对象。

Intent对象

Intent对象是一捆数据。它包含了接受该意图的组件所感兴趣的信息(例如要采取的操作及操作的对象数据)及Android系统所需的信息(例如要处理该意图的组件类别及如何启动目标活动的指令)。原则上,它包含以下内容:

组件名称

应当处理该意图的组件名称。这部分是一个ComponentName对象——一种目标组件的完整类名(例如“com.example.project.app.FreneticActivity”)和组件所在程序的manifest文件中的包名称组(例如“com.example.project”)的组合。组件名称的包部分和manifest中的包名称组不一定要相同。

组件名称是可选的。如果被设定,Intent对象将被发送给指定类的实例。如果没有设定,Android使用Intent对象中的其他信息来选择合适的目标——参见本文档中之后的“意图解决方案”。

组件名称通过setComponent()、setClass()或setClassName()来设定,通过getComponent()来读取。

行为

命名了要被执行的行为的字符串——或者,对于广播的意图来说将要发生并被报告的行为。Intent类定义了很多行为常量,包括:

常量 目标组件 行为
ACTION_CALL 活 动 初始化一次电话呼叫。
ACTION_EDIT 活 动 向用户显示数据以供编辑。
ACTION_MAIN 活 动 作为一个任务(task)的首活动启动,不包含数据输入且没有返回的输出。
ACTION_SYNC 活 动 以移动设备上的数据同步服务器上的数据。
ACTION_BATTERY_LOW 广播 接收者 电量低的警告。
ACTION_HEADSET_PLUG 广播接收者 耳机插入或是拔出设备。
ACTION_SCREEN_ON 广播接收者 屏幕被打开。
ACTION_TIMEZONE_CHANGED 广播接收者 时区设定被改变。

参见Intent类的描述以获知通用行为的预定义常量列表。其他的行为在Android API的其他部分被定义。也可以为激活自己的程序中的组件而定义自有的行为字符串。它们应当包含作为前缀的程序包——例如:“com.example.project.SHOW_COLOR”

行为很大程度上决定了意图剩余部分的结构——特别是数据和额外信息——就好像是一个方法的名称决定了一组参数和返回值。因此,最好尽可能具体地使用行为名称,将它们紧密地和意图的其他部分配对。换言之,不要单独定义一个行为,而是要为组件可以处理的Intent对象定义一整套的协议。

一个Intent对象的行为通过setAction()方法设置,通过getAction()来读取。

数据

将被执行操作的数据的URI及数据的MIME类型。不同的行为和不同的数据规格相对应。例如,如果行为域是ACTION_EDIT,那数据域就需要包含要被显示以编辑的文档的URI。如果行为是ACTION_CALL,那数据域需要是一个要拨打的号码的tel: URI。同样地,如果行为是ACTION_VIEW且数据域是http: URI,那接收方活动将会下载或显示该URI指向的任何数据。

在配对一个意图和一个有能力处理其数据的组件时,知道数据的类型(即它的MIME类型)以及URI是很重要的。例如,一个可以显示图像数据的组建不应被调用来播放音频文件。

在许多情况下,数据类型可以由URI获知——特别是content: URI,它表明数据位于设备中并由一个内容提供者所控制(参见另外的关于内容提供者的解说)。不过也可以在Intent对象内显式地设定类型。setData()方法以仅URI来指定数据,setType()仅以MIME类型来指定数据,setDataAndType()同时以URI和MIME类型来指定数据。URI通过getData()读取,类型由getType()读取。

类别

一个包含了应该由哪一种类型的组件来处理意图的额外信息的字符串。Intent对象中可以存放任何数量的类别描述。和行为一样,Intent类定义了一些类别常量,其中包括:

常量 意义
CATEGORY_BROWSABLE 目标活动可以安全地被浏览器用于显示某一链接指向的数据——例如,一幅图像或是一条电子邮件消息。
CATEGORY_GADGET 该活动可以被另一个持有小部件(gadget)的活动嵌于内部。
CATEGORY_HOME 该活动显示主界面(home screen)这一用户启动设备或按下HOME键后首先看到的画面。
CATEGORY_LAUNCHER 该活动是一个任务中的首活动,被列于应用程序启动器的首级。
CATEGORY_PREFERENCE 目标活动是一个偏好设置面板。

 参见Intent类的描述以获知完整的类别列表。

addCategory()方法将一个类别存入一个Intent对象,removeCategory()则删除之前添加的一个类别,getCategories()用于获取当前在意图对象内的整个类别集。

额外信息

关于需要被传递给要处理意图的组件的其他信息的键值对(key-value pair)。如同一些行为与特定类型的数据URI相配对,另一些会与特定的额外信息相配对。例如,一个ACTION_TIMEZONE_CHANGED意图有一个额外信息“time-zone”来识别新的时区,ACTION_HEADSET_PLUG则有一个额外信息“state”来识别耳机是被插入还是拔出,另外还有额外信息“name”来识别耳机的类型。如果想要设计一个SHOW_COLOR行为,颜色的值将被设置于一个额外的键值对之中。

Intent对象有一系列的put…()方法来插入不同类型的额外数据,类似地有一组get…()方法来读取这些值。这些方法和Bundle对象中的相类似。事实上,额外信息可以如同一个Bundle那样通过putExtras()和getExtras()方法来被设置和读取。

旗标

各种类型的旗标。其中很多指示了Android系统如何去启动一个活动(例如,该活动应该属于哪一个任务)以及在启动后应该如何处理它(例如,它是否应该属于最近活动列表)。所有的这些旗标在Intent类中被定义。

Android系统和该平台的应用程序使用Intent对象来发出系统创建的广播及激活系统定义的组件。要了解如果构建一个意图以激活一个系统组件,参见附录中的意图列表。

意图的解决方案

意图可以被分为两类:

  • 显式意图通过其名称指定目标组件(前面提到过,该组件的名称域有一个值集)。因为组件的名称通常不会被其他程序的开发者知道,显式的意图往往被用于程序内部消息——例如一个活动启动一个附属的服务或是一个平级的活动。
  • 隐式意图不会命名目标(组件的名称域是空白的)。隐式意图常用于激活其他活动中的组件。

Android将一个显式意图传递给所指定的目标类的一个实例。只有Intent对象中的组件名称会影响到意图最终将被传送给哪一个组件。

对于隐式意图需要一种不同的策略。由于没有指定的目标,Android系统必须找到最为合适的那一组件(或那一些组件)——一个单独的执行所请求的行动的活动或服务,或是一组广播接收者来响应广播通告。系统通过比较Intent对象的内容和意图过滤器(和组件相关联的用以接收潜在意图的结构)来实现这一目的。过滤器告知了一个组件的功能并界定了它可以处理的意图。它们使得组件可能接收所告知的类型的隐式意图。如果一个组件没有任何的意图过滤器,它将只能接收显示意图。一个拥有意图过滤器的组件可以同时接收显式和隐式意图。

当一个Intent对象被意图过滤器检查时只有三个方面会被考虑:

行为

数据(同时包括URI和数据类型)

类别

额外信息和旗标不会影响到决定由哪个组件接收意图。

意图过滤器(Intent filter)

为了告知系统它可以处理哪些隐式意图,活动、服务和广播接收者都可以拥有一个或多个意图过滤器。每一个过滤器将描述组件的某一种功能以及该组件要接收的意图集。它有效地过滤下所需的类型,排除不需要的意图——不过仅能排除不需要的(未命名目标类的)隐式意图。显式意图将始终被传递给其目标而无论它包含什么内容;过滤器不会起作用。然而一个隐式意图就只有在通过了组件的过滤器之后才能够被传递给组件。

一个组件对每一类它可以处理的工作及用户所能看见的每一种形式分别有着不同的过滤器。例如,范例中Note Pad程序的NoteEditor活动有两个过滤器——一个用于创建用户可以查看并编辑的特定便筏,另一个用于启动一个空的用户可以输入并保存的新的便筏。(Note Pad的所有的过滤器将会在之后的Note Pad范例一节中详细说明。)

一个意图过滤器是IntentFilter类的一个实例。不过因为Android系统必须在启动一个组件之前知道它所具有的功能,所以意图过滤器一般不在Java代码中使用,而是作为程序的manifest文件中(AndroidManifest.xml)的<intent-filter>元素。(不过有一个例外是广播接收者的过滤器是通过调用Context.registerReceiver()动态地在代码中注册的;它们被作为IntentFilter对象直接创建。)

一个过滤器有着和Intent对象中的行为、数据和类别域相对应的域。一个隐式意图将检测过滤器的所有这三个内容。要被传递给某个组件,一个意图必须通过该组件的过滤器的所有三项检测。即使只有一项不满足,Android系统也不会将该意图传递给组件——至少不会是基于过滤器传递给组件。不过,由于一个组件可以有多个意图过滤器,一个意图如果无法通过其中一个过滤器还是有可能能通过其他的的。

过滤器和安全

意图过滤器不能确保安全性。尽管它只让一个组件接受特定类型的隐式意图,它无法阻止显式意图指向目标组件。即使过滤器约束了意图使其只能使用将会被请求处理特定的行为和数据源的组件,一些人仍然可以将一个显式意图与不同的行为和数据源结合在一起,并将目标以组件的名称来命名。

下面将详细描述三种检测是如何进行的:

行为检测

manifest文件中的一个<intent-filter>元素以<action>子元素的形式列出了行为。例如:

如同示例所展示的,一个Intent对象只命名一个单独的行为,而一个过滤器可以列出多个行为。列表不能为空;一个过滤器至少要包含一个<action>元素,否则它将屏蔽所有的意图。

要通过这个检测,在Intent对象中指定的行为必须与过滤器所列出的行为之一相匹配。如果该对象或是过滤器没有指定一个行为,就会有下面的结果:

  • 如果过滤器没有列出任何行为,就没有行为可以与意图相匹配,所有的意图都无法通过检测。没有意图可以通过过滤器。
  • 另一方面,如果Intent对象没有指定任何的行为,它将自动通过检测——只要过滤器含有一个或以上的行为。

类别检测

<intent-filter>也会将类别作为子元素列出。例如:

注意之前描述过的表示行为和类型的常量不在mainfest文件中被使用。而是使用完整的字符串值。例如,在范例中的“android.intent.category.BROWSABLE”字符串和文档之前提到的CATEGORY_BROWSABLE常量相对应。类似地,字符串“android.intent.action.EDIT”ACTION_EDIT常量相对应。

一个意图要通过类别检测,Intent对象中的每一个类别都必须与过滤器中的某个类别相匹配。过滤器可以列出其他更多的类别,但不能省略任何一个意图中含有的类别。

原则上,所以说,一个不含有类别的Intent对象,无论过滤器中有哪些类别,总是能够通过这项检测。这通常是正确的。不过,有一个例外,Android把所有传递给startActivity()的隐式意图视为它们包含了至少这样一个类别:“android.intent.category.DEFAULT”CATEGORY_DEFAULT常量)。因此,要接受隐式意图的活动必须在其意图过滤器中包含有“android.intent.category.DEFAULT”。(设有“android.intent.action.MAIN”“android.intent.category.LAUNCHER”的过滤器不在此范围之中。它们将活动标记为了新任务的开始,并显示在应用启动器(launcher)屏幕上。它们可以在类别列表中包含“android.intent.category.DEFAULT”,不过这并不是必须的。)参见之后的“使用意图匹配”来进一步了解这些过滤器。

数据检测

如同行为和类别,意图过滤器的的数据类型也是作为子元素保存的。而且和它们一样,这些子元素可以出现多次或完全不出现。例如:

每一个<data>元素可以指定一个URI和数据类型(MINE媒体类型)。URI的每一个部分由不同的属性——模式(scheme)、主机(host)、接口(port)和路径(path)。

scheme://host:port/path

例如,下面的URI,

content://com.example.project:200/folder/subfolder/etc

其模式是“content”,主机是“com.example.project”,接口是“200”,路径是“folder/subfolder/etc”。主机和接口共同构成了URI的授权;如果没有指定主机,那么接口将被忽略。

每一个属性都是可选的,但并非与其他属性相互独立:要让一个授权变得有意义,必须指定一个模式。要让路径具有意义,必须指定一个模式和一个授权。

当一个Intent对象中的URI和过滤器中的URI相比较时,仅比较过滤器中所包含的URI部分。例如,如果一个过滤器仅指定了一个模式,那所有具有这个模式的URI就与过滤器相匹配。如果过滤器指定了一个模式及一个授权但是没有指定路径,那么无论是什么路径,具有相同模式和授权的URI将得到匹配。如果过滤器指定了模式、授权和路径,那就只有具有相同模式、授权和路径的URI得到匹配。不过,过滤器中指定的路径可以包含通配符以仅仅限定部分路径。

一个<data>元素的类型(type)属性指定了数据的MINE类型。在过滤器中这比URI更为常见。Intent对象和过滤器都可以用”*”通配符作为子类别域来标识子类别匹配,例如“text/*”“audio/*”

数据检测同时将Intent对象中的URI和数据类型与过滤器中的URI和数据类型相比较。该过程遵循以下规则:

  • 不包含URI和数据类型的Intent对象只有在过滤器也不指定任何URI及数据类型时才能通过检测。
  • 包含URI但不包含数据类型(且数据类型不能通过URI推断出来)的Intent对象只有在其URI与过滤器中的URI相匹配且过滤器同样没有指定类型时才能通过检测。这是仅仅在类似mailto:和tel:这样没有指向实际数据的URI时才会出现的情况。
  • 包含数据类型但不包含URI的Intent对象只有在过滤器列出了相同的数据类型而没有指定URI时才能通过检测。
  • 同时包含有URI和数据类型(或URI可以推导出数据类型)的Intent对象在其数据类型与过滤器中列出的类型相匹配时通过检测的数据类型部分。当其URI与过滤器中的URI相匹配,或,其具有一个content:file:URI并且过滤器没有指定URI时,通过测试的URI部分。换言之,如果过滤器仅列出了数据类型,一个组件将被假定支持content:file:数据。

如果一个意图可以通过不止一个活动或服务的过滤器,用户将被询问要激活哪一个组件。如果没有一个目标能被找到,则会抛出一个例外。

常见情况

上面数据检测中的最后一条规则,说明了组件可以从一个文件或是内容提供者处获取本地数据。因此,它们的过滤器可以仅仅列出数据类型而不需要显式地命名content:file:模式。这是一种典型的情况。比如说,一个如下的<data>元素,告诉Android组件可以从内容提供者处获取图像数据并显示:

因为大部分可用的数据由数据提供者分发,所以指定了数据类型而没有指定URI的过滤器也许是最为常见的了。

另一种常见的情况是过滤器指定了模式和数据类型。例如,如下的一个<data>元素告诉Android组件可以从网络获取视频数据并播放:

试想,例如,浏览器类应用在用户打开网页上的一个链接后将做什么。它首先尝试显示数据(如果该链接是一个HTML页面的话)。如果它无法显示数据,它将生成一个包含模式和数据类型的隐式意图并试图启动一个可以处理该工作的活动。如果没有合适的活动,它将请求下载管理器下载该数据。这处于内容提供者的控制之下,所以有大量的可能合适的活动(它们的过滤器仅指定了数据类型)将能够做出响应。

大部分程序还有一种不需要任何指定数据引用的开始刷新的方法。能够初始化一个程序的活动有着将行为指定为”android.intent.action.MAIN”的过滤器。如果它们需要被显示在应用程序启动器中的话,它们还需要被指定为”android.intent.category.LAUNCHER”类别:

使用意图匹配

将意图与意图过滤器相匹配不仅仅是为了寻找要激活的某一目标组件,同时也是要获取设备上的某一组组件的某些属性。例如,Android系统通过查找所有具有指定了“android.intent.action.MAIN”行为和“android.intent.category.LAUNCHER”类别的过滤器的活动(如前一节所示)来弹出应用程序启动器这一列出了所有可供用户启动的程序的顶层屏幕内容。之后它在启动器中显示那些活动的图标(icon)和标签(label)。同样地,它通过查找具有“android.intent.category.HOME”类别的过滤器的活动来显示主界面。

程序可以以一种类似的方式来使用意图匹配。PackageManager拥有一组query…()方法来返回所有可以接受某一特定意图的组件,以及一系列的resolve…()方法来决定响应意图最合适的组件。例如,queryIntentActivities()将返回一个列出了所有可以将意图作为参数传递的活动的列表,queryIntentServices()将返回一个类似作用的所有服务的列表。这两种方法都不会激活组件;它们只是列出了那些可以做出响应的。对于广播接收者,还有一个类似的queryBroadcastReceivers()方法。

记事本(Note Pad)范例

记事本范例允许用户浏览笔记列表、查看列表中项目的详细内容、编辑项目、向列表增加新项目。本节将分析在其manifest文件中声明的意图过滤器(如果正在离线使用SDK,可以在<sdk>/samples/NotePad/index.html下找到包括manifest文件在内的该范例程序的所有源文件。如果是在在线查看文档,源文件则是在这里的“教程和范例代码”一节。

在manifest文件中,记事本程序声明了三个活动,每一个都有至少一个意图过滤器。它还声明了一个内容提供者用以管理笔记数据。以下是整个manifest文件的内容:

第一个活动,NoteList,和其他的活动不同,它要对一系列的笔记(笔记列表)进行操作而不是某条单独的笔记。它通常将作为用户进入程序的初始界面。如其三个意图过滤器所描述的,它能执行三项工作:

该过滤器声明了Note Pad程序的主入口。标准MAIN行为是一种不需要Intent中的其他信息(比如不需要数据类型)的入口(entry point),LAUNCHER类别则说明该入口应该被列于应用程序启动器中。

该过滤器声明了这个活动可以对一列笔记进行何种操作。它可以允许用户查看或编辑笔记(通过VIEWEDIT行为),或是从列表中选取一条特定笔记(通过PICK行为)。

<data>元素的mimeType属性指定了这些行为要执行操作的数据类型。它表明了这个活动可以从一个持有Note Pad数据的内容提供器(vnd.google.note)中获取一个指向零个或更多个项目(vnd.android.cursor.dir)的Cursor。启动该活动的Intent对象包含一个content: URI用以指定活动应该打开的确切数据类型。

也请注意该过滤器中提供的DEFAULT类别,因为Context.startActivity()和Activity.startActivityForResult()方法在意图包含DEFAULT类别时将会对其进行处理——只有两种例外:

  • 显式地命名了目标活动的Intent
  • 包含了MAIN行为和LAUNCHER类别的Intent

因此,所有的过滤器都需要DEFAULT类别——除非有MAIN行为和LAUNCHER类别。(意图过滤器不过滤显式意图)

该过滤器说明了这个活动可以返回一条用户选中的笔记而不需要这条笔记所在的列表的相关信息。GET_CONTENT行为类似于PICK行为。两种情况下活动都将返回用户所选择的笔记的URI。(将返回给调用了startActivityForResult()来启动NoteList活动的那个活动。)不过现在,调用者指定了所需的数据类型而不是用户要进行选择的数据列表的类型。

数据类型,vnd.android.cursor.item/vnd.google.note,标明了该活动可以返回的数据的类型——一条单笔记的URI。通过所返回的URI,调用者可以从持有Note Pad数据(vnd.google.note)的内容提供其中获取某一特定条目(vnd.android.cursor.item)的Cursor。

也就是说,前一个过滤器中的PICK行为,数据类型标识出了活动可以显示给用户的数据的类型。而GET_CONTENT过滤器则标识出了活动可以返回给它的调用者的数据的类型。

有了这些能力,下面的意图可以实现活动NotesList的功能:

行为:android.intent.action.MAIN

不需要特定的数据就能启动活动

行为:android.intent.action.MAIN 

类别:android.intent.category.LAUNCHER

不需要特定的被选择的数据就能启动活动。这是实际上应用程序启动器用来填充其最高层列表(top-level list)的意图。所有具有匹配该行为和类别的过滤器的活动将被添加至列表。

行为:android.intent.action.VIEW 

数据:content://com.google.provider.NotePad/notes

请求活动显示在content://com.google.provider.NotePad/notes下的所有笔记的列表。之后用户可以浏览该列表并获取列表中项目的信息。

行为:android.intent.action.PICK 

数据:content://com.google.provider.NotePad/notes

请求活动显示在content://com.google.provider.NotePad/notes下的笔记的列表。之后用户可以从列表中选取一条笔记,活动将返回该条目的URI给启动了NoteList活动的那个活动。

行为:android.intent.action.GET_CONTENT 

数据类型:vnd.android.cursor.item/vnd.google.note

请求活动提供一条单独的Note Pad数据。

第二个活动,NoteEditor,向用户显示一条单独的笔记条目并允许对其进行编辑。根据其两个意图过滤器的描述,它可以进行两项任务:

这个活动的第一个,也是主要的目的是使用户与一条笔记交互,VIEW这条笔记或是EDIT它。(类别EDIT_NOTEEDIT的意义相同。)该意图将包含匹配MIME类型vnd.android.cursor.item/vnd.google.note的数据的URI——也就是说,这条指定的笔记的URI。它通常是被NoteList活动的PICKGET_CONTENT行为所返回的URI。

和之前一样,该过滤器列出了DEFAULT类别因此这个活动可以被其他没有显式地指定NoteEditor类的意图启动。

这个活动的第二个目的是使用户创建一条新的笔记并INSERT至现有的笔记列表中。该意图包含了匹配MIME类型vnd.android.cursor.dir/vnd.google.note的数据的URI——即笔记将被插入的笔记列表的URI。

有了这些能力,下面的意图可以实现NoteEditor活动的功能:

行为:android.intent.action.VIEW 

数据:content://com.google.provider.NotePad/notes/ID

请求活动显示由ID确定的笔记的内容。(关于content: URIs是如何指定一个群组内的个别成员的,请参见“内容提供器”。)

行为:android.intent.action.EDIT 

数据:content://com.google.provider.NotePad/notes/ID

请求活动显示由ID确定的笔记的内容,并允许用户对其进行编辑。如果用户保存了修改,该活动将更新内容提供器中的笔记数据。

行为:android.intent.action.INSERT 

数据:content://com.google.provider.NotePad/notes

请求活动在content://com.google.provider.NotePad/note的笔记列表中创建一个新的空笔记并允许用户对其进行编辑。如果用户保存了笔记,其URI将被返回给调用者。

最后一个活动,TitleEditor,允许用户编辑笔记的标题。这可以通过直接调用该活动来实现(通过显式地在Intent中设定该组件名称),而不需要通过一个意图过滤器。不过这里主要关注如何对现存的数据进行其他操作:

这个活动的唯一的意图过滤器使用了一个名为”com.android.notepad.action.EDIT_TITLE“的自定义行为。它必须像之前的VIEWEDIT行为一样通过一条指定的笔记调用(数据类型vnd.android.cursor.item/vnd.google.note)。不过,这个活动将显示包含于笔记数据中的标题,而不是笔记内容自身。

另外为了支持常见的DEFAULT类别,这个标题编辑器也支持其他两种常见类别:ALTERNATIVE和SELECTED_ALTERNATIVE。这两个类别表明了活动可以在一个选项菜单中显示给用户(就好象LAUNCHER类别表明了活动应当在应用程序启动器中显示给用户一样)。注意过滤器也支持一个显式标签(通过android:label=”@string/resolve_title”)以更好地控制用户能够看到当前被这个活动以其他行为方式查看的数据的哪些内容。(关于这些类别和创建选项菜单的更多信息,请参见PackageManager.queryIntentActivityOptions()和Menu.addIntentOptions()方法。)

有了这些能力,下面的意图就可以实现TitleEditor活动的功能:

行为:com.android.notepad.action.EDIT_TITLE 

数据:content://com.google.provider.NotePad/notes/ID

请求活动显示和笔记ID相关联的标题,并允许用户对其进行编辑。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

媒体

Android平台为多种常见媒体类型提供了内建的编码/解码支持,因而可以简单地向程序整合音频、视频和图像。要使用平台的媒体功能非常简单——同样只需使用意图-活动的机制,之后Android会处理其余的工作。

Android可以从多种数据源类型中播放音频和视频。可以播放存储在程序资源(原始资源 raw resource)中的音频或视频媒体文件,可以播放文件系统中的某一文件,可以播放经由网络的流数据。要让程序播放视频或音频,需使用MediaPlayer类。

如果移动设备硬件支持的话,平台也支持录制音频和视频。要录制音频或视频,需使用MediaRecorder类。注意,模拟器没有获取音频或视频的硬件,不过实际的移动设备通常会提供这些支持,可以通过MediaRecorder类使用。

要了解Android提供内建支持的媒体格式列表,参见附录“Android媒体格式”。

音频和视频播放

可以播放任意来源的媒体:原始资源、系统中的文件或是可用的网络(URL)。

仅可以通过标准输出设备播放音频数据;目前来说,就是至移动设备的扬声器或是蓝牙耳机。现在不能在通话音频中播放声音文件。

播放原始资源

最为常见的需求或许就是从自有程序中播放媒体(一般是声音)了吧。这很容易做到:

  1. 将声音(或其他媒体资源)文件放入工程的res/raw文件夹,Eclipse插件(或aapt)将会发现它并将其识别为R类可以引用的资源。
  2. 创建一个MediaPlayer的实例,用MediaPlayer.create引用该资源,之后调用实例的start()方法:

要停止播放,调用stop()。如果希望之后重放该媒体,就必须在再次调用start()之前reset()及prepare()该MediaPlayer对象。(create()在第一次使用时会调用prepare()。)

要暂停播放,调用pause()。在想要继续播放的时候调用start()。

播放文件或流

可以播放储存于文件系统中或位于网络URL的媒体文件:

  1. 使用new来创建一个新的MediaPlayer实例
  2. 以一个包含了要播放的文件的路径(文件地址或是URL)的字符串调用setDataSource()
  3. 首先prepare()之后start()该实例:

stop()和pause()的工作方式同上。

注意:当引用的文件不存在时,IllegalArgumentExceptionIOException 可能会在使用setDataSource()时被接收或传递。

注意:如果传递的是在线媒体文件的URL,该文件必须可以被逐步下载。

播放JET内容

Android平台包含了一个JET引擎,它允许在程序中添加与JET音频内容的播放控制交互。可以使用SDK附带的JetCreator授权程序创建JET内容的播放控制交互。要在程序中播放并管理JET内容,需要使用JetPlayer类。

关于JET理念的描述及使用JetCreator授权工具的使用说明,参见“JetCreator用户手册”。该工具在OS X和Windows平台下全功能可用,在Linux下所有的内容创建功能也都被支持,只是输入内容审查功能不被支持。

这里有一个如何从一个储存于SD卡的.jet文件中建立JET播放的例子:

SDK包含了一个程序范例——JetBoy——演示了如何使用JetPlayer创建游戏中的音乐音轨播放交互。它同时也展示了如何使用JET事件来同步音乐播放和游戏逻辑。该程序位于<sdk>/platforms/android-1.5/samples/JetBoy

音频录制

通过设备录制音频要比播放音频/视频稍微复杂一些,不过其实也是相当简单:

  1. 使用new来创建android.media.MediaRecorder的一个新的实例
  2. 使用MediaRecorder.setAudioSource()来设置音频源。通常会使用MediaRecorder.AudioSource.MIC
  3. 使用MediaRecorder.setOutputFormat()来设置输出文件格式
  4. 使用MediaRecorder.setOutputFile()来设置输出文件名
  5. 使用MediaRecorder.setAudioEncoder()来设置音频编码
  6. 对MediaRecorder实例调用MediaRecorder.prepare()
  7. 调用MediaRecorder.start()以开始音频录制
  8. 调用MediaRecorder.stop()以结束音频录制
  9. 当不再需要MediaRecorder实例时,对其调用MediaRecorder.release()。建议调用MediaRecorder.release()来立即释放资源
范例:录制音频并播放录制的音频

下面的这个范例类演示了如何建立、开始及终止音频录制,之后播放录制的音频文件。