原文地址:Activities
一、前言
activity是应用组件之一,可以提供给用户用于交互的界面,比如拨打电话,选择图片,发送邮件,查看地图等。每个activity都有一个绘制了用户界面的窗口。窗口通常会完全填满界面,但是窗口也可以比界面稍小或悬浮在窗口顶端。
一个应用通常包含多个activity,而这些activity中,有一个被指定为“main”activity,它表示用户第一次启动这个应用的入口。每个activity都可以被其他activity启动,而每次一个新的activity启动后,之前的activity会被停止,但是系统会将此停止的activity保存在一个栈中(回退栈)。当新的activity启动时,它会被压入栈中,并获得用户焦点。回退栈遵循先进后出的栈机制,所以当用户按下返回键退出当前activity时,当前activity会被弹出栈顶并销毁,而之前的activity会准备好(resume)。
当一个activity因为其他activity启动而被停止时,系统会通过其生命周期的回调方法通知你状态变化。根据生命周期的状态变化activity可以收到很多种回调,比如create、stop、resume、destroy,这些回调可以让你在适当的时机根据状态变化进行适当的操作。比如,当activity被停止时(stopped),你可以释放占用的系统资源(网络资源、数据库连接资源等),当activity恢复时(resume),你可以重新获取必要的资源并继续你之前中断的工作。这些状态过渡都定义在activity的生命周期中。
二、新建activity
通过继承Activity类你可以新建activity(或直接使用已经存在的Activity的子类)。在你编写的子类中,你需要实现一些回调方法,这些方法将在activity生命周期发生变化时由系统调用,如activity创建、停止、恢复、销毁。其中两个重要的回调方法有:
- onCreate()
你必须实现这个方法。当创建此activity时系统会调用它。在这个方法中,你应该做此activity必要的初始化工作。最重要的是,你需要在这个方法中调用setContentView()方法去定义这个activity用户界面的布局。 - onPause()
当用户离开你的activity时系统会调用此方法(这不意味着你的activity一定已经被销毁)。通常你需要在这个方法中保留当前会话数据(因为用户可能不会回到这个activity了)。
还有其他的生命周期回调,你可以用它们提供一个在activity切换时流动式的用户体验,并处理可能的中断(activity被停止或销毁)。生命周期回调的详细信息请看activity生命周期管理部分。
2.1 用户界面
activity的用户界面是一个视图的层级结构(视图指View对象)。每个view都控制了activity窗口的一个矩形空间,并且能响应用户的交互。例如,按钮就是一个view,当用户点击它时它会进行特定的动作。
Android提供了很多预制的view,你可以使用它们设计和管理自己的布局。“widget(控件)”是一种view,它是屏幕上的一个元素,比如按钮、文本框、多选框或者图片。“layout”是一种view,衍生自ViewGroup,为它的子View提供布局模式,如linear layout、grid layout、relative layout。你也可以继承View或ViewGroup(或已存在的子类)来创建自己的widget和layout,并把它们运用到自己的activity布局中。
通常定义布局中view的方法为xml布局文件,它保存在你的应用的资源文件夹中。这样的话,你可以将用户界面的设计维护与控制activity行为的代码分开。你可以使用setContentView()(需要传入布局文件的resource ID)将一个布局文件设置为你的activity的UI。当然,你也可以在activity的代码中新建View,然后将view插入到ViewGroup中,最后将此ViewGroup传入setContentView(),用这种方式自定义UI。
更多创建UI的信息,请看用户界面部分。
2.2 在manifest文件中声明activity
你必须在manifest文件中声明activity,它才能被系统找到。你可以在manifest文件的<application>
元素下添加<activity>
元素来声明activity。例如:
<manifest ... >
<application ... >
<activity android:name=".ExampleActivity" />
...
</application ... >
...
</manifest >
在<activity>
元素中,你可以添加其他的几个属性,以定义activity的标签、图标、主题等内容。android:name
属性是必要的属性,它说明了此activity的类名。在你的应用发布后,此类名就不应该再改变了,因为如果你这样做了,你可能会破坏某些功能,如应用快捷方式
2.2.1 使用intent filter
<activity>
可以指定不同的intent filter,添加<intent-filter>
元素即可,此元素可以声明其他应用组件怎样启动此activity。
当你用Android SDK tools新建应用时,根activity会自动创建,并添加一个intent filter,此intent filter声明了activity会应答“main”action,并且被“launcher”category定位。它看起来像这样:
<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>
元素指明了这个activity是此应用的入口。<category>
元素指明了这个activity会被显示在系统桌面上。
如果你希望避免其他应用启动你的activity,不添加任何intent filter即可(除了上例中的launch activity)。你可以使用显式intent启动这些没有intent filter的activity。
如果你希望你的activity可以应答隐式intent(不管是从其他应用发来的还是自己应用发的),你就必须为你的activity添加intent filter。对于每种你想要响应的intent,你都需要指定一个intent filter。这些intent filter包含<action>
元素(必须)、<category>
元素(可选)、<data>
元素(可选)。这些元素指定了你的activity能够应答哪些类型的intent。
更多信息请看intent and intent filter部分。
三、启动activity
调用startActivity()并传入intent可以启动另一个activity。此intent可以指定你想要的启动的activity,或者描述你希望目标activity能够做的事情,由系统为你选择合适的activity。intent也可以携带目标activity需要的数据。
在自己的应用内部,启动的通常是已知的activity。你可以用显式intent指定activity类名启动它。例如下面,启动一个叫SignInActivity的activity:
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
如果你的应用不知道目标activity的类名,但是知道自己需要让目标activity做什么动作,如发送邮件、发送信息、改变状态,这时你可以使用隐式intent指定需要目标activity完成的action等内容,由系统选择合适的activity。如果有多个activity符合条件,会显示对话框由用户选择使用哪一个。例如,如果你想要发送邮件,可以创建如下的intent:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
此intent中的EXTRA_EMAIL是一个邮件地址的string列表,描述了邮件需要发送到哪里。如果有应用响应了这个intent,应用会从extra中读取前面提到的string列表并将其加入到邮件的“目标邮箱”域。接着此应用的activity启动,当用户操作完毕,此应用的activity结束,然后你的activity会重新resume。
3.1 启动activity并获得返回
有时,你希望从你启动的activity获得返回。你可以通过调用startActivityForResult()做到这一点。要接收到目标activity的结果,你还需要实现onActivityResult()方法。当目标activity结束时,它的结果会通过onActivityResult()方法的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...
}
}
}
上述例子中使用onActivityResult()方法处理返回到activity的结果。基本逻辑为:首先检查我们的请求是否成功(成功的话resultCode为RESULT_OK),然后检查此结果是否是我们请求的那个(在startActivityForResult()方法中需要传入requestCode作为参数,同样的这个参数会在onActivityResult中传回,用于标识此请求),最后从返回的intent中查询数据并处理(即入参data)。
上述代码中的ContentResolver用于处理对content provider的查询,它会返回一个Cursor对象用以查询读到的数据。更多信息可以查看Content Provider部分。
四、关闭activity
调用finish()方法可以关闭activity。你也可以通过finishActivity()方法停止一个指定的activity。
注意:大多数情况下,你不需要用上面的方法关闭activity,因为系统有activity生命周期机制,它会为你管理activity。调用上述方法会影响用户体验,只有当你确定你不希望用户返回到这个activity时才使用这些方法关闭此activity。
五、管理activity生命周期
实现指定回调方法后,你就可以管理你自己的activity的生命周期了,从而开发健壮又灵活的应用。activity的生命周期跟其他activity的生命周期通过task和回退栈联系在一起。
activity有三个基本状态:
- resumed
表示此activity在屏幕最前端,且拥有用户焦点(这个状态也可以被称为running状态)。 - paused
表示其他activity在屏幕最前端且拥有用户焦点,而此activity仍然是可见的。这种情况发生在另一个activity在最前端但是只占用了部分屏幕或其有一部分是透明的时。paused状态的activity还完全“存活”着(activity对象仍然在内存中,维护自己的状态和数据,而且保持着对window manager的依附),但是当系统内存严重不足时,这个状态的activity可能被系统关闭(kill)。 - stopped
表示此activity被其他activity完全遮盖了(此activity现在在后台)。stopped状态的activity仍然存活着(activity对象仍然在内存中,维护自己的状态和数据,但是已经没了对window manager的依附),然而它已经对用户不可见了,当系统内存紧张时,这个状态的activity可能会被系统关闭。
如果activity在paused状态或stopped状态,系统可能会因为内存问题关闭它(通过调用finish()方法),甚至直接杀掉它所在的进程。当这个activity重新启动时(被finish或kill之后),它会被重新创建。
5.1 实现生命周期回调方法
activity生命周期状态发生变化时,系统会通过不同的回调方法通知你。所有的回调方法都是钩子,你可以override它们去做适当的工作。下面的例子说明了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的可见生命周期,从onStart()开始,到onStop()结束。在这部分,用户可以在界面上看到这个activity,并且和它交互。例如,onStop()在新activity开始旧activity不可见时被调用。在这两个方法中间,你可以维护需要显示给用户的资源。例如,你可以在onStart()方法注册广播去监控你的UI的变化,然后在onStop()方法反注册广播(此时用户已经看不见你的界面)。在整个生命周期过程中系统可能会调用onStart()和onStop()多次。
activity的前台生命周期,从onResume()开始,到onPause()结束。在这部分,此activity处于屏幕的最前端,而且拥有用户焦点。activity可能频繁的在前台和后台切换,例如,当设备休眠或显示对话框时,此时onPause()方法会被调用。由于在这两个状态间的转换很频繁,所以尽量减少在这两个状态间的代码,以防减慢转换速度使用户等待。
图1表明了上述的闭环和状态转换的路线。其中矩形表示你可以实现的回调方法。
相应的生命周期回调方法详细信息见表1。
表1 activity生命周期回调方法摘要
Method | Method | Method | Description | Killable after? | Next |
---|---|---|---|---|---|
onCreate() | activity第一次被创建时调用。在这个方法中,你应该做一些静态设置:创建view、为list添加数据等等。这个方法会接收上一个状态传来的bundle对象。onCreate()方法后面是onStart()方法。总是接着调用onStart()方法 | No | onStart() | ||
onRestart() | activity被停止后要重新启动时调用。总是接着调用onStart()方法 | No | onStart() | ||
onStart() | 在activity对用户可见前调用。如果activity成为前台界面,那么接着调用onResume(),如果activity被隐藏,那么接着调用onStop() | No | onResume() or onStop() | ||
onResume() | 在activity开始跟用户交互前调用。在这个状态,activity处于activity栈的顶端,并拥有用户焦点。总是接着调用onPause()方法 | No | onPause() | ||
onPause() | 当系统启动其他activity时调用。这个方法通常用于保存数据、停止动画、停止其他会消耗CPU的操作。这个方法要尽可能快的结束,因为这个方法返回后下一个activity才能进入onResume状态。如果activity返回到前台,那么下一个方法是onResume(),如果后面此activity对用户不可见,那么下一个方法是onStop() | Yes | onResume() or onStop() | ||
onStop() | 当activity对用户不可见时调用(可能是由于此activity要被销毁或其他activity进入onResume状态并覆盖了它)。如果后面此activity进入前台,那么下一个方法是onRestart(),如果后面此activity被销毁,那么下一个方法是onDestroy() | Yes | onRestart() or onDestroy() | ||
onDestroy() | activity被销毁前调用(可能是由于activity调用了finish方法,也可能是系统为了节省内存选择销毁此activity,你可以使用isFinishing()方法判断被销毁的原因)。这是activity能收到的最后的回调 | Yes | nothing |
上表中的“Killable after?”表示系统是否可以在此方法返回后杀掉activity所在的进程,而不执行此activity的其他方法。三个方法:onPause()、onStop()、onDestroy()为“yes”。对于onPause()方法来说,当系统在紧急情况下需要回收内存时,会在杀掉activity所在进程前调用它,但是onStop()和onDestroy()就不会调用了。因此你应该在onPause()方法中保存重要的用户数据。但是注意不要在onPause()中执行太多操作,以防阻塞用户进入下个activity。
“Killable after?”为no表示系统在此状态结束到下一个状态开始前不能杀掉activity所在的进程。但是注意,从onPause()返回,到onResume()调用这个过程中,activity所在进程是可以被杀掉的。
最后一句话总结下:在onPause()被调用并返回以前,activity所在进程不能被杀掉。
注意:在非常极端的情况下,按照表1不在“killable”状态(不能被杀掉)的activity还是有可能被系统杀掉。
5.2 保存activity状态
当activity在paused状态或stopped状态时,虽然它不可见,但是它仍然存在于内存中,所有的信息和状态都被保存着。因此当此activity重新回到前台(处于resume状态),用户在这个activity上的操作会被重新获得。
然而,如果系统为了回收内存而销毁了一个activity,这个activity对象就被销毁了,这种情况下就算它回到前台,系统也不能将它的状态恢复了。此时系统将会重新创建一个activity对象。对于用户来说,他不知道系统已经销毁了activity,所以他希望当他回到此activity时能恢复之前的数据。这种情况下,你可以通过实现onSaveInstanceState()方法保存重要数据。
系统在activity进入可销毁状态时会调用onSaveInstanceState()方法。系统会传入一个bundle对象,你可以通过putString()或putInt()方法将数据保存进去。之后,如果系统杀掉了你的应用进程,而用户按返回键后回到你的activity,那么系统会重建你的activity并将你之前保存的bundle传入onCreate()方法和onRestoreInstanceState()方法。从这两个方法的bundle中,你可以提取你保存的数据然后恢复activity。如果之前没有保存数据,那么bundle为空(比如你的activity第一次创建时)。
图2中描述了activity返回前台后能保持数据完整的两个过程:如果activity是被销毁的,那么activity重建时读取保持的数据以恢复状态,如果activity只是进入stopped状态,那么不需要读取数据,activity数据本身就是完整的。
注意:不能保证activity被销毁前一定会调用onSaveInstanceState(),有些情况下你不必保持数据,比如用户用返回键退出你的activity,这时他很明确的要关闭你的activity而且不需要你恢复它了。系统可能在onStop()前调用onSaveInstanceState(),也可能在onPause()后调用。
即使你没有实现onSaveInstanceState()方法,activity的某些数据也会被保存/恢复。因为activity类是有onSaveInstanceState()的默认实现的。默认实现调用了布局中所有view的onSaveInstanceState()方法,在这些方法中,每个view都保存了它需要保存的数据。几乎Android框架的所有控件都实现了onSaveInstanceState()方法,这样UI的变化就会被保存,并在activity重建时恢复。例如EditText会保存用户输入的数据,CheckBox会保存它是否被选中。要使用默认的onSaveInstanceState()方法只需要你指定控件的ID即可(android:id属性)。如果控件没有ID,系统就不能保存它的状态了。
虽然默认onSaveInstanceState()方法保存了UI的一些数据,但是你还是应该override它以保存必要的信息。
你可以将view的“android:saveEnabled”属性设置为false,这样此view的状态就不会被保存了(或者调用setSaveEnabled()方法)。通常情况下你不应该这样设置,除非你想要将此UI在恢复时被设置为其他值。
由于onSaveInstanceState()方法有默认实现,所以当你override此方法时必须调用超类的实现,然后再做自己的逻辑。同样的,在override onRestoreInstanceState()后,也需要调用超类的实现。
注意:由于不能保证onSaveInstanceState()方法一定会被调用,所以你应该只用它保存activity的状态信息(UI的状态),保存持久化的数据(如将数据保存到数据库中)的工作应该交给onPause()方法。
测试应用恢复状态能力的一个小窍门是旋转设备,这样的话屏幕会改变方向,这时系统会销毁并重建activity(系统需要为新的屏幕配置选择合适的资源文件)。从这个角度讲,保存activity的数据是很重要的,因为用户可能会频繁的旋转屏幕。
5.3 处理配置改变问题
有些设备的配置可能会在运行时改变(例如屏幕方向,键盘可用性,语言等)。当改变发生时,Android系统会重新创建正在运行的activity(系统在调用onDestroy()后立刻调用onCreate())。这样设计系统是为了让你的应用可以根据不同的设备配置自动加载你提供的多种资源文件(例如为不同大小、方向的屏幕提供的不同布局文件)。
如果你很好的处理了由于配置改变导致的activity重启,并且按照上述方法保存了activity状态,你的应用将会在处理activity生命周期的异常事件时有更好的弹性。
最好的处理activity重启的方法就是按照我们之前讨论的,用onSaveInstanceState()保存状态,用onRestoreInstanceState()(或onCreate())恢复状态。
关于运行时发生配置变化的更多信息,请看“处理运行时改变”部分
5.4 activity之间的协调
当一个activity启动另一个activity时,两个activity的生命周期都会发生变化。第一个activity先pause然后stop(当然,如果此activity在后台是可见的,则不会stop),而另一个activity会create。此时两个activity所在的进程会发生交互。
如果两个activity在同一个进程中,activity A启动activity B:
- 执行activity A的onPause()方法
- 依次执行activity B的onCreate()方法、onStart()方法、onResume()方法,此时activity B拥有了用户焦点。
- activity已经不可见,执行onStop()方法
这些生命周期方法的执行顺序可以帮助你管理activity间传输的数据,例如,如果你在第一个activity写数据,第二个activity读数据,那么你应该在第一个activity的onPause()方法保存数据,而不是onStop()。