一、活动的生命周期
1、活动状态
-
运行状态
当一个活动位于栈顶的时候,该活动就处于运行状态。(Android是使用任务栈来管理活动的,也称返回栈(Back Stack)。在默认情况下,每当我们启动一个新的活动,它会在返回栈中入栈,并处于栈顶位置。每当我们按下Back键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动会位于栈顶。系统总是会显示处于栈顶的活动给用户。) -
暂停状态
当一个活动不再处于栈顶的位置,但仍然可见时,这时活动就进入了暂停状态。处于暂停状态的活动是完全存活的。 -
停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。 -
销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。
2、活动的回调方法
- onCreate()
①Activity开始创建时
②程序在暂停、停止状态下被杀死后重新打开时 - onStart()
①onCreate()方法之后,即Activity正在被启动时(此时,Activity还不可见(在后台),还无法与用户交互)
②Activity从停止状态恢复时调用 - onResume()
①Activity第一次启动完毕(onStart()方法之后调用)
②当前Activity被挡住一部分后,重新回到当前Activity时(从暂停状态恢复时调用)
③界面获得焦点 - onPause()
①Activity停止时,仍可见时
②界面失去焦点 - onStop()
Activity不在UI最顶层,完全被挡住不可见时 - onDestroy()
Activity即将被销毁时 - onRestart()
Activity处于onStop()状态时(不可见时)重新回到UI最顶层
3、活动的生命周期
(1)Activity启动:onCreate()→onStart()→onResume()
(2)点击Home键回到主界面/打开新的Activity:onPause()→onStop()
注:有一种特殊情况,如果新Activity采用了透明主题,那么当前Activity不会回调onStop()
(3)再次回到原Activity时:onRestart()→onStart()→onResume()
(4)退出当前Activity时:onPause()→onStop()→onDestroy()
(5)当执行到onPause()方法Activity失去焦点时,重新回到前台会执行onResume()方法,如果此时进程被销毁Activity重新执行时会先执行onCreate()方法。
(6)当执行到onStop()方法Activity不可见时,再次回到前台会执行onRestart()方法,如果此时进程被销毁Activity会重新执行onCreate()方法。
(7)两个Activity(A、B)切换(B是正常的Activity)的生命周期:onPause(A)->onCreate(B)->onStart(B)->onResume(B)->oStop(A)。这时如果按回退键回退到A,生命周期为:onPause(B)->onRestart(A)->onStart(A)->onResume(A)->oStop(B)。如果在切换到B后调用了A.finish(),则会走到onDestory(A),这时点回退键会退出应用。
(8)两个Activity(A、B)切换(B透明主题的Activity或是Dialog风格的Acivity)的生命周期:onPause(A)->onCreate(B)->onStart(B)->onResume(B)。这时如果回退到A ,生命周期为:onPause(B)->onResume(A)->oStop(B)->onDestory(B)。
4、切换横竖屏时Activity的生命周期
(1)不设置Activity的android:configChanges时,切屏时会重新调用各自的生命周期,切横屏时会执行一次,切竖屏时会执行两次。
- 切横屏时:执行一次生命周期方法
onSaveInstancsState()→onPause()→onStop()→onDestroy()→onCreate()→onStart()→onRestoreInstanceState()→onResume() - 切竖屏时:执行两次生命周期方法
onSaveInstancsState()→onPause()→onStop()→onDestroy()→onCreate()→onStart()→onRestoreInstanceState()→onResume()
(2)切换Activity的android:configChanges="orientation"时,切屏时还是会重新调用各自的生命周期,切横、竖屏时只会执行一次。
(3)设置Activity的android:congifChanges="orientation|keyboardHidden"时,切屏时不会重新调用各自的生命周期,只会执行onConfigurationChanged()方法。
注:切换横竖屏时,若想保存网页数据,可以通过重写onSaveInstanceState()方法实现;若想恢复数据,则可通过重写onRestoreInstanceState()方法实现。
二、Activity的启动方式
Activity是通过Intent来启动的,在启动时分两种情况,一种是显式启动,一种是隐式启动,具体如下:
(1)显式启动
该启动方式比较快速,创建Intent后直接指定包名和类名即可。
- 1、直接设置相应Activity的class来启动新的Activity
- 2、通过设置包名和全类名来启动新的Activity
(2)隐式启动
该启动方式不显式指定组件,而是通过动作、类型、数据匹配对应的组件。
使用隐式子Intent需要注意:
- 1、只有< action >和< category >中的内容同时能够匹配上Intent指定的action和category时,这个活动才能响应该Intent。
- 2、每个Intent中只能指定一个action,但却能指定多个category。
- 3、只有< data >标签中指定的内容和Intent中携带的Data完全一致时,当前活动才能够响应该Intent。
- 4、当代码中的Intent对象不用全部匹配这几个标签,每个类型匹配一个即可启动相应的Activity,若代码中的Intent对象中有设置action、category、data这几个属性,则在< intent-filter >标签中必须全部匹配这些属性所对应的标签,Activity才能被启动。
三、活动的启动模式
启动模式一共有4种,分别是standard(叠加)、singleTop(栈顶复用)、singleTask(栈内复用)和singleInstance(在应用程序和系统中的唯一性),可以在AndroidManifest.xml中通过给< activity >标签指定android:launchMode属性来选择启动模式。
-
1、standard(默认模式)
standard是Activity默认的启动模式,特点是,每启动一个Activity就会在栈顶创建一个新的实例,在不进行显示指定的情况下,所有活动都会自动适应这种启动模式。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建一个新的实例。
示例如下:
运行程序,在MainActivity中连续点击3次按钮,logcat打印信息如下图所示:
每点击一次按钮,就会新创建出一个新的MainActivity实例。此时返回栈中存在4个MainActivity实例(程序启动时也算一次),因此需要连续点击4次Back键才能退出程序。
应用场景:闹钟程序。 -
2、singleTop(栈顶复用模式)
当活动的启动模式指定位singleTop,在启动活动时,首先会判断要启动的Activity是否位于栈顶,如果位于栈顶则直接复用,否则创建新的实例。
示例如下:
修改启动模式,运行程序,可以看到已经创建了一个MainActivity实例,之后不管多少次点击按钮都不会再有新的打印信息。因为当前MainActivity已经处于返回栈的栈顶,每次想要再启动一个新的MainActivity时都会直接使用栈顶的活动,因此MainActivity只有一个实例,仅按一次Back键就可以退出程序。
当MainActivity并未处于栈顶位置时,这时再启动MainActivity,还是会创建新的实例。
在FirstActivity中再次启动MainActivity时,栈顶活动已经变成了FirstActivity,因此会创建一个新的MainActivity实例。
应用场景:浏览器的书签、通讯消息聊天界面。 -
3、singleTask(栈内复用模式)
当活动的启动模式指定为singleTask,每次启动该活动的时候系统会首先在返回栈中检查是否有存在该活动的实例,如果发现以及存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
示例如下:
在MainActivity点击按钮进入FirstActivity,然后在FirstActivity点击按钮,又重新回到MainActivity。
现在返回栈中只剩下一个FirstActivity的实例,点击一次Back键就可以退出程序。
应用场景:浏览器主界面。 -
4、singleInstance(单实例模式)
指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。在这种模式下,会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共同的用一个返回栈,也就解决了共享活动实例的问题。
Activity采用singleInstance模式启动分两种情况:一种是要启动的Activity不存在,则系统会先创建一个新的任务栈,然后再创建Activity实例。一种是要启动的Activity已存在,无论当前Activity位于哪个程序哪个任务栈,系统都会把Activity所在的任务栈转移到前台,从而使Activity显示。
在该模式下,只有一个实例,并且这个实例独立运行在一个任务栈中,该任务栈不允许有别的Activity存在。
示例如下:
指定FirstActivity的启动模式为singleInstance,修改程序输出每一个活动的Task id。
由上图可以看到,FirstActivity的Task id不同于MainActivity和SecondActivity,这说明FirstActivity确实是放在一个单独的返回栈里面的,而且这个栈中只有FirstActivity这一个活动。
(1)以singleInstance模式启动的Activity具有全局唯一性,即整个系统中只存在一个这样的实例。
(2)以singleInstance模式启动的Activity在整个系统中是单例,如果在启动这样的Activity时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
(3)以singleInstance模式启动的Activity具有独占性,即它会独自占用一个任务,被它开启的任何Activity都会运行在其他任务中。
(4)被singleInstance模式的Activity开启的其他Activity,能够在新的任务中启动,但不一定开启新的任务,也可能在已有的一个任务中开启。
意义:为了在不同程序中共享一个Activity实例。
应用场景:来电界面、浏览器(将浏览器设置为单例模式)。
补充:如何给Activity指定启动模式?
(1)通过AndroidManifest.xml文件为Activity指定启动模式。
(2)通过在Intent中设置标志位来为Activity指定启动模式。
二者的区别:①首先,优先级上,第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;②其次,在限定范围上,第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种方式无法为Activity指定singleInstance模式。
四、Activity之间如何进行数据回传?
Activity提供了一个startActivityForResult(Intent intent, int requestCode) 方法,该方法也用于启动Activity,并且这个方法可以在当前Activity销毁时返回一个结果给上一个Activity,实现数据回传功能。在实际开发中这种功能很常见,如发朋友圈时,进入图库选择好照片后,会返回到发表状态页面并带回所选的图片信息。
在一个Activity中很可能调用startActivityForResult()方法启动多个不同的Activity,每一个Activity返回的数据都会回调onActivityResult()这个方法,因此,首先要做的就是检查requestCode的值来判断数据来源,确定数据是从哪一个Activity返回的,然后通过resultCode的值来判断数据处理结果是否成功,最后从data中取出数据,这样就完成了Activity数据返回的功能。
五、Activity之间传递数据的类型?
在Activity之间传输的数据类型有基本类型、数组、Bundle、Serializable对象、Parcelable对象。
六、Activity被系统回收
1、如何应对后台的Activity被系统回收?
Activity被系统回收有3种情况,具体如下:
(1)每个手机的内存是有限制的,当Android系统发现内存不足时,它会将后台运行的一些程序杀死,回收这部分内存。
(2)如果没有对横竖屏切换的情况进行任何处理,那么在Activity进行横竖屏切换时,Activity会先被完全销毁回收,然后再被重新创建,导致页面数据丢失。
(3)当App长期在后台运行时,有时出于省电等节省资源的目的,系统也会将APP回收掉。
Activity中提供了一个onSavedInstanceState(Bundle obj)方法,当系统销毁Activity时,会将Activity的状态信息以键值对的形式存放在bundle对象中。开发者可以重写Activity的onSavedInstanceState()方法,将要保存的页面数据全部存到bundle对象中。加入Activity被回收了,那么下次再进入这个Activity时就一定会调用onCreate()方法,开发者可以在onCreate()方法中通过bundle对象中保存的用户数据来做一些恢复数据的工作,防止Activity被系统回收时造成用户数据丢失。
2、Activity被系统回收时会出现哪些问题?
当Activity被系统回收后,相应的用户数据自然也会被回收掉。同时,页面上的某些功能可能会依赖于某些页面数据,如果数据被回收了,那么当进入页面时,可能会造成一些异常,导致应用程序崩溃。
七、Activity状态保存与恢复
Activity被主动回收时,如按下Back键,系统不会保存它的状态,只有被动回收时,虽然这个Activity实例已被销毁,但系统在新建一个Activity实例时,会带上先前被回收Activity的信息。在当前Activity被销毁前调用onSaveInstanceState(onPause和onStop之间保存),重新创建Activity后会在onCreate后调用onRestoreInstanceState(onStart和onResume之间被调用),它们的参数Bundle用来数据保存和读取的。
保存View状态有两个前提:①View的子类必须实现了onSaveInstanceState;②必须要设定ID,这个ID作为Bundle的Key。
八、已调用多个Activity后如何安全退出?
对于单一的Activity的应用来说,退出很简单,直接调用finish()方法即可。但是,对于多个Activity的应用程序来说,当打开多个Activity后,如果想在最后打开的Activity中直接退出应用程序,这就需要将每个Activity都关闭掉,然后退出。下面列举四种安全退出已调用多个Activity的方法:
- 1、抛异常强制退出
该方法通过抛异常,使程序Force Close(强制关闭),但是,这种方式会让Android系统弹出Force Close的窗口,用户体验很差。 - 2、记录打开的Activity并逐个关闭
这种方式进行的操作需要抽取到Activity的父类中来实现,在父类的onCreate()方法中调用Activity集合的add()方法把每一个打开的Activity添加进该集合中。当退出App时,需要在父类中创建一个killAll()方法,在该方法中复制一份Activity的集合,然后遍历复制后的集合来关闭所有Activity。 - 3、发送特定的广播实现安全退出
在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可。在这个过程中,注册广播接收者的逻辑可以抽取到父类中实现,需要安全退出应用时,仅需发送action为注册时指定的action即可,所有开启的Activity都注册有监听此广播的广播接收者,广播接收者收到此类广播时,将直接调用finish()方法,关闭当前Activity。 - 4、递归退出每个Activity
当需要打开新的Activity时,使用startActivityForResult()方法打开新Activity,需要安全退出应用时,自定义一个标志退出的Flag,在各Activity的onActivityResult()方法中处理这个Flag,来实现递归关闭。
九、注意事项
- 1、Activity间的数据通信,对于数据量比较大的情况,避免使用Intent+Parcelable的方式,可以考虑EventBus等替代方案,以免造成TransactionTooLargeException。
- 2、Activity间通过隐式Intent的跳转,在发出Intent之前必须通过resolveActivity检查,避免找不到合适的调用组件,造成ActivityNotFoundException的异常。
- 3、(待补充)