一、生命周期全面分析
Android活动默认运行在当前进程所拥有的栈中,前台可见的活动则在活动栈的最顶部。其他后台活动则在栈的里面,在正常的情况下(内存充足)其他的活动并没有被回收或者杀死,它们仍然存在于栈中保持着原来的状态。当前面的活动退出后,后面的活动就会搬到前台使得被用户可见。如果在非正常情况下(内存紧张、按下Home键后右启动其他应用)那么栈内的非前台Activity就可能被回收,但是当我们返回到该Activity时它又会被重新构造,并且会通过onSaveInstance和onRestoreInstance加载原来的数据使得它保持之前的状态呈现给用户。无论是正常情况,还是非正常情况下,这样的实现都是基于一个非常重要的机制——生命周期。
1.什么是生命周期
周期即活动从开始到结束所经历的各种状态。生命周期即活动从开始到结束所经历的各个状态。从一个状态到另一个状态的转变,从无到有再到无,这样一个过程中所经历的状态就叫做生命周期。
Acitivity本质上有四种状态:
- 运行:如果一个活动被移到了前台(活动栈顶部)。
- 暂停:如果一个活动被另一个非全屏的活动所覆盖(比如一个Dialog),那么该活动就失去了焦点,它将会暂停(但它仍然保留所有的状态和成员信息,并且仍然是依附在WindowsManager上),在系统内存积极缺乏的时候会将它杀死。
- 停止:如果一个活动被另一个全屏活动完全覆盖,那么该活动处于停止状态(状态和成员信息会保留,但是Activity已经不再依附于WindowManager了)。同时,在系统缺乏资源的时候会将它杀死(它会比暂停状态的活动先杀死)。
- 重启:如果一个活动在处于停止或者暂停的状态下,系统内存缺乏时会将其结束(finish)或者杀死(kill)。这种非正常情况下,系统在杀死或者结束之前会调用onSaveInstance()方法来保存信息,同时,当Activity被移动到前台时,重新启动该Activity并调用onRestoreInstance()方法加载保留的信息,以保持原有的状态。
在上面的四中常有的状态之间,还有着其他的生命周期来作为不同状态之间的过度,用于在不同的状态之间进行转换,生命周期的具体说明见下。
二、各个生命周期状态的说明
1.正常情况下的生命周期
onCreate:与onDestroy配对,表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中可以做一些初始化的工作(加载布局资源、初始化Activity所需要的数据等),耗时的工作在异步线程上完成。
onRestart:表示Activity正在重新启动。一般情况下,在当前Activity从不可见重新变为可见的状态时onRestart就会被调用。这种情形一般是由于用户的行为所导致的,比如用户按下Home键切换到桌面或者打开了一个新的Activity(这时当前Activity会暂停,也就是onPause和onStop被执行),接着用户有回到了这个Activity,就会出现这种情况。
onStart:与onStop配对,表示Activity正在被启动,并且即将开始。但是这个时候要注意它与onResume的区别。两者都表示Activity可见,但是onStart时Activity还正在加载其他内容,正在向我们展示,用户还无法看到,即无法交互。
onResume:与onPause配对,表示Activity已经创建完成,并且可以开始活动了,这个时候用户已经可以看到界面了,并且即将与用户交互(完成该周期之后便可以响应用户的交互事件了)。
onPause:与onResume配对,表示Activity正在暂停,正常情况下,onStop接着就会被调用。在特殊情况下,如果这个时候用户快速地再回到当前的Activity,那么onResume会被调用(极端情况)。一般来说,在这个生命周期状态下,可以做一些存储数据、停止动画的工作,但是不能太耗时,如果是由于启动新的Activity而唤醒的该状态,那会影响到新Activity的显示,原因是onPause必须执行完,新的Activity的onResume才会执行。
onStop:与onStart配对,表示Activity即将停止,可以做一些稍微重量级的回收工作,同样也不能太耗时(可以比onPause稍微好一点)。
onDestroy:与onCreate配对,表示Activity即将被销毁,这是Activity生命周期的最后一个回调,我们可以做一些回收工作和最终的资源释放(如Service、BroadReceiver、Map等)。
正常情况下,Activity的常用生命周期就是上面的7个,下图更加详细的描述的各种生命周期的切换过程:
这里要说的是,从上图我们可以看到一个现象:
onStart与onStop、onResume与onPause是配对的。两种Activity回到前台的方式,从onPause状态回到前台会走到onResume状态,从onStop状态回到前台会到onStart状态,这是从是否可见和是否在前台来说的。从是否可见来说,onStart和onStop是配对的;从是否在前台来说,onResume和onPause是配对的。至于为什么会有他们,在第三点生命周期的使用会说到。
我们来看看正常情况下生命周期的系统日志:
03-23 00:15:52.970 32278-32278/com.example.david.lifecircle E/TAG: onCreate() is invoked!
03-23 00:15:52.971 32278-32278/com.example.david.lifecircle E/TAG: onStart() is invoked!
03-23 00:15:52.971 32278-32278/com.example.david.lifecircle E/TAG: onResume() is invoked!
03-23 00:15:55.858 32278-32278/com.example.david.lifecircle E/TAG: onPause() is invoked!
03-23 00:16:02.573 32278-32278/com.example.david.lifecircle E/TAG: onRestart() is invoked!
03-23 00:16:02.573 32278-32278/com.example.david.lifecircle E/TAG: onStart() is invoked!
03-23 00:16:02.573 32278-32278/com.example.david.lifecircle E/TAG: onResume() is invoked!
2.异常情况下的生命周期
一般正常情况的声明周期就像上面所说的一样,但是因为Android本身内存或者其他的一些情况会使得Activity不按照正常的生命周期。比如当资源配置发生改变、系统内存不足时,Activity就会被杀死。下面分析这两种常见的情况。
情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
理解这个问题,我们首先要对系统的资源加载机制有一定了解,不过这里我不分析系统资源加载机制了(因为我也不怎么懂)。简单说明一下,就像是我们把一张图片放在drawable目录之后,就可以通过Resources去获取这张图片。同时为了兼容不同的设备,我们还可能需要在其他的一些目录放置不同的图片,比如 drawable-mdpi、drawable-hdpi等。这样,当应用程序启动时,系统就会根据当前设备的情况去加载合适的Resource资源,比如说横屏和竖屏的手机会拿到两张不同的图片(设定了landscape或portrait状态下的图片)。
如果说,当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并重新创建(当然我们也可以组织系统重新创建,具体就在Mainfest中申明android:Configchanges=属性即可)。异常情况下的调用流程:
- 调用onSaveInstance保存当前Activity状态。注意,它与onPause方法没有先后之分。
- 调用onStop方法做后续处理。
- 调用onDestroy方法销毁当前活动。
- 重新onCreate该活动。
- 调用onStart方法之后,再调用onRestoreInstance方法加载保存的数据。
- 接下来就与正常的一样了,调用onResume,然后运行。
我们来看一下生命周期异常运行的系统日志:
03-23 00:19:23.480 26457-26457/com.example.david.lifecircle E/TAG: onCreate() is invoked!
03-23 00:19:23.481 26457-26457/com.example.david.lifecircle E/TAG: onStart() is invoked!
03-23 00:19:23.481 26457-26457/com.example.david.lifecircle E/TAG: onResume() is invoked!
03-23 00:19:51.323 26457-26457/com.example.david.lifecircle E/TAG: onPause() is invoked!
03-23 00:19:51.324 26457-26457/com.example.david.lifecircle E/TAG: onSaveInstanceState() is invoked! Save Text = Save Data
03-23 00:19:51.478 26457-26457/com.example.david.lifecircle E/TAG: onCreate() is invoked!
03-23 00:19:51.488 26457-26457/com.example.david.lifecircle E/TAG: onStart() is invoked!
03-23 00:19:51.490 26457-26457/com.example.david.lifecircle E/TAG: onRestoreInstanceState() is invoked! Recover Text = Save Data
03-23 00:19:51.490 26457-26457/com.example.david.lifecircle E/TAG: onResume() is invoked!
情况2:资源内存不足导致低优先级的Activity被杀死
这种情况不好模拟,但是其数据存储和恢复过程和情况1完全一致,这里简单的描述一下Activity的优先级情况。Activity的优先级从高到低可以大致分为一下三种:
(1)前台Activity——正在和用户交互的Activity,优先级最高。
(2)可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但无法和用户直接交互。
(3)后台Activity——已经被暂停或者停止的Activity,优先级最底。当系统内存不足的时候,系统就会按照上述优先级从低到高来杀死目标Activity。并在后续通过onSaveInstance和onRestoreInstance来存储和恢复数据。
特别提醒的是:如果一个进程中没有四大组件(Activity、Service、ContentProvider、BroadCastReceiver)。那么这个进程就会很快被杀死,因此一些后台工作不适合脱离四大组件而独立运行在后台中,否则很容易被杀死。一般是将后台工作放入Service中从而保证进程有一定的优先级,这样才不会被系统轻易杀死。
三、生命周期的使用
1.常见的生命周期有关问题:
- onStart和onResume、onPause和onStop从描述上看来差不多,但是他们为什么会分开呢?有什么不同?
- 两个Activity A和B,从A中启动B,那么B的onResume与A的onPause哪个会先执行呢?
- onSaveInstance与onRestoreInstance是任何情况下都可以使用的嘛?所有的保存数据和恢复的操作都可以在这对方法中执行?
- 如上面所说,如何使得在系统配置放生改变后,Activity不被重新创建呢?
2.解析
1、 onStart和onResume、onPause和onStop这两对看起来是差不多,而且很多时候都会同时调用onPause、onStop,然后回到onStart、onResume。但是在一些比较特殊的情况下就不一样了。我们举两种情况,
第一种:前台弹出了一个Dialog,那么这个Dialog的作用只是提醒用户或者让用户输入一个信息等就完毕了,这是一个比较轻量级的任务;
第二种:重新启动另一个Activity界面,转到另一个模块。这时新启动的Activity就不是一个临时或者轻量级的任务了。
这两种情况,第一种一般很快就会返回当前Activity,不会太耗时;第二种可能会很久,在这段时间内系统可能需要启动其他的应用,那么就会产生内存紧张的问题。所以,我认为是要区分这两种情况,才会加入这两对方法。在第一种情况下,可以在onPause中做一些较轻微的数据存储,因为一般很快就会回到当前Activity;第二种情况下,适合在onStop中做一些较重量级的存储。除此之外,我想不到其他的使用了。
2、这个问题可以从源码中得到解答。不过源码太复杂,涉及底层太多(AMS、Binder、ActivityStack、ActivityThread等)。不过可以直接调用生命周期,输出系统日志来得到解答。从下面的日志我们可以看出,的确是要等到A活动的onPause方法之后B才能执行(这里onCreate没有输出日志):
03-23 01:02:31.339 32382-32382/com.example.david.lifecircle E/MainActivity: onCreate() is invoked!
03-23 01:02:31.341 32382-32382/com.example.david.lifecircle E/MainActivity: onStart() is invoked!
03-23 01:02:31.341 32382-32382/com.example.david.lifecircle E/MainActivity: onResume() is invoked!
03-23 01:04:04.005 32382-32382/com.example.david.lifecircle E/MainActivity: onPause() is invoked!
03-23 01:04:04.047 32382-32382/com.example.david.lifecircle E/SecondActivity: onStart() is invoked!
03-23 01:04:04.047 32382-32382/com.example.david.lifecircle E/SecondActivity: onResume() is invoked!
3、 onSaveInstance和onRestoreInstance是只有Activity异常销毁的时候才会调用的,所以这里一般执行的是Activity异常销毁时需要保存和恢复的数据;onSaveInstance和onRestoreInstance方法还可以判断Activity是否被重建,但正常情况下是不会调用的。所以正常情况下,还是应该在onPause和onStop方法中保存数据。
4、上面提到,我们可以在AndroidMainfest.xml里,对< activity />增加一个android:configChanges属性,来指定在哪些配置改变的情况下Activity不需要重建。如下所示:
android:configChanges="orientation|screenSize"//界面方向以及大小的改变不需要重建
我们在AndroidMainfest.xml做如下申明:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.david.lifecircle">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity"></activity>
</application>
</manifest>
MainActivity中的部分代码:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e("MainActivity","onConfigurationChanged() is invoked!"+newConfig.orientation);
}
@Override
protected void onPause() {
super.onPause();
Log.e("MainActivity","onPause() is invoked!");
}
@Override
protected void onResume() {
super.onResume();
Log.e("MainActivity","onResume() is invoked!");
}
@Override
protected void onStart() {
super.onStart();
Log.e("MainActivity","onStart() is invoked!");
}
@Override
protected void onRestart() {
super.onRestart();
Log.e("MainActivity","onRestart() is invoked!");
}
点击屏幕旋转,然后来看一下系统日志输出:
03-23 01:14:11.357 10361-10361/com.example.david.lifecircle E/MainActivity: onCreate() is invoked!
03-23 01:14:11.359 10361-10361/com.example.david.lifecircle E/MainActivity: onStart() is invoked!
03-23 01:14:11.359 10361-10361/com.example.david.lifecircle E/MainActivity: onResume() is invoked!
03-23 01:14:28.140 10361-10361/com.example.david.lifecircle E/MainActivity: onConfigurationChanged() is invoked!2
03-23 01:14:38.294 10361-10361/com.example.david.lifecircle E/MainActivity: onConfigurationChanged() is invoked!1
03-23 01:14:47.531 10361-10361/com.example.david.lifecircle E/MainActivity: onConfigurationChanged() is invoked!2
我们发现,屏幕旋转之后,并没有重新调用生命周期,说明活动并没有被重建。configChanges属性还有许多的值,如:mccmnclocal ouchscreenkeyboard等等。
最后用一个实际的例子来说明Activity的各个生命周期。假设有一个程序由2个Activity A和B组成,A是这个程序的启动界面。当用户启动程序时,Process和默认的Task分别被创建,接着A被压入到当前的Task中,依次执行了 onCreate, onStart, onResume事件被呈现给了用户;此时用户选择A中的某个功能开启界面B,界面B被压入当前Task遮盖住了A,A的onPause事件执行,B的 onCreate, onStart, onResume事件执行,呈现了界面B给用户;用户在界面B操作完成后,使用Back键回到界面A,界面B不再可见,界面B的onPause, onStop, onDestroy执行,A的onResume事件被执行,呈现界面A给用户。此时突然来电,界面A的onPause事件被执行,电话接听界面被呈现给用 户,用户接听完电话后,又按了Home键回到桌面,打开另一个程序“联系人”,添加了联系人信息又做了一些其他的操作,此时界面A不再可见,其 onStop事件被执行,但并没有被销毁。此后用户重新从菜单中点击了我们的程序,由于A和其所在的进程和Task并没有被销毁,A的onRestart 和onStart事件被执行,接着A的onResume事件被执行,A又被呈现给了用户。用户这次使用完后,按Back键返回到桌面,A的 onPause, onStop被执行,随后A的onDestroy被执行,由于当前Task中已经没有任何Activity,A所在的Process的重要程度被降到很 低,很快A所在的Process被系统结束
常见的例子
情形一、一个单独的Activity的正常的生命过程是这样的:onCreate->onStart->onPause->onStop->onDestroy。例如:运行一个Activity,进行了一些简单操作(不涉及页面的跳转等),然后按返回键结束。
情形二、有两个Activity(a和b),一开始显示a,然后由a启动b,然后在由b回到a,这时候a的生命过程应该是怎么样的呢(a被b完全遮盖)?
a经历的过程为onCreate->onStart->onResume->onPause->onStop->onRestart->onStart->onResume。这个过程说明了图中,如果Activity完全被其他界面遮挡时,进入后台,并没有完全销毁,而是停留在onStop状态,当再次进入a时,onRestart->onStart->onResume,又重新恢复。
情形三、基本情形同二一样,不过此时a被b部分遮盖(比如给b添加个对话框主题 android:theme=”@android:style/Theme.Dialog”)
a经历的过程是:onCreate->onStart->onResume->onPause->onResume
所以当Activity被部分遮挡时,Activity进入onPause,并没有进入onStop,从Activity2返回后,执行了onResume
情形四、 打开程序,启动a,点击a,启动AlertDialog,按返回键从AlertDialog返回。
a经历的过程是:onCreate->onStart->onResume
当启动和退出Dialog时,Activity的状态始终未变,,因为Dialog实际上时一个View,它是属于某一个Activity的,因此如果Dialog显示在当前Activity之前时不会影响到Activity的生命周期的。但是如果是其他Activity的Dialog弹出那么就会触发onPause()方法的执行。