文章目录
Activity
是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。每个Activity
都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
一个应用通常由多个彼此松散联系的Activity
组成。一般会指定应用中的某个Activity
为主Activity
,即首次启动应用时呈现给用户的那个Activity
。而且每个Activity
均可启动另一个Activity
,以便执行不同的操作。每次新Activity
启动时,前一Activity
便会停止,但系统会在堆栈(返回栈
)中保留该Activity
。当新Activity
启动时,系统会将其推送到返回栈上,并取得用户焦点。返回栈遵循基本的后进先出
堆栈机制,因此当用户完成当前Activity
并按返回
按钮时,系统会从堆栈中将其弹出(并销毁),然后恢复前一Activity
。
当一个Activity
因某个新Activity
启动而停止时,系统会通过该Activity
的生命周期回调方法通知其这一状态变化。Activity
因状态变化:系统是创建Activity
、停止Activity
、恢复Activity
还是销毁Activity
,而收到的回调方法可能有若干种,每一种回调都会为您提供执行与该状态变化相应的特定操作的机会。例如停止时,您的Activity
应释放任何大型对象,例如网络或数据库连接。当Activity
恢复时,您可以重新获取所需资源,并恢复执行中断的操作。这些状态转变都是Activity
生命周期的一部分。
创建Activity
要创建Activity
,您必须创建Activity
的子类(或使用其现有子类)。您需要在子类中实现Activity
在其生命周期的各种状态之间转变时(例如创建Activity
、停止Activity
、恢复Activity
或销毁Activity
时)系统调用的回调方法。两个最重要的回调方法是onCreate
和onPause
。
onCreate
您必须实现此方法。系统会在创建您的Activity
时调用此方法。您应该在实现内初始化Activity
的必需组件。最重要的是,您必须在此方法内调用setContentView
,以定义Activity
用户界面的布局。
onPause
系统将此方法作为用户离开Activity
的第一个信号(但并不总是意味着Activity
会被销毁)进行调用。您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。
您还应使用几种其他生命周期回调方法,以便提供流畅的Activity
间用户体验,以及处理导致您的Activity
停止甚至被销毁的意外中断。后文的管理Activity
生命周期部分对所有生命周期回调方法进行了阐述。
实现用户界面
Activity
的用户界面是由层级式视图(衍生自View
类的对象)提供的。每个视图都控制Activity
窗口内的特定矩形空间,可对用户交互作出响应。例如,视图可以是在用户触摸时启动某项操作的按钮。
您可以利用Android
提供的许多现成视图设计和组织您的布局。小部件
是提供按钮、文本字段、复选框或仅仅是一幅图像等屏幕视觉(交互式)元素的视图。布局
是衍生自ViewGroup
的视图,为其子视图提供唯一布局模型,例如线性布局、网格布局或相对布局。您还可以为View
类和ViewGroup
类创建子类(或使用其现有子类)来自行创建小部件和布局,然后将它们应用于您的Activity
布局。
利用视图定义布局的最常见方法是借助保存在您的应用资源内的XML
布局文件。这样一来,您就可以将用户界面的设计与定义Activity
行为的源代码分开维护。您可以通过setContentView
将布局设置为Activity
的UI
,从而传递布局的资源ID
。不过,您也可以在Activity
代码中创建新View
,并通过将新View
插入ViewGroup
来创建视图层次,然后通过将根ViewGroup
传递到setContentView
来使用该布局。
在清单文件中声明Activity
您必须在清单文件中声明您的Activity
,这样系统才能访问它。要声明您的Activity
,请打开您的清单文件,并将<activity>
元素添加为<application>
元素的子项:
<manifest ... >
<application ... >
<activity android:name=".ExampleActivity" />
...
</application ... >
...
</manifest >
您还可以在此元素中加入几个其他特性,以定义Activity
标签、Activity
图标或风格主题等用于设置Activity UI
风格的属性。android:name
属性是唯一必需的属性,它指定Activity
的类名。应用一旦发布,即不应更改此类名,否则可能会破坏诸如应用快捷方式等一些功能。
使用Intent过滤器
<activity>
元素还可指定各种Intent
过滤器,使用<intent-filter>
元素以声明其他应用组件激活它的方法。
当您使用Android SDK
工具创建新应用时,系统自动为您创建的根Activity
包含一个Intent
过滤器,其中声明了该Activity
响应主
操作,且应置于launcher
类别内。Intent
过滤器的内容如下:
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<action>
元素指定这是应用的主
入口点;<category>
元素指定此Activity
应列入系统的应用启动器内,以便用户启动该Activity
。
如果您打算让应用成为独立应用,不允许其他应用激活其Activity
,则您不需要任何其他Intent
过滤器。正如前例所示,只应有一个Activity
具有主
操作和launcher
类别。如果您想让Activity
对衍生自其他应用(以及您的自有应用)的隐式Intent
作出响应,则必须为Activity
定义其他Intent
过滤器。对于您想要作出响应的每一个Intent
类型,您都必须加入相应的<intent-filter>
,其中包括一个<action>
元素,还可选择性地包括一个<category>
元素和/或一个<data>
元素。这些元素指定您的Activity
可以响应的Intent
类型。
启动Activity
您可以通过调用startActivity
,并将其传递给描述您想启动的Activity
的Intent
来启动另一个Activity
。Intent
对象会指定您想启动的具体Activity
或描述您想执行的操作类型(系统会为您选择合适的Activity
,甚至是来自其他应用的Activity
)。Intent
对象还可能携带少量供所启动Activity
使用的数据。
在您的自有应用内工作时,您经常只需要启动某个已知Activity
。您可以通过使用类名创建一个显式定义您想启动的Activity
的Intent
对象来实现此目的。例如,可以通过以下代码让一个Activity
启动另一个名为SignInActivity
的Activity
:
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
不过,您的应用可能还需要利用您的Activity
数据执行某项操作,例如发送电子邮件、短信或状态更新。在这种情况下,您的应用自身可能不具有执行此类操作所需的Activity
,因此您可以改为利用设备上其他应用提供的Activity
为您执行这些操作。这便是Intent
对象的真正价值所在:您可以创建一个Intent
对象,对您想执行的操作进行描述,系统会从其他应用启动相应的Activity
。如果有多个Activity
可以处理Intent
,则用户可以选择要使用哪一个。例如,如果您想允许用户发送电子邮件,可以创建以下Intent
:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
添加到Intent
中的EXTRA_EMAIL extra
是一个字符串数组,其中包含应将电子邮件发送到的电子邮件地址。当电子邮件应用响应此Intent
时,它会读取extra
中提供的字符串数组,并将它们放入电子邮件撰写窗体的收件人
字段。在这种情况下,电子邮件应用的Activity
启动,并且当用户完成操作时,您的Activity
会恢复执行。
启动Activity以获得结果
有时您可能需要从启动的Activity
获得结果。在这种情况下,请通过调用startActivityForResult
(而非startActivity
)来启动Activity
。要想在随后收到后续Activity
的结果,请实现onActivityResult
回调方法。当后续Activity
完成时,它会使用Intent
向您的onActivityResult
方法返回结果。
例如您可能希望用户选取其中一位联系人,以便您的Activity
对该联系人中的信息执行某项操作。您可以通过以下代码创建此类Intent
并处理结果:
private void pickContact() {
/* Create an intent to "pick" a contact, as defined by the content provider URI */
Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
startActivityForResult(intent, PICK_CONTACT_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
/* If the request went well (OK) and the request was PICK_CONTACT_REQUEST */
if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
/* Perform a query to the contact's content provider for the contact's name */
Cursor cursor = getContentResolver().query(data.getData(),
new String[] {Contacts.DISPLAY_NAME}, null, null, null);
if (cursor.moveToFirst()) { /* True if the cursor is not empty */
int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
String name = cursor.getString(columnIndex);
/* Do something with the selected contact's name */
}
}
}
上例显示的是,您在处理Activity
结果时应该在onActivityResult
方法中使用的基本逻辑。第一个条件检查请求是否成功(如果成功,则resultCode
将为RESULT_OK
)以及此结果响应的请求是否已知:在此情况下,requestCode
与随startActivityForResult
发送的第二个参数匹配。代码通过查询Intent
中返回的数据(data
参数)从该处开始处理Activity
结果。实际情况是,ContentResolver
对一个内容提供程序执行查询,后者返回一个Cursor
,让查询的数据能够被读取。
结束Activity
您可以通过调用Activity
的finish
方法来结束该Activity
。您还可以通过调用finishActivity
结束您之前启动的另一个Activity
。
在大多数情况下,您不应使用这些方法显式结束Activity
。正如下文有关Activity
生命周期的部分所述,Android
系统会为您管理Activity
的生命周期,因此您无需结束自己的Activity
。调用这些方法可能对预期的用户体验产生不良影响,因此只应在您确实不想让用户返回此Activity
实例时使用。
管理Activity生命周期
通过实现回调方法管理Activity
的生命周期对开发强大而又灵活的应用至关重要。Activity
的生命周期会直接受到Activity
与其他Activity
、其任务及返回栈的关联性的影响。Activity
基本上以三种状态存在:
- 继续:此
Activity
位于屏幕前台并具有用户焦点(有时也将此状态称作运行中
)。 - 暂停:另一个
Activity
位于屏幕前台并具有用户焦点,但此Activity
仍可见。也就是说,另一个Activity
显示在此Activity
上方,并且该Activity
部分透明或未覆盖整个屏幕。暂停的Activity
处于完全活动状态(Activity
对象保留在内存中,它保留了所有状态和成员信息,并与窗口管理器保持连接),但在内存极度不足的情况下,可能会被系统终止。 - 停止:该
Activity
被另一个Activity
完全遮盖(该Activity
目前位于后台
)。已停止的Activity
同样仍处于活动状态(Activity
对象保留在内存中,它保留了所有状态和成员信息,但未与窗口管理器连接)。不过,它对用户不再可见,在他处需要内存时可能会被系统终止。
如果Activity
处于暂停或停止状态,系统可通过要求其结束(调用其finish
方法)或直接终止其进程,将其从内存中删除。(将其结束或终止后)再次打开Activity
时,必须重建。
实现生命周期回调
当一个Activity
转入和转出上述不同状态时,系统会通过各种回调方法向其发出通知。所有回调方法都是挂钩,您可以在Activity
状态发生变化时,替代这些挂钩来执行相应操作。以下框架Activity
包括每一个基本生命周期方法:
public class ExampleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* The activity is being created */
}
@Override
protected void onStart() {
super.onStart();
/* The activity is about to become visible */
}
@Override
protected void onResume() {
super.onResume();
/* The activity has become visible (it is now "resumed") */
}
@Override
protected void onPause() {
super.onPause();
/* Another activity is taking focus (this activity is about to be "paused") */
}
@Override
protected void onStop() {
super.onStop();
/* The activity is no longer visible (it is now "stopped") */
}
@Override
protected void onDestroy() {
super.onDestroy();
/* The activity is about to be destroyed */
}
}
正如以上示例所示,您在实现这些生命周期方法时必须始终先调用超类实现,然后再执行任何操作。这些方法共同定义Activity
的整个生命周期。您可以通过实现这些方法监控Activity
生命周期中的三个嵌套循环:
Activity
的整个生命周期发生在onCreate
调用与onDestroy
调用之间。您的Activity
应在onCreate
中执行全局
状态设置(例如定义布局),并释放onDestroy
中的所有其余资源。例如,如果您的Activity
有一个在后台运行的线程,用于从网络上下载数据,它可能会在onCreate
中创建该线程,然后在onDestroy
中停止该线程。Activity
的可见生命周期发生在onStart
调用与onStop
调用之间。在这段时间,用户可以在屏幕上看到Activity
并与其交互。例如,当一个新Activity
启动,并且此Activity
不再可见时,系统会调用onStop
。您可以在调用这两个方法之间保留向用户显示Activity
所需的资源。例如,您可以在onStart
中注册一个BroadcastReceiver
以监控影响UI
的变化,并在用户无法再看到您显示的内容时,在onStop
中将其取消注册。在Activity
的整个生命周期,当Activity
在对用户可见和隐藏两种状态中交替变化时,系统可能会多次调用onStart
和onStop
。Activity
的前台生命周期发生在onResume
调用与onPause
调用之间。在这段时间,Activity
位于屏幕上的所有其他Activity
之前,并具有用户输入焦点。Activity
可频繁转入和转出前台:例如,当设备转入休眠状态或出现对话框时,系统会调用onPause
。由于此状态可能经常发生转变,因此这两个方法中应采用适度轻量级的代码,以避免因转变速度慢而让用户等待。
下图说明了这些循环以及Activity
在状态转变期间可能经过的路径。矩形表示回调方法,当Activity
在不同状态之间转变时,您可以实现这些方法来执行操作:
下表列出了相同的生命周期回调方法,其中对每一种回调方法做了更详细的描述,并说明了每一种方法在Activity
整个生命周期内的位置,包括在回调方法完成后系统能否终止Activity
:
方法 | 说明 | 是否能事后终止? | 后接 |
---|---|---|---|
onCreate | 首次创建Activity 时调用。您应该在此方法中执行所有正常的静态设置:创建视图、将数据绑定到列表等等。系统向此方法传递一个Bundle 对象,其中包含Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的保存Activity 状态) | 否 | onStart |
onRestart | 在Activity 已停止并即将再次启动前调用 | 否 | onStart |
onStart | 在Activity 即将对用户可见之前调用。如果Activity 转入前台,则后接onResume ;如果Activity 转入隐藏状态,则后接onStop | 否 | onResume 或onStop |
onResume | 在Activity 即将开始与用户进行交互之前调用。此时,Activity 处于Activity 堆栈的顶层,并具有用户输入焦点 | 否 | onPause |
onPause | 当系统即将开始继续另一个Activity 时调用。此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗CPU 的内容,诸如此类。它应该非常迅速地执行所需操作,因为它返回后,下一个Activity 才能继续执行。如果Activity 返回前台,则后接onResume ,如果Activity 转入对用户不可见状态,则后接onStop | 是 | onResume 或onStop |
onStop | 在Activity 对用户不再可见时调用。如果Activity 被销毁,或另一个Activity (一个现有Activity 或新Activity )继续执行并将其覆盖,就可能发生这种情况。如果Activity 恢复与用户的交互,则后接onRestart ,如果Activity 被销毁,则后接onDestroy | 是 | onRestart 或onDestroy |
onDestroy | 在Activity 被销毁前调用。这是Activity 将收到的最后调用。当Activity 结束(有人对Activity 调用了finish ),或系统为节省空间而暂时销毁该Activity 实例时,可能会调用它。您可以通过isFinishin 方法区分这两种情形 | 是 | 无 |
名为是否能事后终止?
的列表示系统是否能在不执行另一行Activity
代码的情况下,在方法返回后随时终止承载Activity
的进程。有三个方法带有是
标记:onPause
、onStop
和onDestroy
。由于onPause
是这三个方法中的第一个,因此Activity
创建后,onPause
必定成为最后调用的方法,然后才能终止进程:如果系统在紧急情况下必须恢复内存,则可能不会调用onStop
和onDestroy
。因此,您应该使用onPause
向存储设备写入至关重要的持久性数据(例如用户编辑)。不过,您应该对onPause
调用期间必须保留的信息有所选择,因为该方法中的任何阻止过程都会妨碍向下一个Activity
的转变并拖慢用户体验。
在是否能在事后终止?
列中标记为否
的方法可从系统调用它们的一刻起,防止承载Activity
的进程被终止。因此,在从onPause
返回的时间到onResume
被调用的时间,系统可以终止Activity
。在onPause
被再次调用并返回前,将无法再次终止Activity
。
保存Activity状态
管理Activity
生命周期的引言部分简要提及,当Activity
暂停或停止时,Activity
的状态会得到保留。确实如此,因为当Activity
暂停或停止时,Activity
对象仍保留在内存中:有关其成员和当前状态的所有信息仍处于活动状态。因此,用户在Activity
内所做的任何更改都会得到保留,这样一来,当Activity
返回前台(当它继续
)时,这些更改仍然存在。
不过,当系统为了恢复内存而销毁某项Activity
时,Activity
对象也会被销毁,因此系统在继续Activity
时根本无法让其状态保持完好,而是必须在用户返回Activity
时重建Activity
对象。但用户并不知道系统销毁Activity
后又对其进行了重建,因此他们很可能认为Activity
状态毫无变化。在这种情况下,您可以实现另一个回调方法对有关Activity
状态的信息进行保存,以确保有关Activity
状态的重要信息得到保留,即onSaveInstanceState
。
系统会先调用onSaveInstanceState
,然后再使Activity
变得易于销毁。系统会向该方法传递一个Bundle
,您可以在其中使用putString
和putInt
等方法以名称-值对
形式保存有关Activity
状态的信息。然后,如果系统终止您的应用进程,并且用户返回您的Activity
,则系统会重建该Activity
,并将Bundle
同时传递给onCreate
和onRestoreInstanceState
。您可以使用上述任一方法从Bundle
提取您保存的状态并恢复该Activity
状态。如果没有状态信息需要恢复,则传递给您的Bundle
是空值(如果是首次创建该Activity
,就会出现这种情况)。
在两种情况下,Activity
重获用户焦点时可保持状态完好:系统在销毁Activity
后重建Activity
,Activity
必须恢复之前保存的状态;系统停止Activity
后继续执行Activity
,并且Activity
状态保持完好。
无法保证系统会在销毁您的Activity
前调用onSaveInstanceState
,因为存在不需要保存状态的情况(例如用户使用返回
按钮离开您的Activity
时,因为用户的行为是在显式关闭Activity
)。如果系统调用onSaveInstanceState
,它会在调用onStop
之前,并且可能会在调用onPause
之前进行调用。
不过,即使您什么都不做,也不实现onSaveInstanceState
,Activity
类的onSaveInstanceState
默认实现也会恢复部分Activity
状态。具体地讲,默认实现会为布局中的每个View
调用相应的onSaveInstanceState
方法,让每个视图都能提供有关自身的应保存信息。Android
框架中几乎每个小部件都会根据需要实现此方法,以便在重建Activity
时自动保存和恢复对UI
所做的任何可见更改。例如,EditText
小部件保存用户输入的任何文本,CheckBox
小部件保存复选框的选中或未选中状态。您只需为想要保存其状态的每个小部件提供一个唯一的ID
(通过android:id
属性)。如果小部件没有ID
,则系统无法保存其状态。
您还可以通过将android:saveEnabled
属性设置为false
,或通过调用setSaveEnabled
方法显式阻止布局内的视图保存其状态。您通常不应将该属性停用,但如果您想以不同方式恢复Activity UI
的状态,就可能需要这样做。
尽管onSaveInstanceState
的默认实现会保存有关您的Activity UI
的有用信息,您可能仍需替换它以保存更多信息。例如,您可能需要保存在Activity
生命周期内发生了变化的成员值(它们可能与UI
中恢复的值有关联,但默认情况下系统不会恢复储存这些UI
值的成员)。
由于onSaveInstanceState
的默认实现有助于保存UI
的状态,因此如果您为了保存更多状态信息而替换该方法,应始终先调用onSaveInstanceState
的超类实现,然后再执行任何操作。同样,如果您替换onRestoreInstanceState
方法,也应调用它的超类实现,以便默认实现能够恢复视图状态。
由于无法保证系统会调用onSaveInstanceState
,因此您只应利用它来记录Activity
的瞬态(UI
的状态):切勿使用它来存储持久性数据,而应使用onPause
在用户离开Activity
后存储持久性数据(例如应保存到数据库的数据)。
您只需旋转设备,让屏幕方向发生变化,就能有效地测试您的应用的状态恢复能力。当屏幕方向变化时,系统会销毁并重建Activity
,以便应用可供新屏幕配置使用的备用资源。单凭这一理由,您的Activity
在重建时能否完全恢复其状态就显得非常重要,因为用户在使用应用时经常需要旋转屏幕。
处理配置变更
有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。发生此类变化时,Android
会重建运行中的Activity
(系统调用onDestroy
,然后立即调用onCreate
)。此行为旨在通过利用您提供的备用资源(例如适用于不同屏幕方向和屏幕尺寸的不同布局)自动重新加载您的应用来帮助它适应新配置。
如果您对Activity
进行了适当设计,让它能够按以上所述处理屏幕方向变化带来的重启并恢复Activity
状态,那么在遭遇Activity
生命周期中的其他意外事件时,您的应用将具有更强的适应性。
正如上文所述,处理此类重启的最佳方法是利用onSaveInstanceState
和onRestoreInstanceState
(或onCreate
)保存并恢复Activity
的状态。
协调Activity
当一个Activity
启动另一个Activity
时,它们都会体验到生命周期转变。第一个Activity
暂停并停止(但如果它在后台仍然可见,则不会停止)时,同时系统会创建另一个Activity
。如果这些Activity
共用保存到磁盘或其他地方的数据,必须了解的是,在创建第二个Activity
前,第一个Activity
不会完全停止。更确切地说,启动第二个Activity
的过程与停止第一个Activity
的过程存在重叠。
生命周期回调的顺序经过明确定义,当两个Activity
位于同一进程,并且由一个Activity
启动另一个Activity
时,其定义尤其明确。以下是当Activity A
启动Activity B
时一系列操作的发生顺序:
Activity A
的onPause
方法执行。Activity B
的onCreate
、onStart
和onResume
方法依次执行(Activity B
现在具有用户焦点)。- 然后,如果
Activity A
在屏幕上不再可见,则其onStop
方法执行。
您可以利用这种可预测的生命周期回调顺序管理从一个Activity
到另一个Activity
的信息转变。例如,如果您必须在第一个Activity
停止时向数据库写入数据,以便下一个Activity
能够读取该数据,则应在onPause
而不是onStop
执行期间向数据库写入数据。