2019-05-08
Activity生命周期
(一)简介
onCreate()
在系统创建活动时触发;
onStart()
作为onCreate()退出时,活动进入开始状态,并且activity变得对用户可见;
onResume()
当一个活动和用户发生交互的时候,触发该方法。此时,activity位于activity堆栈的顶部,并捕获所有用户输入。应用程序的大多数核心功能是在此方法中实现的。
onPause()
当活动失去焦点并进入暂停状态时,系统会调用。例如当用户点击“后退”或“最近”按钮时,会出现此状态。
当系统调用此方法时,通常表示用户正在离开此activity(文档中表示为partially visable,也就是部分可见),并将很快进入“已停止”或“已恢复”状态。在一些场景下会需要调用此方法:示出(也就是该activity不被显示在屏幕上时)导航地图屏幕或媒体播放器播放的活动。即使这些活动失去焦点,用户也希望它们的UI继续更新。
你不应该在此方法中保存应用程序或用户数据,进行网络通话,或执行数据库事务。
一旦onPause()完成执行,下一个回调是onStop()或onResume(),视activity进入暂停状态后发生的情况而定。
onStop()
当活动不再对用户可见时调用。
此方法被调用的情况有:activity正在被破坏,新activity正在开始,或现有activity正在进入恢复状态并且覆盖已停止的活动。
如果内存紧张,系统会直接结束这个活动,而不会触发 onStop 方法。 所以保存状态信息是应该在onPause时做,而不是onStop时做。
onRestart()
当处于“已停止”(Stop)状态的activity即将重新启动时,系统将调用此方法。此回调始终紧跟着onStart()后发生。
onDestory()
此方法在销毁activity之前被调用。通常实现以确保在activity或包含它的进程被销毁时释放所有活动的资源。
==额外:==
onSaveInstanceState() :系统调用该方法,允许活动保存之前的状态,比如说在一串字符串中的光标所处的位置等。
通常情况下,开发者不需要重写覆盖该方法,在默认的实现中,已经提供了自动保存活动所涉及到的用户界面组件的所有状态信息。
(二)Activity状态
一般认为Activity有以下四种状态:
-
活动的:当一个Activity在栈顶,它是可视的、有焦点、可接受用户输入的。Android试图尽最大可能保持它活动状态,杀死其它Activity来确保当前活动Activity有足够的资源可使用。当另外一个Activity被激活,这个将会被暂停。
-
暂停:在很多情况下,你的Activity可视但是它没有焦点,换句话说它被暂停了。有可能原因是一个透明或者非全屏的Activity被激活。
当被暂停,一个Activity仍会当成活动状态,只不过是不可以接受用户输入。在极特殊的情况下,Android将会杀死一个暂停的Activity来为活动的Activity提供充足的资源。当一个Activity变为完全隐藏,它将会变成停止。 -
停止:当一个Activity不是可视的,它“停止”了。这个Activity将仍然在内存中保存它所有的状态和会员信息。尽管如此,当其它地方需要内存时,它将是最有可能被释放资源的。当一个Activity停止后,一个很重要的步骤是要保存数据和当前UI状态。一旦一个Activity退出或关闭了,它将变为待用状态。
-
待用: 在一个Activity被杀死后和被装载前,它是待用状态的。待用Acitivity被移除Activity栈,并且需要在显示和可用之前重新启动它。
(三)后台堆栈模型
- 当前活动开始另一个活动时,新活动将被推到堆栈顶部并获得焦点。之前的活动仍在堆栈中,但已停止。当活动停止时,系统将保留其用户界面的当前状态。当用户按下“ 返回” 按钮时,当前活动将从堆栈顶部弹出(活动被销毁),之前的活动将恢复(其UI的先前状态将恢复)。堆栈中的活动永远不会重新排列,只能在当前活动启动时从堆栈中推送并弹出到堆栈中,并在用户使用Back退出时弹出按钮。因此,后堆栈作为“后进先出”对象结构操作。图1显示了这种行为,时间轴显示了活动之间的进度以及每个时间点的当前后栈。 任务堆栈
注意:可以在后台同时保存多个任务。但是,如果用户同时运行许多后台任务,系统可能会开始销毁后台活动以恢复内存,从而导致活动状态丢失。
- 由于后备堆栈中的活动永远不会重新排列,如果您的应用程序允许用户从多个活动启动特定活动,则会创建该活动的新实例并将其推送到堆栈(而不是带来任何先前的活动实例)到顶部)。因此,应用程序中的一个活动可能会被多次实例化(甚至来自不同的任务),因此,如果用户使用“ 后退”按钮向后导航,则活动的每个实例都按顺序显示被打开(每个都有自己的UI状态)。但是,如果您不希望多次实例化活动,则可以修改此行为。有关如何执行此操作将在后面的“ ==管理任务==”一节中讨论。
(四)管理任务
定义启动模式
启动模式允许您定义活动的新实例与当前任务堆栈的关联方式。您可以通过两种方式定义不同的启动模式:使用清单文件或使用Intent标志。(清单文件Manifest.xml优先级高一些)
使用清单文件定义启动模式:
在清单文件中声明活动时,可以使用<activity> 元素的launchMode属性指定活动应如何与任务关联。其有四种不同的启动模式:
- "standard" (默认模式):系统在启动它的任务中创建活动的新实例,并将意图路由到该实例。活动可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例。
- "singleTop":若activity的实例已存在于当前任务堆栈顶部(也就是当前活动),则系统通过调用onNewIntent()方法将Intent路由到该实例,而不是创建activity的新实例;反之则和standard模式一样。
- "singleTask":系统创建新任务堆栈并将activity放在其根目录下实例化。但如果activity的实例已存在于单独的任务堆栈中,则系统会通过调用onNewIntent()路由到现有实例,而不创建新的。一次只能存在一个该activity实例。
注意:虽然activity在新任务中启动,但“后退”按钮仍会将用户返回到上一个activity。
- "singleInstance":与上一个类似,区别在于:系统不启动任何其他活动纳入控股实例的任务。活动始终是其任务的唯一成员; 任何由此开始的活动都在一个单独的任务中打开。
实例
- 假设任务的后台堆栈由根活动A组成,其中活动B,C和D位于顶部(堆栈为ABCD; D位于顶部)。意图到达类型D的活动。如果D具有默认"standard"启动模式,则启动该类的新实例并且堆栈变为ABCDD。但是,如果D的启动模式是"singleTop",D的现有实例接收意图onNewIntent(),因为它位于堆栈的顶部 - 堆栈仍然是ABCD。但是,如果意图到达类型B的活动,则将新的B实例添加到堆栈中,即使其启动模式为"singleTop"。
-
Android浏览器应用程序声明Web浏览器活动应始终在其自己的任务中打开 - 通过singleTask在<activity>元素中指定启动模式。这意味着,如果您的应用发出打开Android浏览器的意图,则其活动不会与您的应用放在同一任务中。相反,要么为浏览器启动新任务,要么如果浏览器已经在后台运行任务,则该任务将被提前处理新意图。
实例2
无论活动是在新任务中启动还是在与启动它的活动相同的任务中启动,“ 返回”按钮始终会将用户带到上一个活动。但是,如果启动指定singleTask启动模式的活动,则如果后台任务中存在该活动的实例,则将整个任务带到前台。此时,后端堆栈现在包括在堆栈顶部提出的任务中的所有活动。上图说明了这种情况。
使用Intent标志定义启动模式
启动活动时,您可以通过在传递到的intent中包含标志来修改活动与其任务的默认关联。您可以用来修改默认行为的标志是:
- FLAG_ACTIVITY_NEW_TASK:与singleTask相同。
- FLAG_ACTIVITY_SINGLE_TOP:与singleTop相同。
- FLAG_ACTIVITY_CLEAR_TOP:如果正在启动的活动已在当前任务中运行,则不会启动该活动的新实例,而是销毁其上的所有其他活动,并将此意图传递给活动的恢复实例(现在在顶部的)。launchMode没有此值。FLAG_ACTIVITY_CLEAR_TOP最常与 FLAG_ACTIVITY_NEW_TASK共同使用。当一起使用时,这些标志是一种在另一个任务中定位现有活动并将其置于可以响应意图的位置的方法。
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
启动模式的区别
启动模式的介绍:堆栈与启动模式
四种加载模式的区别:
-
所属task的区别
一般情况下,“standard”和”singleTop”的activity的目标task,和收到的Intent的发送者在同一个task内,就相当于谁调用它,它就跟谁在同一个Task中。
除非Intent包括参数FLAG_ACTIVITY_NEW_TASK。如果提供了FLAG_ACTIVITY_NEW_TASK参数,会启动到别的task里。
“singleTask”和”singleInstance” 总是把要启动的activity作为一个task的根元素,他们不会被启动到一个其他task里。 -
是否允许多个实例
“standard”和”singleTop”可以被实例化多次,并且是可以存在于不同的task中;这种实例化时一个task可以包括一个activity的多个实例;
“singleTask”和”singleInstance”则限制只生成一个实例,并且是task的根元素。
singleTop 要求如果创建intent的时候栈顶已经有要创建的Activity的实例,则将intent发送给该实例,而不创建新的实例。 -
是否允许其它activity存在于本task内
“singleInstance”独占一个task,其它activity不能存在那个task里;
如果它启动了一个新的activity,不管新的activity的launch mode 如何,新的activity都将会到别的task里运行(如同加了FLAG_ACTIVITY_NEW_TASK参数)。
而另外三种模式,则可以和其它activity共存。 -
是否每次都生成新实例
“standard”对于每一个启动Intent都会生成一个activity的新实例;
“singleTop”的activity如果在task的栈顶的话,则不生成新的该activity的实例,直接使用栈顶的实例,否则,生成该activity的实例。
比如:
现在task栈元素为A-B-C-D(D在栈顶),这时候给D发一个启动intent,如果D是 “standard”的,则生成D的一个新实例,栈变为A-B-C-D-D。
如果D是singleTop的话,则不会生产D的新实例,栈状态仍为A-B-C-D
如果这时候给B发Intent的话,不管B的launchmode是”standard” 还是 “singleTop” ,都会生成B的新实例,栈状态变为A-B-C-D-B。
“singleInstance”是其所在栈的唯一activity,它会每次都被重用。
“singleTask” 如果在栈顶,则接受intent,否则,该intent会被丢弃,但是该task仍会回到前台。 当已经存在的activity实例处理新的intent时候,会调用onNewIntent()方法,如果收到intent生成一个activity实例,那么用户可以通过back键回到上一个状态;如果是已经存在的一个activity来处理这个intent的话,用户不能通过按back键返回到这之前的状态。
(五)处理activity状态更改
发生配置更改
有许多事件可以触发配置更改。最突出的例子是屏幕纵向和横向之间的变化。其他情况包括更改语言或输入设备。
当发生配置更改时,将销毁并重新创建activity。原来的activity实例对象的onPause()、onStop()和onDestory()方法将被触发。而activity的一个新实例将被创建(也就是onCreate()、onStart()和onResume()方法会被触发)。
可以使用ViewModel,onSaveInstanceState()方法和持久本地存储的组合来保持activity在配置更改中的UI状态。这里需要根据App情况进行选择。
activity或对话框出现在前台(foreground)
这里有两种情况:
-
activity的焦点被部分覆盖:系统会调用onPause()方法,使activity进入暂停状态,当被覆盖的activity重新获得焦点时,会调用onResume()方法。
-
前台出现新activity或对话框,关注焦点并完全覆盖正在进行的activity:当前activity进入“已停止”状态,系统快速连续调用onPause()和onStop()。当覆盖活动的同一实例回来到前台时,系统调用onRestart(), onStart()以及 onResume()对活动。如果它是覆盖活动的新实例,则系统不会调用onRestart(),只调用 onStart()和 onResume()。
注意:当用户点击“概览”或“主页”按钮时,系统的行为就像当前活动已完全覆盖一样。
用户点击后退按钮(Back button)
如果activity是在前台,而用户点击后退按钮(回到桌面或主页面),该activity通过调用onPause()、onStop()和onDestory()方法。除了被销毁之外,activity也从后台堆栈中移除。
==要注意的是:==默认情况下,onSaveInstanceState()在这种情况下不会触发。但可以在这种情况下覆盖该****onBackPressed()以实现某些自定义行为,例如“确认退出?”对话框。
系统会杀死应用程序进程
如果应用程序在后台并且系统需要为前台应用程序释放额外的内存,则系统可以终止后台应用程序以释放更多内存。
==补充==:onSaveInstanceState()被触发的条件
- 背景:用户期望活动的UI状态在整个配置更改期间保持不变,例如旋转或切换到多窗口模式。但是,当发生此类配置更改时,系统会默认销毁活动,从而消除存储在活动实例中的任何UI状态。同样,用户希望UI状态保持不变,如果他们暂时从您的应用切换到其他应用,然后稍后再回到您的应用。但是,当用户离开并且您的活动停止时,系统可能会破坏您的应用程序的进程。
- 解决办法:如果您的UI数据简单而轻量级,例如原始数据类型或简单对象(如String),则可以单独使用onSaveInstanceState()来保持UI状态跨越配置更改和系统启动的进程死亡。但是,在大多数情况下,您应该使用ViewModel和onSaveInstanceState()(如 保存UI状态中所述),因为onSaveInstanceState()会导致序列化/反序列化成本。
-
当您的活动开始停止时,系统会调用该 onSaveInstanceState() 方法,以便您的活动可以将状态信息保存到实例状态包。此方法的默认实现保存有关活动视图层次结构状态的瞬态信息,例如EditText窗口小部件中的文本或窗口小部件的滚动位置 ListView。
要为活动保存其他实例状态信息,必须覆盖 onSaveInstanceState() 并将键值对添加到在Bundle活动意外销毁时保存的对象。如果重写onSaveInstanceState(),则必须调用超类实现,如果希望默认实现保存视图层次结构的状态。例如:
static final String STATE_SCORE = “playerScore” ;
static final String STATE_LEVEL = “playerLevel” ;
@Override
public void onSaveInstanceState (Bundle savedInstanceState ){
//保存用户当前的游戏状态
savedInstanceState.putInt (STATE_SCORE ,currentScore );
savedInstanceState.putInt (STATE_LEVEL ,currentLevel );
//始终调用超类,以便保存视图层次结构状态
super.onSaveInstanceState (savedInstanceState );
}
注意: 当用户显式关闭活动时或当finish()被调用时,onSaveInstanceState()不会被回调
要保存持久性数据(例如用户首选项或数据库的数据),您应该在活动位于前台时采取适当的机会。如果没有这样的机会,您应该在onStop()方法期间保存这些数据 。
- 使用onRestoreInstanceState(Bundle saveInstanceState)方法来还原已保存的状态。(你当然也可以在onCreate()中还原已保存的状态,但需要额外判断是否是销毁后重新创建此Activity,也就是要判断onCreate(Bundle savedInstanceState)中的savedInstanceState!=null)
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
currentScore = savedInstanceState.getInt(STATE_SCORE);
currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
(六) Activity生命周期交互
多个Activity之间切换时的状态变化
- 情景一:当打开一个app,进入MainActivity时,会依次调用onCreate()、onStart()和onResume()这三个方法。然后当用户点击某个按钮进入到另一个Activity时(这里称之为second_Activity),首先MainActivity会调用onPause()方法进入“已暂停”状态,接着是second_Activity开始依次调用onCreate()、onStart()和onResume(),当second_Activity进入到“可见”状态时,MainActivity就会调用onStop()方法。至此,从一个activity跳转到另一个activity的状态变化就结束了。
- 情景二:接着上述,当在second_Activity中点击“回退”时,该activity会被暂停,也就是调用onPause()方法,然后前一个activity,也就是MainActivity会依次调用onRestart()、onStart()和onResume()方法来重新启动(当activity进入Stopped状态时,若要跳到Started状态,要先执行onRestart()方法,再执行onStart()方法)。而secondActivity接着会调用onStop()和onDestory()方法,这样,second_Activity就会被彻底销毁。
疑问:在打开新的Activity时,为什么不执行当前activity的onPause()和onStop()方法,而是将二者分别在要跳转的activity的三个方法执行前后调用?
答案:onPause()方法一般用于关闭当前Activity的视频音频,或者其他可能妨碍到新的Activity的功能(比如接电话等)。还可以在跳转到新的Activity前保存当前Activity的一些状态信息等。而在第二个Activity完全加载之后才执行原Activity的onStop()方法,这个设计主要是为了防止当第二个Activity在加载时出错出现闪退等异常时,第一个Activity还能够显示在屏幕上,而不是“不可见状态”。所以说这是一种考虑了很多情况的设计。
横竖屏切换时的状态变化
横竖屏切换时,系统会在当前Activity依次执行onPause()、onStop()和onDestory(),然后再重新创建。若想要在切换前后有一致的UI状态,就需要重载onSaveInstanceState(Bundle outState)方法来将信息保存进outState中,然后在onRestoreInstanceState中还原。
生命周期应用场景
媒体播放器播放时跳转到其他Activity
假如当前Activity在播放着音乐,突然一个电话过来,那么用户肯定是想当前音乐停止然后切换到电话界面,当电话结束后回退到原来界面时,继续从刚才的地方播放音乐。根据第一条中的情景二,我们知道此时需要分别在onPause()和onResume()方法中实现一些操作。
- 在onPause()方法中,判断播放器在播放的话,就暂停(mediaPlayer.pause()),并且记录下当前的播放位置(int position=mediaPlayer.getCurrentPosition())
- 而在onResume()方法中,则需要在position!=0的情况下,将音乐调整到position,然后start。(mediaPlayer.seekTo(position);mediaPlayer.start())。
- 其实还应该在onDestroy中释放掉音乐组件,以免内存泄漏。
Activity启动的另一种方式:匿名启动
如下所示,在Manifest.xml文件中,我们可以在<intent-filter>中插入<action>b并添加属性android:name,这样别的Activity就可以通过这个名字来start此Activity,而不是通过Activity真实的类名(在调用其他App的界面时会使用到,自己的App内部没必要用这种方式)。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="jieko" /> //这里可以为此Activity生成自定义的名字
//category指定Activity启动的环境
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
//调用方式:
Intent intent = new Intent();
intent.setAction("jieko");
startActivity(intent);
还可以通过上面的“调用方法”调用一些系统内置的一些应用,比如浏览器、图库、相机、短信、电话等等。示例如下:
//调用浏览器
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); //Intent中内置有一些常量,用来调用系统app
Uri uri = Uri.parse("...");
intent.setData(uri);
startActivity(intent);