Android帮助文档翻译——开发指南(二)Activity

时间:2021-04-08 05:34:07

转载时请注明转自:http://blog.csdn.net/sam_zhang1984

交流可加新浪微博:Android开发人

 

Activity(这个觉得没必要翻译,直接用还好理解 @Sam)就提供与用户交互的界面,用户通过这个交互界面可以进行一些操作,比如打电话、照相、发邮件或查看地图。每一个Activity都提供了一个可以绘制用户交互界面的窗体,这个窗体通知充满整个屏幕,但也可以是小于屏幕或悬浮于其它窗体上。

一个应用程序通常是由多个松耦合的Activity组成。一般情况下,在一个应用程序的多个Activity中有一个Activity会被指定成主Activitymain),这样当程序第一次运行时用户就会首先看到这个Activity的界面。程序运行后,每一个Activity都可以启动其它Activity来实现用户的操作,当启动了一个新的Activity后,旧的Activity将会停止,而新的Activity会被系统压到一个栈中(称为“back stack”,这个名词也不准备翻译 @Sam)。即当有一个新的Activity启动时,系统会把这个Activity压入到back stack中并获取用户的焦点。这个back stack遵循“后进先出”的队列原则,因此,当用户操作完当前Activity并按了“返回”值后,当前这个Activity将会从栈中被弹出并销毁,之前的Activity将重新恢复。

当一个Activity由于有新的Activity被启动而停止时,它将会通过Activity的生命周期回调方法获取到这个状态改变的信息。一个Activity可能会因为状态的改变而收到多个回调方法,无论是系统创建了这个Activity、停止这个Activity、恢复这个Activity,还是销毁这个Activity,都会接收到一个回调方法。利用这些回调方法,你可以在Activity状态改变时进行一些相应的操作工作,比如当Activity在停止时,你可以释放像网络连接和数据库连接这样的一些重量级的对象;当Activity恢复时,你可以重新申请必要的资源和恢复一些中断的动作。这些状态之间的转换就构成了一个Activity生命周期。

本节接下来主要讨论如何创建和使用一个Activity,包括详细讨论一个Activity的生命周期,这样你就可以在这些状态转变过程中进行一些合适的操作。

 

创建一个Activity

要创建一个Activity就必须创建一个继承了ActivityActivity子类的类。在你所创建的类中,你可以实现其回调方法,让系统在Activity状态(创建、停止、恢复或销毁)改变时调用。其中最重要的两个回调方法是:

onCreate()

这是一个必须实现的回调方法。它是在Activity创建时调用的。在你实现的这个回调方法中,你应当初始化你这个Activity里面所必需的组件,更重要一点是你必须调用setContentView()方法定义你这个Activity用户交互界面的布局。

onPause()

这个方法是当用户离开Activity时系统调用的第一个方法,所谓用户离开Activity不一定是说Activity被销毁。由于此时用户还没返回到这个Activity因此在这个回调方法中你一般需要提交一些用户当前会话中需要保存的数据

除了这两个回调方法外,还有一些其它的生命周期回调方法可以使用。你可以使用这些回调方法让用户在各种Activity切换过程有更好的用户体验,也可以处理一些会引起Activity停止甚至销毁的意外中断。所有的生命周期回调方法会在后面再进行讨论。

 

实现一个用户交互界面

Activity的用户交互界面由一系列从View类派生出的视图(view)体系组成的。每一个视图(view)控制了Activity窗体中某一个特定的矩形区域,并能对用户交互动作进行响应。比如,一个视图(view)可能就是一个已经创建响应用户触摸动作的按钮。

Android提供了很现成的视图,你可以直接使用这些来设计和组织你的布局。对于能提供在屏幕上可视且可交互元素的视图我们称其为“Widgets”,比如按钮、文字标签、复选按钮或一张图片。而那些由ViewGroup派生并能为其子视图提供唯一布局模式的视图我们称其为“Layouts”,比如线性布局、网格布局或相对布局。你也可以自己通过继承ViewViewGroup或其子类,创建自己的WidgetsLayouts,并在你的Activity布局中使用它们。

使用视图定义一个布局的最普遍做法,是在你的应用程序资源中使用一个XML布局文件进行定义。通过这种做法,你可以脱离定义了Activity行为的源代码来维护你的用户交互界面的设计。你可以调用setContentView()方法并传递进一个布局的资源ID来为你的用户交互界面(UI 下面都直接使用UI代替 @Sam)。尽管如此,你也可以代码中通过在一个ViewGroup中插入新视图来创建一个视图体系,这时你就需要传根ViewGroupsetContentView()方法来设置你的UI布局。

 

manifest文件中定义Activity

为了让系统能使用你的Activity,你必须在manifest文件中定义它,定义的方法是:打开manifest文件并在<application>标签下增加一个<activity>子标签,代码如下:

  

在这个<activity>标签中,你还可以添加更多的属性来定义Activity的性能,如Activity的标签、图标或UI界面样式的主题。更多关于<activity>标签的属性描述见reference章节。

使用意图过滤器

一个<activity>标签也可以使用一个<intent-filter>
子标签来指定意图过滤器,这样其它应用程序的组件就可以通过这个来激活它。

当你使用SDK工具新建一个应用程序时,开发工具会自动为你的首个activity加入一个可以响应“main”动作并放在程序列表"launcher" category)意图过滤器,代码如下:

  

<action>
标签指定了应用程序的“main”入口,<category>
标签则表明了这个activity应该放设备的程序列表中并允许用户使用。

如果你希望你的程序是独立并且不允许其它应用程序调用,那你就可以不加任何意图过滤器(intent filters)。就像上面的代码例子一样,一个activity只能有一个“main”动作和一个"launcher"category(这个真不知怎么翻译@Sam)。那些你不想要其它应用程序调用的activity就不要添加意图过滤器,但你想在自己应用程序中调用的话就必须使用显式的调用(后面章节会详细阐述)。

然而,如果你希望你的activity要对其它应用程序(或你自己的程序)的隐式意图进行响应,那么你就必须为你的activity添加一个意图过滤器。你必须为每一种准备进行响应的意图类型用<intent-filter>
标签和<action>
子标签进行定义,还可以添加<category>
子标签和<data>
子标签进行辅助描述。通过这些标签的定义,你就可以指定你的activity能响应的意图类型。

更多关于activity如何响应意图的讲解,可以参考“意图和意图过滤器”一节。

 

启动一个activity

通过调用startActivity()


方法,并传递进一个描述你想要启动activity
的意图,你就可以启动其它

activity了。被传递的这个意图即可以明显指定你要启动的activity,也可以描述你想执行的动作。通过后者的方式,系统会为你选择一个合适的activity来执行,当然是包括不同应用中的activity。一个意图同时可以携带一些目标activity需要的轻量级数据。

当在你自己的应用程序中工作时,你经常需要启动一个已知的activity。启动方法就用一个包含有类名明的意图确指定你想启动的activity,然后传递过去就行。下面例子就演示了如何启动一个名叫SignInActivityactivity

  

尽管如些,你有时可能需要用你自己的数据然后在其它activity中执行某一个动作,比如发邮件、发短信或更新状态等。在这种情况下,有可能你自己的应用程序中没有可以执行这个动作的activity,那么你就可以使用另一种代替的方法来完成这个操作:让设备上其它应用程序中能为你执行这个动作的activity为你执行。这就是意图的真正价值所在,你可以创建一个描述你要执行动作的意图,然后系统就会为你激发一个其它程序中合适的activity。如果设备上有多个activity能胜任这个动作,那么就由用户自己去选择是用哪一个去执行。以下给出创建一个能让用户发邮件的意图:

  

上面的EXTRA_EMAIL附加字符串数据就是送邮件时收件人的地址(EXTRA_EMAIL是一个键,真正的值即收件人地址是后面recipientArray@Sam)。当一个Email程序对这个意图进行响应时,它会把这个附加字符串放到新建邮件的收信人一栏中。这时Email程序的activity将被启动,当你完成了发邮件操作后返回时,你的activity就又恢复了!

 

启动一个带返回值的activity

有时你可能需要从你所启动的activity中返回数据,这种情况下,启动activity时就需要调用startActivityForResult()而不是startActivity()。通过实现onActivityResult()回调方法,就可以接收被启动activity的返回值。当被启动activity执行完成后,它将返回一个意图(Intent)给onActivityResult()方法。

举个例子,你或许需要用户去选取一个联系人,然后你的activity就可以对这个联系人信息进行相关处理。下面就给出了如何去创建这样一个意图并其返回值的代码:

  

这个例子为我们展现了用onActivityResult()方法处理activity返回值的基本逻辑模型。首先,检测请求是否成功(即resultCode是否为RESULT_OK);其次,检测是否是当前请求是否是已知的(即requestCode是否与startActivityForResult()的第二个参数匹配);最后,代码中通过查询一个Intent类型数据(即第三个参数data)来处理activity返回值。

其中有一个ContentResolver执行了一个针对内容提供器(content provider)的查询,并返回一个允许去读限实际数据的游标(Cursor)。

 

关闭一个Activity

你可以通过调用finish()方法来关闭一个activity,也可以通过调用finishActivity()来关闭另一个你之前启动过的activity

注:在大多数情况下,你不需要使用这个方法显式地去关闭一个activity。正如接下来关于activity生命周期中会讨论到一样,Android系统会为你管理activity的整个生命周期,因此你不需要亲自去关闭一个activity。调用这个方法可能会对用户体验起到一些负面影响,只要当你确信用户没必要再返回到这个activity实例时才亲自去关闭它。

 

管理一个Activity的生命周期

通过实现回调方法来管理activity的生命周期是开发一个健壮灵活应用程序的关键所在。与其它activity、它自己的任务(task)和back stack的联系直接影响了一个activity的生命周期。

一个activity基本存在三种状态:

1、            恢复:当一个activity处于屏幕前台并取得用户焦点时所处的状态,通常这种状态也被称为正在运行

2、            暂停:另一个activity前台并取得用户焦点,但这个activity仍然是可见时所处的状态。即另一个activity处于这个activity之上,但它是半透明或没覆盖整个屏幕。一个暂停的activity是完全存活着的,即它被保留在内存里面,保持所有状态和成员信息,并且它的残骸(实在不知怎么翻译 @Sam)则交给窗体管理,但是在内存不足的情况下它仍然是可以被系统销毁的。

3、            停止:一个activity被另一个activity完全遮蔽,即处在后台时的状态。一个停止的activity仍然是存活着的,即这个activity对象仍保留在内存中,保持所有状态和成员信息,但它就不隶属窗体管理了。但它对于用户来说是不可见的,并且在其它需要内存时就会被系统销毁。

如果一个activity处于暂停或停止状态,那么系统就可以通过调用finish方法关闭它或直接销毁它的进程来释放掉这个activity所占的内存。当已经从内存释放后,如果再次打开这个activity,那就得重新创建它了。

 

实现生命周期回调方法

当一个activity在上面讨论的各种状态间过渡时,系统会通过调用相应的回调方法通知它状态的改变。所有这些回调方法都是可以覆写的hook,我们可以通过覆写这些方法可以在activity改变状态时进行一些合适的操作。下面是一个包含了所有生命周期基本方法的activity模型:

  

注:正如上面例子显示一样,你实现的所有生命周期方法时,必须在你的实现实体之前调用父类的实现。

从整体看,这些方法定义了activity的整个生命周期。通过实现这些方法,你可以监视activity生命周期中以下的三个嵌套循环(nested loops):

1、            activity从调用onCreate()到调用onDestroy()过程中的整个生命周期。你的activity需要在onCreate()中设置一些全局的状态属性(例如定义布局),在onDestroy()中释放占用的所有内存。例如,如果你有一个用来下载网络数据的线程运行要运行在后台,就应该在onCreate()时创建它,并在onDestroy()时停止它。

2、            activity从调用onStart()到调用onStop()过程中的可视生命周期。在这个时段时,用户可以在屏幕上看到activity与之交互。例如,当有一个新的activity启动并且当前activity不可见了后,将会调用onStop()。在这两个方法之间,你可以保持一些显示activity给用户看必要的资源。例如,你可以在onStart()方法中注册一个广播接收器来监听影响UI的一些改变,而在用户看不到你的程序所显示的内容时注销它,即调用onStop()时注销(因为当activity不可见时,更新UI已经没有意义了,即这个广播接收器已经没存在的意义,所以可注销它 @Sam)。系统可能会activity的整个生命周期中多次调用onStart()onStop(),就好像activity对于用户来说总是在可见和不可见之间交替一样。

3、            activity从调用onResume()onPause()之间的前台生命周期。在这个期间里面activity处于屏幕前方并取得用户焦点。一个activity可能会在前台和后台间频繁切换,例如,在设备休眠时或出现一个对话框时会调用onPause()。因为这个状态间切换非常频繁,所以在这两个方法中的代码最好是相对轻量级一点,这样才可以避免切换太慢,要让用户等待相对长时间。

1描述了这个循环过程以及一个activity在各种状态之间切换的路径。矩形框表示你可以实现在activity切换状态过程中执行操作的回调方法。

 Android帮助文档翻译——开发指南(二)Activity

                                                           图1


表格1同样是这些生命周期回调方法。不过这里描述得更为详细,明确其各自在一个activity整个生命周期的位置,同时说明了在执行某个回调方法后系统会不会释放掉activity的内存。

 

 

 

方法 描述 其后能否销毁

下一

方法

onCreate()
activity第一次创建时会被调用。在这个方法中你需要完成所有的正常静态设置,比如创建一个视图(view)、绑定列表的数据等等。如果能捕获到activity状态的话,这个方法传递进来的Bundle对象将存放了activity当前的状态。调用该方法后一般会调用onStart()方法。 onStart()
     onRestart()

activity被停止后重新启动时会调用该方法。其后续会调用onStart方法。

onStart()
onStart()

activity对于用户可见前即调用这个方法。如果activity回到前台则接着调用onResume(),如果activity隐藏则调用onStop()(好像有点问题,怎么会是调用onStop呢?? @Sam)。

onResume()
or
onStop()
     onResume()

activity开始与用户交互前调用该方法。在这时该activity处于activity栈的顶部,并且接受用户的输入。其后续会调用onPause()方法。

onPause()
onPause()
在系统准备开始恢复其它activity时会调用该方法。这个方法中通常用来提交一些还没保存的更改到持久数据中,停止一些动画或其它一些耗CPU的操作等等。无论在该方法里面进行任何操作,都需要较快速完成,因为如果它不返回的话,下一个activity将无法恢复出来。如果activity返回到前台将会调用onResume(),如果activity变得对用户不可见了将会调用onStop() onResume()
or
onStop()
onStop()

activity对用户不可见时将调用该方法。可能会因为当前activity正在被销毁,或另一个activity(已经存在的activity或新的activity)已经恢复了正准备覆盖它,而调用该方法。如果activity正准备返回与用户交互时后续会调用onRestart,如果activity正在被释放则会调用onDestroy

onRestart()
or
onDestroy()
onDestroy()

activity被销毁前会调用该方法。这是activity能接收到的最后一个调用。可能会因为有人调用了finish方法使得当前activity正在关闭,或系统为了保护内存临时释放这个activity的实例,而调用该方法。你可以用isFinishing方法来区分这两种不同的情况。

没有

 

“其后能否销毁”一列表示在该方法返回后,系统能否不执行activity任何代码直接销毁托管了当前activity的进程。其中有onPause() onStop() onDestroy()三个方法执行后是可以销毁的。由于onPause()是这三个方法中第一个会被执行的,因此它是在activity创建后销毁前这个过程中,最后一个能被确保执行的方法。如果由于突发事件系统需要回收内存,activity将在执行onPause()后被销毁,这时onStop() onDestroy()就可能不会被调用了。因此,你需要在onPause()方法中保存一些关键的永久性数据,如用户在编辑的内容。尽管如此,你也必须有选择性地在onPause()方法中保存一些数据,因为在这个方法中的任何阻塞处理过程,都会阻塞住下一个activity的切换,在用户体验方面就会觉得程序很慢。

在“能否销毁”列中是“否”的那些方法,表示可以保证托管了当前activity的进程在调用该方法后不会被系统直接销毁。因此,一个activityonPause()方法返回后,调用onResume()方法前是可以被销毁的;在重新调用了onPause方法并返回前,activity是不可以被销毁的。

注:一个activity不一定是表格中定义的技术上的“是否可销毁”,它有时也会被系统所销毁,但这种情况只有在系统中已经没有资源的极端情况下发生。

保存activity的状态

在“管理activity生命周期”一节中已经简略地提到了,可以在activity被暂停或停止时保存activity的状态。因为当activity对象被暂停或停止时,它仍然保留在内存里面,关于它的成员信息和当前状态都是活动的,所以在这个时候进行保存是有效的。从而使得用户在activity中所做的变更都会被保存到内存中,这样的话当activity又返回到前台时,所有的这些变更仍然保持不变。

Android帮助文档翻译——开发指南(二)Activity

尽管如此,当系统由于要回收内存而把activity销毁时(即当activity对象已经被销毁),系统就无法简单地恢复它的状态,而是在用户要返回这个activity时,重新创建一个activity。但用户是无法察觉到系统销毁和重建activity的这些操作,因此用户可能要求返回到的activity要与之前是一样的。这个情况下,你就需要实现另一个能保存activity状态信息的回调方法来保存activity的一些重要信息,并在系统创建它时把这些信息读取出来。

你可以用回调方法onSaveInstanceState()来保存activity的当前状态信息。系统会在activity将被销毁前调用这个方法,并传进一个Bundle对象。你可以使用putString()之类的方法,在这个Bundle对象中以键值对的形式保存状态信息。这时,如果系统销毁了activity所在进程后,用户又导航回你的activity时,系统就会传递这个Bundle对象给onCreate()方法,这样你就可以恢复出你在onSaveInstanceState()中保存的activity状态。如果没状态信息需要恢复,那么传递给onCreate()方法的Bundle对象为null

注:因为很多情况下是不需要保存activity的状态信息的(比如用户按了返回键就是想关闭当前activity),所以并不能保证在你的activity被销毁前一定会调用onSaveInstanceState()方法。如果调用了该方法,一般是在onStop方法之前且可能在onPause之后调用。

尽管如此,即使你没做任何操作或没有实现onSaveInstanceState()方法,你的activity状态也能通过Activity类里面默认实现的onSaveInstanceState方法恢复出来。特别是会为布局中的视图(View)默认调用onSaveInstanceState方法,并在这个方法中允许每一个视图提供它需要恢复的任何信息。几乎每一个Android框架中的widget都视情况实现了这个方法,这样的话,这些UI的可视变化将会自动保存并在activity重建时被恢复。例如,一个EditText控件保存用户输入的内容,一个复选按钮保存它是否是被选中。你要做的只是给每一个你需要保存状态的widget提供一个唯一的ID(用android:id属性)。如果一个widget没有ID的话,它将无法保存状态。

尽管默认实现了onSaveInstanceState方法来保存activity UI中重要的数据,但你也可以自己覆写它来保存一些额外的数据。比如,你可能需要保存一些在activity生命周期里更改的一些成员数据(比如一些与恢复UI有关、用来保存UI数据,但在默认情况下并没有被恢复的成员数据)。

因为默认实现的onSaveInstanceState方法帮助我们保存了UI的状态信息,所以如果需要通过覆写来保存额外信息时,你需要在做其它工作前先调用父类的onSaveInstanceState方法。

注:因为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 A 启动activity B时处理的顺序:

1.        执行activity AonPause()

2.        按顺序执行activty BonCreate() onStart() onResume(),这时activty B获取到用户的焦点

3.        如果activty A在屏幕上不可见,那么执行activty AonStop()方法

对生命周期回调方法执行顺序的预计,可以让你管理好从一个activty向另一个activty转换时的一些信息。比如,如果你必须在第一个activty停止时向数据库中写入数据让第二个activty读取,那么你就应该在应该在onPause方法中向数据库写入数据,而不是在onStop方法中。(因为当activty在后台还是可见时,是不会执行onStop方法的 @Sam