一、概述
本篇主要简单介绍一下activity的生命周期和启动模式。同时,我们会用代码实践在不同的启动模式下,生命周期的具体执行方式。
二、Activity的生命周期
首先,让我们看一下关于Activity生命周期的一张经典图片:
在讲Activity生命周期之前,我们先需要理解三种活动的状态。
前台活动:当前activity正在前台与用户交互。
可见活动:当前activity对用户是可见的,但是很可能不在与用户进行交互。
后台活动:activity对用户不可见,数据在后台被保存起来。
从图上可以看出,生命周期中,onCreate,onStart,onResume,onPause,onStop,onDestroy这六个方法最为重要,而这六个方法是两两对应的。
onCreate():activity被创建时调用,应该在这个方法中完成布局加载,时间绑定等初始化操作。
onStart():activity可见时调用。
onResume():activity在前台时调用。
onPause():activity不是在前台是调用。
onStop():activity已经不可见时调用。
onDestroy():activity正在停止或即将被销毁时调用。
下面,我们通过代码实践中打印的日志来了解android生命周期。
1、activity A 中启动 activity B
2567-2567/example.com.activitydemo I/Lifecycle: A onPause
2567-2567/example.com.activitydemo I/Lifecycle: B onCreate
2567-2567/example.com.activitydemo I/Lifecycle: B onStart
2567-2567/example.com.activitydemo I/Lifecycle: B onResume
2567-2567/example.com.activitydemo I/Lifecycle: A onStop
从日志中我们可以看出,A调用了onPause 和onStop进入了后台状态,而B被创建并且进入了前台状态。
那么这里A和B的生命周期有没有固定的顺序呢?在Android官方文档中,有写出,只有在老的activity执行onPause完成后,才能执行新activity的onResume。然而从sdk23的源码中,我们可以看到其实是在老的activity执行onPause后,新的activity才被启动,所以,若是activity的启动机制没有大改,老activity的onPause将会最先执行。而老activity的onStop则没有固定的顺序。
2、activity B 返回 activityA
2567-2567/example.com.activitydemo I/Lifecycle: B onPause
2567-2567/example.com.activitydemo I/Lifecycle: A onStart
2567-2567/example.com.activitydemo I/Lifecycle: A onResume
2567-2567/example.com.activitydemo I/Lifecycle: B onStop
2567-2567/example.com.activitydemo I/Lifecycle: B onDestroy
从日志中我们可以看出,B被销毁了,而A不需要重新创建,然后进入了前台状态。
顺序和之前同理,老activity的onPause总被最先执行。
3、activity A 按home键
2567-2567/example.com.activitydemo I/Lifecycle: A onPause
2567-2567/example.com.activitydemo I/Lifecycle: A onStop
正常情况下,按home键之后,activity会进入后台状态。那么有没有异常情况呢?那肯定是有的。当系统内存不足时,会回收优先级较低的activity,优先级基本按照 前台activity>可见activity>后台activity。因此,进入后台的activity很可能被回收。activity被回收时,会调用 onSaveInstanceState() 用于保存现场,开发者可以重写这个函数,用于activity被回收后保存需要留存的变量,保存现场后,会调用 onDestroy()销毁activity。 当activity再次被启动时,会先调用onCreate,然后调用onRestoreInstanceState()函数来恢复现场。
三、activity 启动模式
activity有四种启动模式:standard, singleTop,singleTask,singleInstance。
1、standard
标准启动模式,系统默认的activity启动模式,上个章节介绍的生命周期,均是以standard模式启动。
2、singleTop
栈顶复用模式。使用这种模式启动新的activity,如果将要启动的activity在栈顶的话,将不会被重新创建实例。如果要启动的不在栈顶的,那么会新建一个实例,其过程和standard模式相同。
我们来看下已经在栈顶的E,以singleTop模式再次启动E时,会发生什么。
2567-2567/example.com.activitydemo I/Lifecycle: E onPause
2567-2567/example.com.activitydemo I/Lifecycle: E onNewIntent
2567-2567/example.com.activitydemo I/Lifecycle: E onResume
E调用了onPause退出前台,然后调用onNewIntent,再调用onResume回到前台。
3、singleTask
栈内复用模式。类似于单例模式,在这种启动模式下,activity只要在一个栈中存在,那么就不会重新创建实例,并且在重新启动时,会将activity调到栈顶,注意,这里的调到栈顶指的是将被重新启动的activity上面的所有activity全部出栈。
我们先来看下,在栈内为 AFBC其中F为栈内复用模式启动,当我们再次启动F时,生命周期会发生什么
3071-3071/example.com.activitydemo I/Lifecycle: B onDestroy
3071-3071/example.com.activitydemo I/Lifecycle: C onPause
3071-3071/example.com.activitydemo I/Lifecycle: F onNewIntent
3071-3071/example.com.activitydemo I/Lifecycle: F onStart
3071-3071/example.com.activitydemo I/Lifecycle: F onResume
3071-3071/example.com.activitydemo I/Lifecycle: C onStop
3071-3071/example.com.activitydemo I/Lifecycle: C onDestroy
这里可以看到,和我们上面说的情况相同,BC被销毁出栈,使得F恢复到栈顶,然后F并不会重新创建,而是调用了onNewIntant函数。
当F在已经在栈顶,并且在前台时,再次调用F,与singleTop的生命周期调用是相同的。
这里还要再提到一种情况,那么当有多个activity栈的时候,重新启动一个singleTask模式的activity会出现什么情况。
现在我们模拟一种情况: 后台栈为 AFB,前台栈为DC, 其中F为栈内复用模式启动,C为当前前台活动,如下图
此时我们以singleTask模式启动F会发生什么情况呢
24481-24481/example.com.activitydemo I/Lifecycle: C onPause
24481-24481/example.com.activitydemo I/Lifecycle: B onDestroy
24481-24481/example.com.activitydemo I/Lifecycle: F onNewIntent
24481-24481/example.com.activitydemo I/Lifecycle: F onStart
24481-24481/example.com.activitydemo I/Lifecycle: F onResume
24481-24481/example.com.activitydemo I/Lifecycle: C onStop
从日志可以看出, C并没有被销毁,而是转到了后台,B被销毁退出了栈。F没有被重新创建,而是被调用了onNewIntent函数,并且转到了前台。此时的活动栈情况如下图:
想看详细栈的情况的话,可用通过命令 adb shell dumpsys activity 来查看,这里就不贴出来了。
4、singleInstance
单实例启动模式。这个和singleTask有些类似,简单来说,是singleTask的加强版,即以singleInstance启动模式启动的activiy,在所有栈中只能存在一个实例,且该实例单独占有一个独立的栈。在该实例的活动栈中启动其他活动,也会转到其他栈中创建。
我们构造一个G为单实例模式启动,A、B为标准模式启动,示意过程如下图。
5、使用方式
在代码中,如需使用这四种启动模式, 需要在AndroidManifest.xml中指定,如下:
<activity android:name=".D"
android:taskAffinity="com.example.activitydemo1"/>
<activity android:name=".E"
android:launchMode="singleTop"/>
<activity android:name=".F"
android:launchMode="singleTask"/>
<activity android:name=".G"
android:launchMode="singleInstance"/>
launchMode是指定activity的启动模式,taskAffinity是指定activity的启动栈。
四、Activity 启动模式的 Flags
Activity的启动模式,除了上面说的在xml文件中指定外,还能可以在启动时指定intent中的flags来启动。
1、FLAG_ACTIVITY_NEW_TASK
这个关键字需要配合 taskAffinity属性去使用。当你为activity指定一个任务栈时,需要通过该Flag来启动activity,否则直接启动的话,会使得activity无法在新的任务栈中启动。这里还有一个需要注意的地方,在taskAffinity所指定的任务栈中,已经存在将要启动的activity实例,那么使用FLAG_ACTIVITY_NEW_TASK启动的话,将会使得startActivity失效,生命周期不会被调用。
在开发中时,我们都是通过Activity的上下文来启动另一个Activity。那么如果需要使用非Activity的上下文来启动一个activity行不行呢?当然可以,但是不是直接启动,直接启动的话会报以下异常:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.
这个异常说明的很明显,需要用FLAG_ACTIVITY_NEW_TASK这个flag来启动activity。是的,这个就是FLAG_ACTIVITY_NEW_TASK的另一个作用,当你使用非activity的上下文启动activity时,会因为没有对应的任务栈,而导致启动失败,FLAG_ACTIVITY_NEW_TASK关键字便是创建一个新的任务栈,来存放启动的activity。那么会有人问,那应用启动的第一个activity不是也没有对应的任务栈吗?是的,launch的activity的启动模式实际上是singleTask,这个后面的博客,会对这里进行分析。
2、FLAG_ACTIVITY_SINGLE_TOP
这个关键字非常简单,和singleTop启动模式一模一样,与在xml中指定launchMode为singleTop没有区别,这里就不详述了。
3、FLAG_ACTIVITY_CLEAR_TOP
使用该flag启动activity时,会查看在启动activity的任务栈中是否有相同的activity实例,有的话,则将该activity实例以及其之上的所有activity全部出栈,在创建一个新的activity实例压入栈中。示意图如下
如果有多个B的实例,那么此时以FLAG_ACTIVITY_CLEAR_TOP启动B,会出现什么情况呢?
在有多个实例的情况下,只会出栈到最靠近栈顶的实例,如下图:
那么有多个任务栈时,FLAG_ACTIVITY_CLEAR_TOP会不会像singleTask一样,将后台任务栈切到前台呢?
其实并不会,该flag只关注与activity将要进入的栈,如下图:
五、最后说几句
其实 activity的启动,包括其生命周期,这里只是一个概述,在具体的开发过程中,会遇到很多厂商修改系统,导致在一些操作中生命周期的运行和预想的并不相同。
在写这篇文章之前,看了许多书籍和其他博客,发现大家写的不尽相同。此篇博客的结论,是使用小米5、小米6、MX6等主流手机,在 Android 6.0 7.0 7.1 等主流版本上测试得到的结果,然后结合 SDK23的源码得出的结论。若在其他手机上有出入。可留言说明。
为了这篇博客,写了一个小的activity启动程序,用于测试结果。