Activity的生命周期和启动模式
先上一张经典图片镇楼:
测试正常情况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。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Android5.1.1 - APK签名校验分析和修改源码绕过签名校验
Android5.1.1 - APK签名校验分析和修改源码绕过签名校验 作者:寻禹@阿里聚安全 APK签名校验分析 找到PackageParser类,该类在文件“frameworks/base/core/java/android/content/pm/PackageParser.java”中。PackageParser类的collectCertificates方法会对APK进行签名校验,在该方法会遍历APK中的所有文件,并对每个文件进行校验。下面是该方法的部分源码: APK是一个ZIP格式的文件所以使用ZIP相关的类进行读写。上面代码中调用了loadCertificates方法,这个方法返回一个二维数组,如果APK中的文件签名校验失败那么loadCertificates方法会返回一个空数组(可能是null,可能是数组长度为0),按照上面代码的逻辑如果数组为空则会抛出异常。 loadCertificates方法的代码见下: 上面代码中is是JarFile.JarFileInputStream类的对象。loadCertificates方法中调用了readFullyIgnoringConte...
- 下一篇
Android蓝牙通信
Android蓝牙通信 效果图 两台真机设备 源码 GitHub 关于蓝牙的开关控制,设置设备可见、搜索附近的蓝牙设备,已经封装到了 BluetoothManager 类 关于设备的连接、通信。已经封装到了 BluetoothService 类 注:下面的全部内容,主要是思路,具体的可以参考上面的源码,如果对你有帮助记得给个赞哦。 权限 <!-- 蓝牙的权限 --> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 蓝牙的打开与关闭 开启蓝牙 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); /** * 开启蓝牙 */ public void openBluetooth() { try { mBluetoothAdapter.enable(); } catch (Exce...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Hadoop3单机部署,实现最简伪集群
- Mario游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Red5直播服务器,属于Java语言的直播服务器