Activity的生命周期
Activity 是这样一个程序组件,它为用户提供一个用于任务交互的画面。例如,拨打电话,拍照,发邮件。或者查看地图。每一个activity都被分配一个窗口。在这个窗口里,你可以绘制用户交互的内容。 这个窗口通常占满屏幕,但也有可能比屏幕小,并且浮在其它窗口的上面。
一个应用程序通常由多个activity组成,它们彼此保持弱的绑定状态。典型的,当一个activity在一个应用程序内被指定为主activity, 那么当程序第一次启动时,它将第一个展现在用户面前。为了展现不同的内容,每一个activity可以启动另外一个。 每当一个新的activity被启动,那么之前的将被停止。但系统将会把它压入一个栈(“back stack”即后退栈),当一个新的activity启动,它将被放到栈顶并获得用户焦点。后台栈遵循后进先出的栈机制。所以当用户完成当前页面并按下返回按钮时,它将被pop出栈(并销毁),之前的activity将被恢复。 (关于后退栈的更多讨论在任务和后退栈)
当一个activity因为另一个activity的启动而被停止,那么其生命周期中的回调方法,将会以状态改变的形式被调用。 activity通过它自身状态的改变可以收到多个回调方法。当系统创建,停止,恢复,销毁它的时候。并且每个回调方法都给你做相应处理工作的机会。 例如,当停止的时候,你的activity应当释放比较大的对象,例如网络连接,数据连接。当你的activity恢复时,你可以请求必须的资源并恢复一些被打断的动作。 这些状态事务的处理就构成了activity的生命周期。
接下来将讨论如何搭建和使用activity,完整讨论activity的生命周期是怎么工作的,这样你就可以合理地管理不同activity状态间的事务处理
-
-
Activity的启动和关闭
-
Activity的启动
-
-
startActivity(intent):启动其它的Activity。
startActivityForResult(intent, requestCode):启动新Activity并得到返回结果
-
-
-
Activity的关闭
-
-
finish():结束当前Activity。
finishActivity(int requestCode):结束当前Activity并返回结果
-
-
Activity的生命周期
-
Activity的生命周期包括4种状态,3个生命周期循环,7个生命周期函数。
图2. 1
实际上就是讲解下面这一张图
图2.2
-
-
四种状态
-
-
-
-
运行状态
当Activity位于栈顶时,此时正好处于屏幕最前方,此时处于运行状态;如此时的FirstActivity正处于运行状态。
图 2.3
暂停状态
当Activity失去了焦点但仍然对用于可见(如栈顶的Activity是透明的或者栈顶Activity并不是铺满整个手机屏幕),此时处于暂停状态,如果图中的FisrtActivity就处于暂停状态:
图2.4
停止状态
当Activity被其他Activity完全遮挡,此时此Activity对用户不可见,此时处于停止状态;当我们在图2.3点击“启动SecondActivity”,此时的SecondActivity非透明的形式,全部覆盖FirstActivity,那么此时的FirstActivity就处于停止状态。
图2.5
销毁状态
当Activity由于人为或系统原因(如低内存等)被销毁,此时处于销毁状态;
图2.6
当我们在图2.6中点击退出,我们在点击退出的事件中调用了this.finish(),那么此时的FirstActivity就处于销毁状态。
四种状态之间的转换图
图2.7
-
-
三个生命周期循环
-
完整生命周期
activity的完整生存期会在 onCreate() 调用和 onDestroy() 调用之间发生。 你的activity应该在 onCreate() 方法里完成所有“全局global”状态的设置(比如定义layout), 而在onDestroy() 方法里释放所有占用的资源。 例如,如果你的activity有一个后台运行的线程,用于从网络下载数据,那么你应该在 onCreate() 方法里创建这个线程并且在 onDestroy() 方法里停止这个线程。
完整的生命周期即从出生(创建)到死亡(销毁)。
可视生命周期
activity的可见生存期会在 onStart() 调用和 onStop() 调用之间发生。在这期间,用户可在屏幕上看见这个activity并可与之交互。 例如,当一个新的activity启动后调用了 onStop() 方法,则这个activity就无法被看见了。 在这两个方法之间,你可以管理那些显示activity所需的资源。例如,你可以在 onStart() 方法里注册一个 BroadcastReceiver 用于监控影响用户界面的改动。并且当用户不再看到你的显示内容时,在 onStop() 方法里注销掉它。 系统会在activity的整个生存期内多次调用 onStart() 和onStop(), 因为activity可能会在显示和隐藏之间不断地来回切换。
可见生命周期即从可见到不可见。
前台生命周期
activity的前台生存期会在 onResume() 调用和 onPause() 之间发生。在这期间,activity是位于屏幕上所有其它的activity之前,并且拥有用户的输入焦点。 activity可以频繁地进入和退出前台——例如, 当设备进入休眠时或者弹出一个对话框时, onPause() 就会被调用。因为这个状态可能会经常发生转换,为了避免切换迟缓引起的用户等待,这两个方法中的代码应该相当地轻量化。
前台生命周期即从可交互(获取焦点)到不可交互(失去焦点)。
-
-
七个生命周期函数
-
Activity的不同生命周期是通过回调不同的生命周期函数组来实现的(如图),生命周期回调方法已经在表1中列出了,该表更详细地描述了每个回调方法,并且指明了每个方法在activity的全生命周期中的位置, 包括回调方法完成后系统是否会杀死这个activity。
图2.8
方法 |
描述 |
之后可否被杀死? |
下一个方法 |
onCreate() |
activity第一次被创建时调用。在这里你应该完成所有常见的静态设置工作——创建view、绑定list数据等等。 本方法传入一个包含了该activity前一个状态的Bundle对象(如果之前已捕获了状态的话,详见后面的保存Activity状态)。下一个回调方法总是onStart()。 |
否 |
onStart() |
onRestart() |
activity被停止后、又再次被启动之前调用。 下一个回调方法总是onStart() |
否 |
onStart() |
onStart() |
activity要显示给用户之前调用。 如果activity进入前台,则下一个回调方法是onResume();如果进入隐藏状态,则下一个回调方法是onStop()。 |
否 |
onResume() 或 onStop() |
onResume() |
activity开始与用户交互之前调用。这时activity是在activity栈的顶端,用户可以向其中输入。 下一个回调方法总是onPause()。 |
否 |
onPause() |
onPause() |
当系统准备启动另一个正在恢复的activity时调用。这个方法通常用于把未保存的改动提交为永久数据、停止动画播放、以及其它可能消耗CPU的工作等等。 它应该非常迅速地完成工作,因为下一个activity在本方法返回前是不会被恢复运行的。 如果activity返回前台,则下一个回调方法是onResume();如果进入用户不可见状态,则下一个是onStop() |
可以 |
onResume() 或 onStop() |
onStop() |
当activity不再对用户可见时调用。原因可能是它即将被销毁、或者其它activity(已有或新建的)被恢复运行并要覆盖本activity。 如果activity还会回来与用户交互,则下一个回调方法是onRestart();如果这个activity即将消失,则下一个回调方法是onDestroy() |
可以 |
onRestart() 或 onDestroy() |
onDestroy() |
在本activity被销毁前调用。这是activity收到的最后一个调用。 可能是因为activity完成了工作(有些人在这里调用finish()), 也可能是因为系统为了腾出空间而临时销毁activity的本实例。 可以利用isFinishing() 方法来区分这两种情况。 |
可以 |
无 |
表1
标为“之后可否被杀死?”的列指明了系统是否可以在这个方法返回之后的任意时刻杀掉这个 activity的宿主进程, 而不再执行其它流程上的activity代码。 有三个方法是标为“可以”:( onPause()、 onStop()、 和onDestroy())。 因为onPause()是三个方法中的第一个, 一旦activity被创建, onPause() 就是进程可以被杀死之前最后一个能确保被调用的方法 ——如果系统在某种紧急情况下必须回收内存,则 onStop() 和onDestroy() 可能就不会被调用了。因此,你应该使用 onPause() 来把至关重要的需长期保存的数据写入存储器(比如用户所编辑的内容)。 不过,应该对必须通过 onPause() 方法进行保存的信息有所选择,因为该方法中所有的阻塞操作都会让切换到下一个activity的停滞,并使用户感觉到迟缓。
“之后可否被杀死?”列中标为“否”的方法,在它们被调用时的那一刻起,就 会保护本activity的宿主进程不被杀掉。 因此,只有在 onPause() 方法返回时至 onResume() 方法被调用时之间,activity才会被杀掉。直到 onPause() 再次被调用并返回时,activity都不会再次允许被杀死。
Note:表1中标明的技术上不“可杀”的activity仍然有可能会被系统杀死——但那只有在没有任何资源的极端情况下才会发生。 什么时候activity会被杀掉,已在文档进程和线程里详细说明了。
-
-
状态保存
-
当一个activity被paused或者stopped时,activity的状态可以被保存。 的确如此,因为 Activity 对象在paused或者stopped时仍然被保留在内存之中——它所有的成员信息和当前状态都仍然存活。 这样用户在activity里所作的改动全都还保存着,所以当activity返回到前台时(当它“resume“),那些改动仍然有效。
不过,如果系统是为了回收内存而销毁activity,则这个 Activity 对象就会被销毁,这样系统就无法简单地resume一下就能还原完整状态的activity。 如果用户要返回到这个activity的话,系统必须重新创建这个Activity 对象。可是用户并不知道系统是先销毁activity再重新创建了它的,所以,他很可能希望activity完全保持原样。 这种情况下,你可以保证activity状态的相关重要信息都由另一个回调方法保存下来了,此方法让你能保存activity状态的相关信息: onSaveInstanceState()。
在activity变得很容易被销毁之前,系统会调用 onSaveInstanceState()方法。 调用时系统会传入一个Bundle对象, 你可以利用 putString() 之类的方法,以键值对的方式来把activity状态信息保存到该Bundle对象中。 然后,如果系统杀掉了你的application进程并且用户又返回到你的activity,系统就会重建activity并将这个 Bundle 传入onCreate() 和onRestoreInstanceState() 中,你就可以从 Bundle 中解析出已保存信息并恢复activity状态。如果没有储存状态信息,那么传入的 Bundle 将为null(当activity第一次被创建时就是如此)。
activity状态完整地返回给用户的两种方式:被销毁destroyed后再被重建,而且必须恢复了之前保存的状态;或是被停止stopped后再被恢复resumed,状态都完整保留着。
注意: activity被销毁之前,并不能确保每次都会调用 onSaveInstanceState() ,因为存在那些不必保存状态的情况(比如用户使用BACK键离开了你的activity,因为用户明显是关了这个activity)。 如果系统要调用 onSaveInstanceState() 方法,那么它通常会在 onStop() 方法之前并且可能是在 onPause() 之前调用。
不过,即使你没有实现 onSaveInstanceState() 方法,有些activity状态还是会通过 Activity 类缺省实现的onSaveInstanceState() 方法保存下来。特别的是,缺省为layout中的每个 View 实现了调用相应的onSaveInstanceState() 方法,这允许每一个view提供自己需被保存的信息。 几乎Android框架下所有的widget都会在适当的时候实现该方法,这样,任何UI上可见的变化都会自动保存下来,并在activity重建后自动恢复。 例如,EditText widget会保存所有用户已经输入的文本, CheckBoxwidget 也会保存是否被选中。你所要做的工作仅仅是为每一个你想要保存其状态的widget提供一个唯一的ID(就是 android:id 属性)。如果这个widget没有ID的话,系统是无法保存它们的状态的。
通过把android:saveEnabled 设置为"false",或者调用 setSaveEnabled() 方法,你也可以显式地阻止layout中的某个view保存状态。 通常不应该禁用保存,不过假如你需要恢复activity UI的各个不同的状态,也许可以这么做。尽管缺省实现的 onSaveInstanceState() 方法会保存activity UI的有用信息,你仍然需要覆盖它来存入更多的信息。 例如,你可能需要保存在activity生命周期中改变的成员变量值(可能是关于UI恢复的值,但是默认情况下,存放这些UI状态的成员变量值是不会被恢复的)。
因为默认实现的 onSaveInstanceState() 方法已经帮你保存了一些UI的状态,所以如果你重写此方法是为了保存更多的状态信息,那么在执行自己的代码之前应该确保先调用一次父类的 onSaveInstanceState() 方法。同理,如果重写 onRestoreInstanceState() 的话,也应该调用一次父类的该方法,这样缺省的代码就能正常恢复view的状态了。
注意:因为 onSaveInstanceState() 并不保证每次都会被调用,所以你应该只用它来记录activity的一些临时状态信息(UI的状态)——千万不要用它来保存那些需要长久保存的数据。 替代方案是,你应该在用户离开activity的时候利用 onPause() 来保存永久性数据(比如那些需要存入数据库里的数据)。
一个检测应用程序状态恢复能力的好方法就是旋转设备,使得屏幕方向发生改变。 当屏幕的方向改变时,因为要换用符合实际屏幕参数的资源,系统会销毁并重建这个activity。 正因如此,你的activity能够在被重建时完整地恢复状态是非常重要的,因为用户会在使用应用程序时会频繁地旋转屏幕。
设备的某些配置可能会在运行时发生变化(比如屏幕方向、键盘可用性以及语言)。 当发生这些变化时,Android会重建这个运行中的activity(系统会调用 onDestroy() ,然后再马上调用 onCreate() )。这种设计有助于应用程序适用新的参数配置,通过把你预置的可替换资源(比如对应各种屏幕方向和尺寸的layout)自动重新装载进入应用程序的方式来实现。
如果你采取了适当的设计,让activity能够正确地处理这些因为屏幕方向而引起的重启,并能如上所述地恢复activity状态, 那么你的应用程序将对生命周期中其它的意外事件更具适应能力。
处理这类重启的最佳方式,就是利用 onSaveInstanceState() 和onRestoreInstanceState() (或者 onCreate() )进行状态的保存和恢复,如上节所述。
-
-
任务和回退栈
-
Task(任务) 是多个 activity 的集合,用户进行操作时将与这些 activity 进行交互。 这些 activity 按照启动顺序排队存入一个回退栈(即“back stack”)。
大部分 task 都启动自 Home 屏幕。当用户触摸 application launcher 中的图标(或 Home 屏幕上的快捷图标)时,应用程序的 task 就进入前台。 如果该应用不存在 task(最近没有使用过此应用),则会新建一个 task,该应用的“main”activity 作为栈的根 activity 被打开。
当用户返回到 home屏幕执行另一个 task 时,一个 task 被移动到后台执行,此时它的返回栈(back stack)也被保存在后台, 同时 android 为新 task 创建一个新的返回栈(back stack),当它被再次运行从而返回前台时,它的返回栈(back stack)被移到前台,并恢复其之前执行的activity,如下图所示。 如果后台有太多运行 task ,系统将会杀死一些 task 释放内存。
如果当前 activity 启动了另一个 activity,则新的 activity 被压入栈顶并获得焦点。 前一个 activity 仍保存在栈中,但是被停止。activity 停止时,系统会保存用户界面的当前状态。 当用户按下返回键,则当前 activity 将从栈顶弹出(被销毁),前一个 activity 将被恢复(之前的用户界面状态被恢复)。 activity 在栈中的顺序永远不会改变,只会压入和弹出——被当前 activity 启动时压入栈顶,用户用返回键离开时弹出。 这样,back stack 以“后进先出”的方式运行。图1 以时间线的方式展示了多个 activity 切换时对应当前时间点的 back stack 状态。
-
-
加载模式
-
加载模式定义了一个新的 activity 实例与当前 task 的关联方式。定义加载模式的方法有两种:使用 manifest 文件:当你在 manifest 文件中声明一个 activity 时,可以指定它启动时与 task 的关联方式,你可以利用 <activity> 元素的 launchMode 属性来设定 activity 与 task 的关系。 使用 Intent 标志 调用 startActivity() 时,可以在 Intent 中包含一个标志,用于指明新 activity 如何(是否)与当前 task 相关联。launchMode 属性可设为四种启动模式:standard,singleTop ,singleTask ,singleInstance 。
-
-
-
-
standard
-
-
图3.1
Standard:标准模式,这是默认的加载模式,每次通过这种模式来启动目标Activity的时候,Android总会为目标Activity创建一个新的实例,并且将Activityt添加到当前的Task 中(如Standard图例一)。
-
-
-
singleTop
-
-
Acitivity如果采用singleTop加载模式那么在同一个Task内可能有一个或者多个,当系统采用singleTop模式启动目标Activity时,可以分为以下三种情况:
-
若要启动的的目标Activity不存在,系统则会创建目标Acitivity的实例,并且将它们加入Task的栈顶(如图3.2)。
-
若要启动的目标Activity正好位于Task栈顶,则复用已有的Activity( 如singleTop图3.2)。
3. 若要启动的的目标Activity存在,但是没有位于Task栈顶,系统还是会去重新创建新的Activity实例( 如singleTop图3.3)
图3.2
图3.3
-
-
-
singleTask
-
-
Acitivity如果采用singleTask加载模式那么在同一个Task内只有一个实例,当系统采用singleTask模式启动目标Activity时,可以分为以下三种情况:
-
若要启动的的目标Activity不存在,系统则会创建目标Acitivity的实例,并且将它们加入Task的栈顶(如图3.4-1)。
-
若要启动的目标Activity正好位于Task栈顶,此时和singleTop模式的行为相同。
-
若要启动的的目标Activity存在,但是没有位于Task栈顶,系统则会把位于该Activity上面的所有Activity移出Task栈,从而是得目标Activity转入栈顶(如图3.4-2和3.4-3) 。
图3.4
singleInstance
在此种加载模式下,无论从哪个Task中启动目标Activity,系统会创建一个目标Activity实例且会用一个全新的Task栈来装载该Activity实例.( singleInstance图例一、二)
当系统采用singleInstance模式加载Activity时,又分为以下两种情况:
-
如果将要启动的Activity不存在,那么系统将会先创建一个全新的Task,再创建目标Activity实例并将该Activity实例放入此全新的Task中
-
如果将要启动的Activity已存在,那么无论它位于哪个应用程序,哪个Task中;系统都会把该Activity所在的Task转到前台,从而使该Activity显示出来(singleInstance图例二)
图3.5
-
-
-
四种模式对比
-
-
模式 |
是否允许多个实例 |
是否每次都生成新实例 |
是否允许其他Activity存在于本Task内 |
standard |
允许 |
是 |
允许 |
singleTop |
允许 |
如果Task的栈顶为该Activity的实例,则复用该实例;否则则创建新实例 |
允许 |
singleTask |
不能有多个实例。由于该模式下Activity总是位于栈顶,所以Actvity在同一个设备里最多只有一个实例 |
只有在第一次才创建新的 实例,其他情况复用该 Activity |
允许 |
singleInstance |
同singleTask |
同singleTask |
不允许 |
-
-
-
处理affinity
-
-
affinity 表示 activity 预期所处的 task 。 缺省情况下,同一个应用中的所有 activity 都拥有同一个 affinity 值。 因此,同一个应用中的所有 activity 默认都期望位于同一个 task 中。 不过,你可以修改 activity 默认的 affinity 值。 不同应用中的 activity 可以共享同一个 affinity 值,同一个应用中的 activity 也可以赋予不同的 task affinity 值。 你可以用<activity> 元素的taskAffinity 属性修改 activity 的 affinity,taskAffinity 属性是一个字符串值,必须与<manifest> 元素定义的包名称保证唯一性,因为系统把这个包名称用于标识应用的默认 task affinity 值。
affinity 将在以下两种情况下发挥作用:
-
当启动 activity 的 intent 包含了FLAG_ACTIVITY_NEW_TASK标志。
默认情况下,一个新的 activity 将被放入调用 startActivity() 的 activity 所在 task 中,且压入调用者所处的 back stack 顶。 不过,如果传给 startActivity() 的 intent 包含了 FLAG_ACTIVITY_NEW_TASK 标志,则系统会查找另一个 task 并将新 activity 放入其中。这时经常会新开一个任务,但并非一定如此。 如果一个已有 task 的 affinity 值与新 activity 的相同,则 activity 会放入该 task。 如果没有,则会新建一个新 task。
如果本标志使得 activity 启动了一个新的 task,用户按下 Home 键离开时,必须采取一些措施让用户能回到此 task。 某些应用(比如通知管理器)总是让 activity 放入其它 task 中启动,而不是放入自己的 task 中。 因此,它们总是把 FLAG_ACTIVITY_NEW_TASK 标志置入传给 startActivity() 的 intent 中。如果你的 activity 可以被外部应用带此标志来启动,请注意用户会用其它方式返回启动 task,比如通过应用图标(task 的根 activity 带有一个 CATEGORY_LAUNCHER intent 过滤器;参阅下节#启动task)。
-
当一个 activity 的allowTaskReparenting属性设为 "true"。
这种情况下,当某个 task 进入前台时,activity 的 affinity 值又与其相同,则它可以从启动时的 task 移入这个 task 中。
比如,假定某旅游应用中有一个 activity 根据所选的城市来报告天气情况。 它的 affinity 与同一应用中的其它 activity 一样(整个应用默认的 affinity),且它允许重新指定此属性的归属。 当你的另一 activity 启动此天气报告 activity 时,它会在同一个 task 中启动。 然而,当旅游应用的 task 进入前台时,则天气报告 activity 将会重新放入其 task 中并显示出来。
提示: 如果一个 .apk 文件中包含了多个“application”,你可能需要用 taskAffinity 属性来指定每个“application”中 activity 的 affinity 值
-
-
-
清理back stack
-
-
如果用户长时间离开某个 task,系统将会仅保留一个根 activity,而把其它 activity 都清除掉。 当用户返回 task 时,只有根 activity 会被恢复。 系统之所以这么处理,是因为经过了很长时间后,用户是要放弃之前进行的工作,返回 task 是为了开始新的工作。
你可以利用 activity 的某些属性来改变这种方式:
如果 task 中根 activity 的此属性设为 "true" ,则默认的清理方式不会进行。即使过了很长时间,task 中所有的 activity 也都会保留在栈中。
如果 task 中根 activity 的此属性设为 "true",则只要用户离开并再次返回该 task,栈就会被清理至根 activity。也就是说,正好与alwaysRetainTaskState相反。用户每次返回 task 时看到的都是初始状态,即使只是离开一会儿。
此属性类似于clearTaskOnLaunch,只是它只对一个 activity 有效,不是整个 task。这能让任何一个 activity 消失,包括 根 activity。如果 activity 的此属性设为 "true",则只会保留 task 中当前 session 所涉及的内容。如果用户离开后再返回 task,它就不存在了。