Activity是为用户完成某事,比如打电话,拍照,发邮件或者浏览地图,而提供一块屏幕的应用组件。每个Activity拥有一个可以编辑用户界面的窗口。窗口布满整个屏幕,也可以小于或者是漂浮在其它查看之上。
通常,应用程序包含多个相互关联的Activity。一般有个Activity被定义为“主”Activity,在初次打开软件时呈现给用户。每个Activity能够启动另外的Activity来进行不同的动作。新的Activity启动时,之前的Activity会被停止,但是系统将会在“后备堆栈”("back stack")保留该Activity。新Activity启动时,新Activity将被推到“后备堆栈”的上方供用户查看。“后备堆栈”遵循基本的“后进新出”队列原则,这样,当 用户完成当前Activity点击BACK键时,就会将此Activity推出堆栈(即销毁),并恢复之前的Activity。(“后备堆栈”的内容将在“任务和‘后备堆栈’”中详细讨论。)
由于开启新Activity而停用另一个Activity时,通过使用Activity生命周期的回调方法来声明这一状态变化。同一Activity由于处于不同的状态(比如创建,停用,重启,销毁),可能接收到几个不同的回调方法,并且每个回调方法提供完成和当前状态改变相适宜的特定功能。比如,在停用Activity时,将会释放所有大的对象,比如网络或数据库连接。在重启Activity时,将重新获得必要的资源,重启被中断的动作。这些状态转换就构成了Activity生命周期。
该文档将继续讨论创建和使用Activity,包括完整的讨论Activity的工作原理,这样就可以掌握不同Activity之间的交互过程。
Activity的创建
创建Activity必须创建Activity的一个子类(或者一个已存在的子类)。在子类中需要实现回调函数,这样在生命周期的不同状态(比如创建,停用,重启,销毁)的切换时,系统就可以调用。下面是两个最重要的回调函数:
onCreate()
必须先实现该方法。系统将在创建Activity时调用。在实现过程中,应初始化Activity中必需的控件。最重要的是,在这里必须调用setContentView()方法来定义该Activity用户界面的全局文件。
onPause()
只要用户有离开该Activity的迹象(虽然并不总是销毁该Activity),系统就调用该方法。在当前用户会话中应该完成所有需要保存的修改(用户可能不会再返回来)。
还有其它一些生命周期回调函数,可以在Activity之间完成流畅的用户体验,控制引起Activity停用甚至销毁的异常事件。生命周期中的所有回调函数将在管理Activity生命周期部门做进一步讨论。
实现用户界面
Activity的用户界面由继承于View类的对象的层级view来提供。每个view占有Activity窗口中某一矩形空间,并进行用户交互。比如,一个view可能会是发起用户点击的动作事件。
Android提供了一组现成的view,可以用于设计和组织布局。“Widgets”提供可视化(和交互性的)元素,比如button,text field,checkbox,或者image。“Layouts”继承于ViewGroup,提供独有的布局模版用于生成子view,比如线性layout,网格layout或关系layout。也可以从View和ViewGroup继承来创建自己的widgets和layouts,在Activity布局中就可以使用。
定义布局文件的最常用方式是使用保存在程序资源下的XML布局文件。这样,就可以在不影响Activity代码的情况下进行用户界面的设计。使用setContentView()方法传递layout的资源ID来设置Activity的用户界面。还可以在Activity代码中创建新的Views,并在ViewGroup中插入新的Views来建立View层次,然后传递根ViewGroup到setContentView()来使用这个layout。
了解更多关于创建用户界面的信息,请查看用户界面文档。
在manifest中说明Activity
要让系统能够访问Activity,必须先在manifest文件中声明Activity。声明Activity,需要打开manifest文件,在元素下添加子元素。如下所示:
</manifest ...>
</application ...>
</activity android:name=".exampleactivity">
...
...
在这个元素中还可以包含一些其它属性,比如Activity的标签,图标,或定义Activity UI的主题。Android:name属性是必需的,定义了Activity的类名。一旦发布程序,就不能修改,如果改了,很可能会破坏部分基础信息,比如程序快捷键(阅读博客,了解“不能修改的东西”)。
了解更多关于在manifest中声明Activity,请查看元素的参考文档。
使用Intent过滤器
也可以定义多个不同的Intent过滤器,使用元素就可以让其他程序控件来激活这个Activity。
使用Android SDK工具创建新的程序,自动创建的根Activity包含有一个声明了响应main动作的Intent过滤器,应该放置在launcher范畴中。这个Intent过滤器如下:
<activity< span=""> android:name=".ExampleActivity" android:icon="@drawable/app_icon">
<action< span=""> android:name="android.intent.action.MAIN" />
<category< span=""> android:name="android.intent.category.LAUNCHER" />
元素定义了这里就是程序的“main”进入点。元素定义了该Activity将出现在程序登记列表中(以供用户打开该Activity)。
如果想让程序是完全独立的,不允许其它程序激活它的Activity,那么就不需要其它的Intent过滤器。像前面例子那样,只有一个Activity能够包含main动作和launcher范畴。不想让其它程序激活的Activity就不能包含Intent过滤器,并使用详述的Intent来启动它们(将在下面讨论)。
然而,如果想让Activity响应派生于其它程序的详述的Intent,就必须在Activity中另外定义Intent过滤器。对于想要响应的Intent的每个类型,必须在下编写元素,而和供选择使用。这些元素定义了Activity响应的Intent的类型。
更多关于Activity响应Intent的信息,请查看Intent和Intent过滤器的文档。
Activity启动
通过调用startActivity()就可以启动另一个Activity,并传递一个描述想要启动的Activity的Intent。Intent中说明了准确地说明了想要启动的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是邮件将要发送至的email地址字符串。邮件程序响应该Intent后,就会读取extra里的字符串数组并将它们放置在邮件编写表单里的发送至区域。此时,邮件程序的Activity就会启动,用户完成之后就会重启原来的Activity。
作为结果启动Activity
可能有时候需要从你启动的Activity中接收一个结果。通过调用startActivityResult()方法(而不是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...
}
}
}
这个例子展示了将通过onActivityResult()方法掌控结果时用到的基本逻辑。第一个条件是确保请求是否成功——如果成功,那么resultCode将是RESULT_OK——和当前请求之后是否公认——这样,requestCode将会和stratActivityForResult()方法传来的第二个参数进行匹配。至此,代码将会通过查询Intent(data参数)返回的数据来掌控Activity的结果。
接下来,ContentResolver完成对content provider的查询,而后者返回允许查询数据被读取的Cursor。更多信息请查看Content Providers文档。
关于使用Intent的更多信息,请查看Intents和Intent过滤器文档。
关闭Activity
可以通过调用它的finish()方法来关闭Activity。也可以通过调用finishActivity()方法来关闭独立的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就包含了基本生命周期的每个方法:
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的整个生命周期。实现这些方法就可以模拟生命周期中三个嵌套的循环:
完整周期发生于onCreate()和onDestroy()的调用之间。Activity应该在onCreate()方法中完成全局状态(比如定义全局文件)的设置,在onDestroy()方法中释放所有存留的资源。比如,Activity有个线程在后台运行着从网络上下载数据,可能就要使用onCreate()创建线程,之后用onDestroy()停止线程。
可见周期发生于onStart()和onStop()的调用之间。在此期间,用户可以在屏幕上看到Activity,并进行操作。比如,onStop()会在新Activity启动时被调用,这个Activity就不能再被看到。在两个方法之间,可以保存现有资源以备用户重启该Activity。比如,可以在onStart()方法中注册BroadcastReceiver来监视影响UI的变化,用户不再看所显示内容时在onStop中进行注销。如果Activity在可见和不可见之间进行切换时,系统会在Activity完整周期内多次调用onStart()和onStop()。
前端周期发生于onResume()和onPause()的调用之间。在此期间,Activity处于屏幕上所有Activity之上。Activity经常在前端和后端之间进行切换——比如,在设备进入睡眠状态或者来电时,onPause()就会被调用。由于这种状态经常切换,这两个方法之间的代码应该相对轻型,防止用户等待缓慢的切换过程。
图1说明这些循环和Activity可能要走的路程。矩形框代表完成Activity之间状态切换时所实现的回调方法。
图1 Activity生命周期
表1中列出这些生命周期回调方法,更详细地描述每个回调函数,并确定它们在Activity整个生命周期中的位置,包括回调方法结束后系统是否能够将其杀死。
表1 Activity生命周期回调函数汇总
函数
描述
完成后可否杀死
下一步
onCreate()
Activity开始创建时被调用。这里需要完成所有常规设置——建立view,绑定数据,等。在这个状态被捕获时,该方法通过包含有Activity之前状态的Bundle对象来传递。(见后面的Activity状态的保存)下一步永远是onStart()。
不
onStart()
onRestart()
Activity已经被停止再次启动之前会被调用。下一步永远是onStart()。
不
onStart()
onStart()
调用后用户可以看到该Activity。在onResume()前,Activity就会在前端显示,或在onStop()前就会隐藏起来。
不
onResume()
或
onStop()
onResume()
调用后Activity就可以和用户进行交互。此时Activity处于Activity堆栈的顶端。下一步永远是onPause()。
不
onPause()
onPause()
在系统即将启动另一个Activity时被调用。该方法通常用来对固定数据,停止激活和其它占用CPU资源等不做保存。不管它有多么迅速地结束,它就是这样子,因为下一个Activity只会在返回时才会被重启。在onResume()前Activity就会返回到前端,在onStop()前用户就不能看待该Activity。
是
onResume()
或
onStop()
onStop()
调用后用户就不再能够看到Activity。可能是因为被销毁,或者是另一个Activity被重启而将其覆盖。在onRestart()前Activity就会返回到前端,在onDestroy()前Activity就会结束。
是
onRestart()
或
onDestroy()
onDestroy()
调用后Activity就会被销毁。这是Activity接收到的最后一个调用方法。在Activity结束(有人调用finish())时,或系统暂时地销毁Activity的实例时都会调用。可以同isFinish()一起对比区分两者的应用场合。
是
无
“完成后可否杀死”一栏表明在该方法未执行一行代码的情况下系统是否会杀死该Activity。三个方法标注为“是”:onPause(),onStop()和onDestroy()。由于onPause()是三个中的第一个,一旦Activity被创建,onPause()方法是能够保证在进程被杀死前被调用的最后一个方法——如果紧急情况下系统回收内存,那么onStop()和onDestory()方法将不再会被调用。因此,需要使用onPause()方法来将关键数据(如用户编辑)写入到存储区。然而,在onPause()方法中要有选择地将信息进行保存,因为这个方法执行过程将会延缓切换到下一个Activity,减慢用户体验。
在“完成后可否杀死”一栏中标注为“否”的方法能够保护进程不被系统所杀死。因此,从onPause()返回到onResume()的时候,Activity是可以被杀死的。直到onPause()方法被再次调用并返回,它才会被杀死。
注意:从技术角度讲,在表1中定义的不能被杀死的Activity可能也会被系统杀死——这只在没有其它资源的极端情况下才会发生。Activity被杀死的情况在进程和线程文档中将进一步讨论。
保存Activity状态
在Activity生命周期管理中简单地提到Activity暂停或停止时,Activity的状态将会被保存。确实如此,因为Activity对象在暂停或停止时仍占用着内存——所有成员的信息以及现行状态仍是激活状态。因此,用户在Activity中做的所有修改都会保存在内存中,那么Activity重新回到前端显示(也就是重启)后,这些改变仍然是存在的。
然而,系统回收内存而销毁Activity时,Activity对象会被销毁,因此系统就不能简单地原封不动地重启该Activity。这样子,用户想要导航回去,系统就必须重建Activity对象。然而用户并不会意识到系统销毁和重建Activity,而期待Activity和之前一模一样。这种情况下,通过实现外加的回调方法来对Activity状态信息进行保存,以便系统重建Activity时能够使用这些状态信息。
在onSaveInstanceState()回调方法中可以保存当前Activity的状态信息。在Activity自愿被销毁之前,系统会调用这个方法,传递一个Bundle对象。而在Bundle中使用类似putString()方法可以以键值对的形式来保存状态信息。然后,如果系统杀死Activity并且用户导航返回,系统会传递Bundle对象到onCreate()方法,这样就可以在onSaveInstanceState()方法中重建已保存的Activity状态。如果没有重建的状态系统,那么Bundle传递给onCreate()方法将是空值。
注意:并不能保证一定会调用onSaveInstanceState()方法,然后才销毁Activity,因此有时候没必要保存这些状态(比如用户使用返回键离开Activity,就是想关掉该Activity)。调用该方法都是在onStop()方法之前,也可能在onPause()方法之前。
图2 Activity原封不动地返回用户焦点的两种方式:一种是被停止后重启,Activity仍是原来的状态(左边所示);一种是被销毁后重建,Activity必须重建之前的状态(右边所示)。
然而,即使什么都不做,不实现onSaveInstanceState()方法,Activity的部分状态信息仍会被Activity类的默认onSaveInstanceState()方法保存下来。特别地,默认onSaveInstanceState()方法会为布局中的每个View进行保存,这样可以让每个view能够提供自己应该保存的信息。Android框架里,几乎每个windget都适当地实现该方法对UI的可见修改都会自动地保存,并在重建Activity时使用。比如,EditText这个widget会保存用户输入的所有文字,而CheckBox这个widget会保存是否已被选择。唯一要做的就是为需要保存状态的每个widget设置唯一的ID。如果没有ID,那么就不会保存它的状态。
尽管默认onSaveInstanceState()方法保存Activity UI的有用信息,有时候可能仍需要重写来保存额外的信息。比如,可能需要保存在Activity生命中被修改了的成员变量的值(这可能和存储在UI中的数值没关系,但是默认情况下这些值并不会被保存在UI值里面)。
因为默认onSaveInstanceState()方法会保存UI的状态,如果为了保存额外状态信息而重写该方法,首先要做的应该是调用超类中的onSaveInstanceState()函数。
注意:由于onSaveInstanceState()方法并不能保证一定会调用,所以只有在记录短暂状态(UI状态)时才会使用——千万别用来保存常用数据。相反,应该使用onPause()方法来保存用户离开Activity时的常用数据(如哪些应该保存到数据库中的数据)。
简单地旋转设备改变屏幕,就能够测试程序是否能够存储状态信息。当屏幕方向改变时,为了应用可能会在此方向状态下的资源,系统会销毁并重建Activity。仅此原因,Activity完全地重建状态就尤为重要,因为用户在使用程序时旋转了屏幕方向。
掌控配置的变化
有些设备在运行时配置会有所变化(比如屏幕方向,键盘启用和语言)。有这些变化时,Android会重启正在运行的Activity(先调用onDestory(),迅速地执行onCreate()方法)。这样重启可以通过自动重新加载程序以及资源,让程序更好地适应新的配置信息。如果Activity能够恰当地控制此类事件,在整个生命周期中对于突发事件就会更加地灵活。
掌控配置变化的最好办法是,使用上面讲到的onSaveInstanceState()和onRestoreInstanceState()或是onCreate()来简单地保存程序的状态信息。
在掌控运行时变化中对此做更加详细的讨论。
整合Activity
一个Activity启动另一个的时候,它们都会经历生命周期切换。第一个Activity暂停并在第二个Activity创建时停止(然而,如果仍在背景里可见那么就不会停止)。只有这些Activity共用保存在磁盘上的数据时,认识到在第二次Activity创建之前第一个并未完全地停止这一点是很重要的。而且启动第二个Activity的同时将会停止第一个Activity。
生命周期回调的顺序定义得很明确,特别是一个过程中有两个Activity,一个启动另一个Activity的时候。下面是Activity A启动Activity B时的操作顺序:
1. Activity A的onPause()方法执行;
2. Activity B的onCreate(),onStart()和onResume()方法依次执行(此时Activity B占有用户焦点);
3. 然后,如果不在屏幕上查看Activity A,那么它的onStop()方法就会执行。
这些预设好的生命周期回调有助于对Activity切换时的信息进行管理。比如,在第一个Activity停止时想要往数据库里写数据,以便后一个Activity能够读取,那么就要在onPause()方法中写到数据库,而不是在onStop()方法中。
【本文由Tank_Snow独家授权给译言网使用,未经许可请勿转载。商业或非商业使用请联系译言网】