先上一张经典图片镇楼:
测试正常情况Activity生命周期的代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("tag", "生命周期:onCreate");
}
@Override
protected void onStart() {
super.onStart();
Log.e("tag", "生命周期:onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.e("tag", "生命周期:onResume");
}
@Override
protected void onRestart() {
super.onRestart();
Log.e("tag", "生命周期:onRestart");
}
@Override
protected void onPause() {
super.onPause();
Log.e("tag", "生命周期:onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.e("tag", "生命周期:onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("tag", "生命周期:onDestroy");
}
}
测试结果:
第一次启动Activity,生命周期回调如下:
这时候将应用切换到后台或打开一个新的Activity,生命周期回调如下:
然后再次打开这个应用,生命周期回调如下:
最后按返回键退出这个应用,生命周期回调如下:
假如应用已经被切换到后台,这时直接结束所有进程,生命周期回调如下:
从整个生命周期来说,onCreate和onDestroy是配对的,分别标识着Activity的创建和销毁,并且只可能有一次被调用。从Activity是否可见来说,onStart和onStop是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;从Activity是否在前台来说,onResume和onPause是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。
假设当前Activity为A,如果这时用户打开一个新ActivityB,那么B的onResume和A的onPause哪个先执行???
测试代码:
private void initView() {
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
}
测试结果:
可以看到,新启动一个Activity的时候,旧Activity的onPause会先执行,然后才会启动新的Activity。通过分析这个问题,我们知道不能在onPause中做重量级的操作,因为必须onPause执行完成以后新Activity才能Resume。
2异常情况下Activity的生命周期分析
2.1资源相关的系统配置发生改变导致Activity的生命周期的改变
最常见的一种情况就是屏幕旋转导致的Activity的生命周期的改变,网上关于Android横竖屏切换生命周期的资料一大把,照着这些资料高高兴兴的敲着代码,尼玛却发现坑爹呀!!!
代码与结果才是最有说服力,开始举栗子:
Activity的配置文件不设置任何参数,从竖屏切换到横屏:
调用了一次完整的生命周期
Activity的配置文件不设置任何参数,从横屏切换到竖屏:
调用了一次完整的生命周期,并没有调用两次!!!
Activity的配置文件设置:android:configChanges=”orientation”,从竖屏切换到横屏或者从横屏切换到竖屏:
调用了一次完整的生命周期,也就是说,设置这个参数没有任何作用!!!
Activity的配置文件设置:android:configChanges=”orientation|keyboardHidden,从竖屏切换到横屏或者从横屏切换到竖屏:
调用了一次完整的生命周期,也就是说,额外设置这个参数没有任何作用,这不坑爹嘛!说好的不调用生命周期呢???
后来才知道,当android:targetSdkVersion<=12,不会调用完整生命周期;当android:targetSdkVersion>12,会调用完整生命周期。我的 targetSdkVersion是23,嗦嘎寺内。
那么我该怎样设置才能在targetSdkVersion大于12的时候,横竖屏切换不调用生命周期呢:
android:configChanges="orientation|keyboardHidden|screenSize"
这样就够了,看看打印结果,没有打印与生命周期相关的log,只是调用了系统的onConfigurationChanged方法,这时候我们可以自己做一些处理,完美。
当然,你要是这样设置
android:screenOrientation="portrait"
这就另当别论,只允许竖屏,管你怎么切换都没用,就不谈生命周期的调用了。
OK,我们已经知道这种系统配置发生变化的情况会调用Activity的完整生命周期。这种Activity被异常中止的情况下,系统会调用onSaveInstanceState来保存当前Activity的状态,正常情况下系统不会回调这个方法。 继续举栗子:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("tag", "A生命周期:onCreate");
initView();
if (savedInstanceState != null) {
Log.e("tag", "savedInstanceState保存的值为:" + savedInstanceState.get("data"));
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.e("tag", "onSaveInstanceState");
outState.putString("data", "我是需要保存的值");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e("tag", "savedInstanceState保存的值为:" + savedInstanceState.get("data"));
}
还是这段代码,当我第一次正常启动时,没有什么不同:
当我切换到横屏时,高潮来了:
果然和预期的一样,这种异常情况调用了onSaveInstanceState方法,比如我们需要在这种异常情况下保存一些必要的参数,像播放视频时横竖屏切换,我一定得保存当前的播放进度等信息。那么我们可以把这些参数以键值对的形式保存在onSaveInstanceState方法中。怎样恢复这些数据呢,恢复数据的位置有两种:onRestoreInstanceState和onCreate,区别如下:
onRestoreInstanceState一旦被调用,其参数Bundle savedInstanceState一定是有值的,我们不用额外判断是否为空
onCreate如果正常启动的话,其参数Bundle savedInstanceState为空,我们需要额外判断是否为空
2.2资源内存不足导致优先级的Activity被杀死
Activity按照优先级从高到低,可以分为以下三种:
(1)前台Activity:正在和用户交互的Activity,优先级最高;
(2)可见但非前台Activity:比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互;
(3)后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低
系统会按照上述优先级去杀死目标Activity所在的进程,并通过onSaveInstanceState与onRestoreInstanceState方法来存储和恢复数据。一些后台工作不适合脱离四大组件而独自运行在后台中,这样很容易被杀死。比较好的做法是将后台工作放入Service中从而保证进程有一定的优先级,这样不会轻易地被系统杀死。
3Fragment的生命周期分析:
先上经典图片:
刚好之前写过一篇封装BaseActivity与BaseFragment的文章:
从BaseActivity与BaseFragment的封装谈起
我这次加入了生命周期的监听,看看是什么情况:
测试情况,初始化第一个Fragment:
然后将当前Fragment进行remove操作,并加入回退栈:
返回到第一个Fragment:
退出当前Fragment:
测试结果一目了然。更好的理解与掌握Activity与Fragment的生命周期对以后开发有很大作用!!!
4 Activity的启动模式
任务栈:任务栈Task:一种以栈的形式来存放Activity实例的容器,原则是“后进先出”,主要有两个操作:压栈和出栈。其中存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。是为实现一个功能而负责管理所有用到的Activity实例的栈。默认情况下,所有Activity所需的任务栈名字为应用的包名。
启动一个Application的时候,系统会为它默认创建一个对应的Task,用来放置根Activity。默认情况下,启动Activity会放在同一个Task中,新启动的Activity会被压入启动它的那个Activity的栈中,并且显示新启动的Activity。当用户按下回退键时,这个Activity就会被弹出栈;当按下Home键回到桌面,再启动另一个应用,这时候之前那个Task就被移到后台,成为后台任务栈,而刚启动的那个Task就被调到前台,成为前台任务栈,Android系统显示的就是前台任务栈中的Top实例Activity。
启动模式: 通过设置启动模式来修改系统的默认行为
standard:标准模式,可以不用写配置,这也是系统的默认模式。每次启动一个Activity都会创建一个新的实例,不管这个实例是否已经存在。
应用场景:绝大多数Activity。
singleTop:栈顶复用模式,在这中模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNextIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate和onStart不会被系统调用,因为它并没有发生改变。
应用场景:假设目前栈内的情况为ABC,其中ABC为三个Activity,A位于栈底,C位于 栈顶。这个时候再次启动C,如果C的启动模式为singleTop,那么栈内的情况依然为ABC;如果C的启动模式为standard,那么由于C被创建,导致栈内的情况就变为ABCC。
singleTask:栈内复用模式, activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent() 方法,并且清空这个activity任务栈上面所有的activity。
应用场景:大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。
测试例子:首先从主界面A跳转到第二个界面B,再从第二个界面B跳转到第三个界面C,最后从第三个界面C跳转到主界面A。
main.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
second.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(getApplicationContext(), ThirdActivity.class));
}
});
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
third.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(new Intent(getApplicationContext(), MainActivity.class));
}
});
设置主界面A的启动模式为,其他两个是默认启动模式
android:launchMode="singleTask"
我们监听一下最后从第三个界面C跳转到主界面A发生了什么:
第二个界面B首先执行了onDestroy,然后第三个界面C也执行了onDestroy,也就是说两个Activity的实例都被销毁了,是不是这样呢?我们这时候在主界面按返回键,看看发生了什么:
果然,主界面A实例销毁,直接退出了应用,与预期效果一致。
singleInstance:单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。
应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。
1.3 Activity的Flags
系统提供了两种方式来设置一个Activity的启动模式,除了在AndroidManifest文件中设置以外,还可以通过Intent的Flag来设置一个Activity的启动模式,下面我们在简单介绍下一些Flag。
FLAG_ACTIVITY_NEW_TASK
使用一个新的Task来启动一个Activity,但启动的每个Activity都讲在一个新的Task中。该Flag通常使用在从Service中启动Activity的场景,由于Service中并不存在Activity栈,所以使用该Flag来创建一个新的Activity栈,并创建新的Activity实例。
FLAG_ACTIVITY_SINGLE_TOP
使用singletop模式启动一个Activity,与指定android:launchMode=“singleTop”效果相同。
FLAG_ACTIVITY_CLEAR_TOP
使用SingleTask模式来启动一个Activity,与指定android:launchMode=“singleTask”效果相同。
FLAG_ACTIVITY_NO_HISTORY
Activity使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Activity栈中。
测试例子:
还是上面一样的场景,这次我们不在配置文件设置,将从第三个界面C跳转到主界面A的代码设置如下:
third = (Button) findViewById(R.id.third_btn);
third.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setClass(ThirdActivity.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
});
看看发生了什么:
然后按下返回键:
与预期效果一致,完美。
5 IntentFilter的匹配原则
只有一个Intent同时匹配action类别,category类别,date类别才算完全匹配,只有完全匹配才能成功启动目标Activity。一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。
action的匹配规则:
action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同,action区分大小写,大小写不同字符串相同的action会匹配失败。
category的匹配规则:
Intent如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同,如果没有category依旧可以匹配成功。
data的匹配规则:
与action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。