您现在的位置是:首页 > 文章详情

Activity的生命周期和启动模式

日期:2016-08-01点击:643

先上一张经典图片镇楼:

这里写图片描述

测试正常情况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。

原文链接:https://yq.aliyun.com/articles/446030
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章