2.1Activity基础介绍
目录介绍
- 01.Activity生命周期
- 1.1 生命周期方法说明
- 1.2 特殊情况下生命周期
- 1.3 Activity三种运行状态
- 1.4 App切换到后台分析
- 02.Activity的启动模式
- 2.1 启动模式的类别
- 2.2 启动模式的结构——栈
- 2.3 标准模式(standard)
- 2.4 栈顶复用模式(singleTop)
- 2.5 栈内复用模式(singleTask)
- 2.6 单例模式(singleInstance)
- 2.7 Activity的Flags
- 03.特殊情况栈交互
- 04.Activity异常情况
- 4.1 Activity异常生命周期
- 4.2 后台Activity被异常回收
01.Activity生命周期
1.1 生命周期方法说明
- 在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期:
- (1) onCreate():当 Activity 第一次创建时会被调用。这是生命周期的第一个方法。在这个方法中,可以做一些初始化工作,比如调用setContentView去加载界面布局资源,初始化Activity所需的数据。当然也可借助onCreate()方法中的Bundle对象来回复异常情况下Activity结束时的状态(后面会介绍)。
- (2) onRestart():表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为导致的,比如用户按Home键切换到桌面或打开了另一个新的Activity,接着用户又回到了这个Actvity。(关于这部分生命周期的历经过程,后面会介绍。)
- (3) onStart(): 表示Activity正在被启动,即将开始,这时Activity已经出现了,但是还没有出现在前台,无法与用户交互。这个时候可以理解为Activity已经显示出来,但是我们还看不到。
- (4) onResume():表示Activity已经可见了,并且出现在前台并开始活动。需要和onStart()对比,onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
- (5) onPause():表示 Activity正在停止,仍可见,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地回到当前Activity,那么onResume就会被调用(极端情况)。onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行。
- (6) onStop():表示Activity即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。
- (7) onDestroy():表示Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。
- 在平常的开发中,我们经常用到的就是 onCreate()和onDestroy(),做一些初始化和回收操作。
1.2 特殊情况下生命周期
- 第一种情况,销毁当前的Activity后重建,这种也尽量避免。
- 在横竖屏切换的过程中,会发生Activity被销毁并重建的过程。在了解这种情况下的生命周期时,首先应该了解这两个回调:onSaveInstanceState和onRestoreInstanceState。
- 在Activity由于异常情况下终止时,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用是在onStop之前,它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。
- 当异常终止的Activity被重建以后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象参数同时传递给onRestoreInstanceState和onCreate方法。
- 因此,可以通过onRestoreInstanceState方法来恢复Activity的状态,该方法的调用时机是在onStart之后。其中onCreate和onRestoreInstanceState方法来恢复Activity的状态的区别: onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断。onCreate需要非空判断。建议使用onRestoreInstanceState。
- 第二种情况,当前的Activity不销毁,设置Activity的属性。
- 比如视频播放器就经常会涉及屏幕旋转场景。可以通过在AndroidManifest文件的Activity中指定如下属性:
<activity android:name=".activity.VideoDetailActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="portrait"/>
- 来避免横竖屏切换时,Activity的销毁和重建,而是回调了下面的方法:
//重写旋转时方法,不销毁activity @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); }
- 资源内存不足导致优先级低的Activity被杀死
- Activity优先级的划分和下面的Activity的三种运行状态是对应的。
- (1) 前台Activity——正在和用户交互的Activity,优先级最高。
- (2) 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户交互。
- (3) 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
- 当系统内存不足时,会按照上述优先级从低到高去杀死目标Activity所在的进程。我们在平常使用手机时,能经常感受到这一现象。这种情况下数组存储和恢复过程和上述情况一致,生命周期情况也一样。
1.3 Activity三种运行状态
- ①Resumed(活动状态)
- 又叫Running状态,这个Activity正在屏幕上显示,并且有用户焦点。这个很好理解,就是用户正在操作的那个界面。
- ②Paused(暂停状态)
- 这是一个比较不常见的状态。这个Activity在屏幕上是可见的,但是并不是在屏幕最前端的那个Activity。比如有另一个非全屏或者透明的Activity是Resumed状态,没有完全遮盖这个Activity。
- ③Stopped(停止状态)
- 当Activity完全不可见时,此时Activity还在后台运行,仍然在内存中保留Activity的状态,并不是完全销毁。这个也很好理解,当跳转的另外一个界面,之前的界面还在后台,按回退按钮还会恢复原来的状态,大部分软件在打开的时候,直接按Home键,并不会关闭它,此时的Activity就是Stopped状态。
1.4 App切换到后台分析
- app切换到后台,当前activity会走onDestroy方法吗?
- 不会走onDestroy方法,会先后走onPause和onStop方法。
- 一般在onStop方法里做什么?
- 比如。写轮播图的时候,会在onStop方法里写上暂停轮播图无限轮播,在onStart方法中会开启自动无限轮播。
- 再比如,写视频播放器的时候,当app切换到后台,则需要停止视频播放,也是可以在onStop中处理的。
- 什么情况会导致app会被杀死,这时候会走onDestroy吗?
- 系统资源不足,会导致app意外被杀死。应用只有在进程存活的情况下才会按照正常的生命周期进行执行,如果进程突然被kill掉,相当于System.exit(0); 进程被杀死,根本不会走(activity,fragment)生命周期。只有在进程不被kill掉,正常情况下才会执行onDestroy()方法。
02.Activity的启动模式
2.1 启动模式的类别
- Android提供了四种Activity启动方式:
- 标准模式(standard)
- 栈顶复用模式(singleTop)
- 栈内复用模式(singleTask)
- 单例模式(singleInstance)
2.2 启动模式的结构——栈
- Activity的管理是采用任务栈的形式,任务栈采用“后进先出”的栈结构。
2.3 标准模式(standard)
- 每启动一次Activity,就会创建一个新的Activity实例并置于栈顶。谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。
- 例如:Activity A启动了Activity B,则就会在A所在的栈顶压入一个新的Activity。
- 特殊场景
- 特殊情况,如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。
- 解决办法:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。
- 应用场景:
- 绝大多数Activity。如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理。
- 所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。
- 特殊情况,如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。
2.4 栈顶复用模式(singleTop)
- 如果需要新建的Activity位于任务栈栈顶,那么此Activity的实例就不会重建,而是重用栈顶的实例。并回调如下方法:
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); }
- 由于不会重建一个Activity实例,则不会回调其他生命周期方法。
- 如果栈顶不是新建的Activity,就会创建该Activity新的实例,并放入栈顶。
- 应用场景:
- 在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。
- 当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。
- 同standard模式,如果是外部程序启动singleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。
2.5 栈内复用模式(singleTask)
- 该模式是一种单例模式,即一个栈内只有一个该Activity实例。
- 该模式,可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中,即singleTask的Activity可以指定想要加载的目标栈。
- singleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中。
<activity android:name=".Activity1" android:launchMode="singleTask" android:taskAffinity="com.yc.task" android:label="@string/app_name"> </activity>
- 关于taskAffinity的值:
- 每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。
- 这个也可以不用设置 执行逻辑:
- 在这种模式下,如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入栈内。
- 如果Activity指定的栈存在,如果其中没有该Activity实例,则会创建Activity并压入栈顶,如果其中有该Activity实例,则把该Activity实例之上的Activity杀死清除出栈,重用并让该Activity实例处在栈顶,然后调用onNewIntent()方法。
- 对应如下三种情况:
- 应用场景:
- 大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。
- 在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。
- 应用案例
- 例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。
2.6 单例模式(singleInstance)
- 作为栈内复用模式(singleTask)的加强版
- 打开该Activity时,直接创建一个新的任务栈,并创建该Activity实例放入新栈中。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。
- 应用场景:
- 呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。
2.7 Activity的Flags
- Activity的Flags很多,这里介绍集中常用的,用于设定Activity的启动模式。可以在启动Activity时,通过Intent的addFlags()方法设置。
- (1)FLAG_ACTIVITY_NEW_TASK
- 其效果与指定Activity为singleTask模式一致。
- (2)FLAG_ACTIVITY_SINGLE_TOP
- 其效果与指定Activity为singleTop模式一致。
- (3)FLAG_ACTIVITY_CLEAR_TOP
- 具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。如果和singleTask模式一起出现,若被启动的Activity已经存在栈中,则清除其之上的Activity,并调用该Activity的onNewIntent方法。如果被启动的Activity采用standard模式,那么该Activity连同之上的所有Activity出栈,然后创建新的Activity实例并压入栈中。
- 同一程序不同的Activity是否可以放在不同的Task任务栈中?
- 可以的。比如:启动模式里有个singleInstance,可以运行在另外的单独的任务栈里面。用这个模式启动的activity,在内存中只有一份,这样就不会重复的开启。
- 也可以在激活一个新的activity时候,给intent设置flag,Intent的flag添加FLAG_ACTIVITY_NEW_TASK,这个被激活的activity就会在新的task栈里面
03.特殊情况栈交互
- 特殊情况——前台栈和后台栈的交互
- 假如目前有两个任务栈。前台任务栈为AB,后台任务栈为CD,这里假设CD的启动模式均为singleTask,现在请求启动D,那么这个后台的任务栈都会被切换到前台,这个时候整个后退列表就变成了ABCD。当用户按back返回时,列表中的activity会一一出栈。
- 如下图。
- 如果不是请求启动D而是启动C,那么情况又不一样,如下图。
- 调用SingleTask模式的后台任务栈中的Activity,会把整个栈的Activity压入当前栈的栈顶。
- singleTask会具有clearTop特性,把之上的栈内Activity清除。
04.Activity异常情况
4.1 Activity异常生命周期
- 虽然前面也说了这个异常的生命周期,但是还是有些疑问?
- 异常条件会调用什么方法
- 当非人为终止Activity时,比如系统配置发生改变时导致Activity被杀死并重新创建、资源内存不足导致低优先级的Activity被杀死,会调用 onSavaInstanceState() 来保存状态。该方法调用在onStop之前,但和onPause没有时序关系。
- 有人会问,onSaveInstanceState()与onPause()的区别,onSaveInstanceState()适用于对临时性状态的保存,而onPause()适用于对数据的持久化保存。
- 当异常崩溃后App又重启了,这个时候会走onRestoreInstanceState()方法,可以在该方法中取出onSaveInstanceState()保存的状态数据。
- 什么时候会引起异常生命周期
- 资源相关的系统配置发生改变或者资源不足:例如屏幕旋转,当前Activity会销毁,并且在onStop之前回调onSaveInstanceState保存数据,在重新创建Activity的时候在onStart之后回调onRestoreInstanceState。其中Bundle数据会传到onCreate(不一定有数据)和onRestoreInstanceState(一定有数据)。
- 还有中情况是异常导致app崩溃,然后并没有完全杀死进程,重启回到activity页面,也会引起异常生命周期。
4.2 后台Activity被异常回收
后台的Activity被系统回收怎么办?
- Activity中提供了一个 onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
- onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据。每个保存方法需要传入两个参数,第一个参数是键,用于后面从 Bundle中取值,第二个参数是真正要保存的内容。
说一下onSaveInstanceState()和onRestoreInstanceState()方法特点?
- Activity的 onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法,它们不同于onCreate()、onPause()等生命周期方法,它们并不一定会被触发。
//保存数据 @Override protected void onSaveInstanceState(Bundle outBundle) { super.onSaveInstanceState(outBundle); outBundle.putBoolean("Change", mChange); } //取出数据 @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mChange = savedInstanceState.getBoolean("Change"); } //或者在onCreate方法取数据也可以 //onCreate()方法其实也有一个Bundle类型的参数。这个参数在一般情况下都是null, //但是当活动被系统回收之前有通过 onSaveInstanceState()方法来保存数据的话,这个参就会带有之前所保存的全部数据 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { String data = savedInstanceState.getString("data"); } }
- Activity的 onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法,它们不同于onCreate()、onPause()等生命周期方法,它们并不一定会被触发。
什么时候会触发走这两个方法?
- 当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。除非该activity是被用户主动销毁的,通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
onSaveInstanceState()被执行的场景有哪些?
- 系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activityA是否会被销毁,因此系统都会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则当用户按下HOME键时
- 长按HOME键,选择运行其他的程序时
- 锁屏时
- 从activity A中启动一个新的activity时
- 屏幕方向切换时
- 系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activityA是否会被销毁,因此系统都会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则当用户按下HOME键时
屏幕旋转时生命周期
- 屏幕旋转时候,如果不做任何处理,activity会经过销毁到重建的过程。一般这种效果都不是想要的。比如视频播放器就经常会涉及屏幕旋转场景。技术博客大总结
- 第一种情况:当前的Activity不销毁【设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法】
<activity android:name=".activity.VideoDetailActivity" android:configChanges="orientation|keyboardHidden|screenSize" android:screenOrientation="portrait"/>
- 执行该方法
//重写旋转时方法,不销毁activity @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); }
- 第二种情况:销毁当前的Activity后重建,这种也尽量避免。【不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,默认首先销毁当前activity,然后重新加载】
1.0.0.3 如何避免配置改变时Activity重建?优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态?
- 如何避免配置改变时Activity重建
- 为了避免由于配置改变导致Activity重建,可在AndroidManifest.xml中对应的Activity中设置android:configChanges="orientation|screenSize"。此时再次旋转屏幕时,该Activity不会被系统杀死和重建,只会调用onConfigurationChanged。因此,当配置程序需要响应配置改变,指定configChanges属性,重写onConfigurationChanged方法即可。
- 使用场景,比如视频播放器横竖屏切换播放视频,就需要设置这种属性。具体可以看我封装的视频播放器库,地址:https://github.com/yangchong211/YCVideoPlayer
- 优先级低的Activity在内存不足被回收后怎样做可以恢复到销毁前状态
- 优先级低的Activity在内存不足被回收后重新打开会引发Activity重建。Activity被重新创建时会调用onRestoreInstanceState(该方法在onStart之后),并将onSavaInstanceState保存的Bundle对象作为参数传到onRestoreInstanceState与onCreate方法。因此可通过onRestoreInstanceState(Bundle savedInstanceState)和onCreate((Bundle savedInstanceState)来判断Activity是否被重建,并取出数据进行恢复。但需要注意的是,在onCreate取出数据时一定要先判断savedInstanceState是否为空。
- 如何判断activity的优先级?技术博客大总结
- 除了在栈顶的activity,其他的activity都有可能在内存不足的时候被系统回收,一个activity越处于栈底,被回收的可能性越大.如果有多个后台进程,在选择杀死的目标时,采用最近最少使用算法(LRU)。
1.0.0.6 说下Activity的四种启动模式?singleTop和singleTask的区别以及应用场景?任务栈的作用是什么?
- Activity的四种启动模式
- standard标准模式:每次启动一个Activity就会创建一个新的实例
- singleTop栈顶复用模式:如果新Activity已经位于任务栈的栈顶,就不会重新创建,并回调 onNewIntent(intent) 方法
- singleTask栈内复用模式:只要该Activity在一个任务栈中存在,都不会重新创建,并回调 onNewIntent(intent) 方法。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,并把该Activity放进去;如果存在,就会创建到已经存在的栈中
- singleInstance单实例模式:具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例
- singleTop和singleTask的区别以及应用场景
- singleTop:同个Activity实例在栈中可以有多个,即可能重复创建;该模式的Activity会默认进入启动它所属的任务栈,即不会引起任务栈的变更;为防止快速点击时多次startActivity,可以将目标Activity设置为singleTop
- singleTask:同个Activity实例在栈中只有一个,即不存在重复创建;可通过android:taskAffinity设定该Activity需要的任务栈,即可能会引起任务栈的变更;常用于主页和登陆页
- singleTop或singleTask的Activity在以下情况会回调onNewIntent()
- singleTop:如果新Activity已经位于任务栈的栈顶,就不会重新创建,并回调 onNewIntent(intent) 方法
- singleTask:只要该Activity在一个任务栈中存在,都不会重新创建,并回调 onNewIntent(intent) 方法
- 任务栈的作用是什么?技术博客大总结
- 它是存放 Activity 的引用的,Activity不同的启动模式,对应不同的任务栈的存放;可通过 getTaskId()来获取任务栈的 ID,如果前面的任务栈已经清空,新开的任务栈ID+1,是自动增长的;首先来看下Task的定义,Google是这样定义Task的:Task实际上是一个Activity栈,通常用户感受的一个Application就是一个Task。从这个定义来看,Task跟Service或者其他Components是没有任何联系的,它只是针对Activity而言的。
- 同一程序不同的Activity是否可以放在不同的Task任务栈中?
- 可以的。比如:启动模式里有个Singleinstance,可以运行在另外的单独的任务栈里面。用这个模式启动的activity,在内存中只有一份,这样就不会重复的开启。
- 也可以在激活一个新的activity时候,给intent设置flag,Intent的flag添加FLAG_ACTIVITY_NEW_TASK,这个被激活的activity就会在新的task栈里面
其他介绍
01.关于博客汇总链接
02.关于我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e