Activity作为与用户相互的界面,其重要性不言而喻,其贵为四大组件之首,我们就分别从以下机个方面来聊一聊Activity。
正常情况下,活动的周期是下面这样的。
onCreate()
活动生命周期的开端,我们一般在此阶段进行初始化操作,比如设置布局、窗口等;
onReStart()
活动由后台转回前台时的开始阶段,比如活动A开启,HOME键返回桌面,再点击回到此应用时调用;
onStart()
此时活动已经可见了,但还无法与用户交互;
onResume()
此时活动由后台转到了前台,已经可以与用户进行交互了,onStart()和onResume()这两个状态时活动都已经时可见了的,不过onStart()还运行在后台,onResume()运行在了前台;
onPause()
正在停止,程序还在前台,所以不适合做耗时操作,因为这样会让用户感觉到卡顿;
onStop()
活动即将停止,可以稍微做一些重量级的工作,但同样不能太耗时;
onDestroy()
活动即将被销毁,这是活动的终点,我们可以在此活动中释放资源等;
问题一:onStart()、onResume()、onPause()、onStop()的异同
这两对似乎是做了同样的操作,实际上,它们只是评判标准不同,onStart()和onPause()是以活动是否可见来创建的回调,而onResume()和onStop()是以活动是否运行在前台来创建的回调,在实际运用的过程中,其实并没有太大区别;
问题二:活动A–>活动B经历了什么
A.onPause–>B.onCreate()–>B.onStart()–>B.onResume()–>A.onStop();
我们考虑这种情况,活动A需要从本地取出一张照片,对应分辨率下有对应的选择,当屏幕竖屏时,我们取到了一张图片并且显示出来,这是屏幕旋转了会发生什么呢?
实际上,当系统检测出我们的一些系统配置发生变化时,这个活动将会被销毁且重新创建,不过在销毁之前,活动会提供onSaveInstanceState()函数来让我们保存数据等,当活动重新创建时(恢复)会调用onRestoreInstanceState()函数来让我们恢复数据。由此可能产生两个问题,一系统在什么时机才会调用这两个函数?二我们应该保存哪些数据?
问题一:onSaveInstanceState()、onRestoreInstanceState()何时调用
onSaveInstanceState()在系统配置改变时就会被调用,比如屏幕的旋转、键盘的弹出等等,此时我们可以利用Bundle将我们希望保存的数据存起来,数据保存没问题了,我们再来看如何取出数据。
我们怎么知道我的活动该不该调用onRestoreInstanceState()呢,实际上,我们的系统很智能,它会判定这个即将要被销毁的活动有没有可能会被重新显示,如果可能的话则调用该函数。比如我们旋转屏幕时,销毁了当前的活动,但是当旋转完成后我们肯定还要显示这个活动,这是系统就会去调用onRestoreInstanceState()函数。再比如,我们按back退出该活动,仅仅就是退出而已。
问题二:该保存哪些数据呢
活动在配置资源改变导致活动重建时,虽说实例被重建了,但并不代表之前的一切都烟消云散了,事实上,我们的系统会通过委托的机制(层层委托保存)将View上的一些数据保存下来,重建后自动恢复,我们需要保存就是我们自定义的成员变量。
这个活动重建来重建去,是不是太耗内存了,我们如何避免一些不必要的重建呢?Android提供了configChanges属性来帮助我们实现这个愿望。有个这样简单的例子,这样当键盘弹出收起时改活动就不会再被重建,所以不会再有onSaveInstanceState()和onRestroeInstanceState(),但会调用一个onConfigurationChanged()方法。
<activity
android:configChanges="keyboardHidden">
activity>
一般常用的configChanges属性:
android:configChanges="orientation | screenSize" //屏幕旋转
android:configChanges="keyboardHidden" //键盘
上面所讨论的是当系统配置发生改变时对活动生命周期所产生的影响,这种影响是可控的。
下面提到只是和2中不同的一种情况,它所经历的生命周期是和2是一样的,只是引用的原因不同并且没有绝对避免发生的方案。这种情况就是因为系统内存不足而导致优先级低的活动被杀死,生命周期可参照2中描述。
launchMode这个概念应该不用再赘述。在讨论活动的launchMode之前我们先大致了解一下活动栈的概念。活动的任务栈由TaskAffinity(任务相关性)来决定,在默认情况下,APP中的活动的任务栈的名称就是应用的包名。我们可以这样理解,一般情况下,一个APP只有一个任务栈来管理活动。好了,下面来说说4中启动模式吧。
1. standard 标准模式
标准模式即默认模式,这种模式就是标准的铁头娃,我们没启动一个活动时,我啥也不管,只管创建一个新的实例放入到栈顶,我们假设此时任务栈是这样的,ABCD(D在栈顶)。
我们启动一个活动B,任务栈是这样了(ABCDB),再启动一个B(ABCDBB)…
2. singleTop 栈顶单一模式
有人也成为栈顶复用模式,不过本人认为栈顶单一模式更加贴切,我要启动一个活动A,我会去查看当前任务栈的栈顶是不是A。
如果是A,那么不会再创建A的实例,也不会重走什么生命周期,这是会调用onNewIntent()函数。
如果不是A,不管A是否存在于其它位置,还是会重建创建一个A。
情况一: ABCD–>启动一个D–>ABCD;
情况二: ABCD–>启动一个C–>ABCDC;
3. singleTask 栈内单一模式
同样可称为栈内复用模式。我们需要两个判定条件,一活动A需要的任务栈是否存在,二任务栈中A是否存在。我们启动一个A,首先会寻找其想要的任务栈是否存在。
1 任务栈不存在,则创建它的任务栈并把A放入到新创建的栈中;
2.1 任务栈存在,任务栈中没有A的实例,创建A放入到该栈中;
2.2 任务栈存在,任务栈中有A的实例,将A之上的所有活动弹出置A于该栈顶,调用onNewIntent()方法;
一般在同一APP下,我们不需要考虑栈是否存在的问题,只需判断2.1和2.2的情况就行。
4. singleInstance 单实例模式
singleTask的加强模式,因为被设置为该模式的活动A,它必须单独处于一个任务栈中。在栈中存在A的实例则复用,不存在A时,创建一个新栈且创建A的实例放入到新创建的栈中。
5. 一个特别的情况
当应用A启动了应用B的活动C时,如果此时活动C的allowTaskReparenting属性为true的话。
刚启动C时,应用B并没有被启动,所以活动C在应用A的任务栈内。
当应用B被启动时,系统检测出活动C是应用B下的,这时会将活动C从应用A的任务栈转入到应用B的任务栈中。
6. 如何定义启动模式
我们先谈论在注册文件中的写法,一般用singleTop比较多。
<activity
android:launchMode="singleTop">
activity>
Activity的Flags的作用主要有两个:设置启动模式、活动运行状态,一个 Intent 可以设置一个 flag,也可以选择若干个进行组合。
1. FLAG_ACTIVITY_SINGLE_TOP
它和清单文件中设置singleTop作用一致。
2. FLAG_ACTIVITY_NEW_TASK
默认情况下,我们startActivity()启动一个新的活动A时,活动A会和调用者在同一栈中。但是如果我们将A设置了FALG_ACTIVITY_NEW_TASK,如果是第一次执行,那么系统将会创建一个不同于调用者的栈,并且创建A的实例放入到栈中,如果不是第一次执行,则不会再创建实例和栈。
3. FLAG_ACTIVITY_CLEAR_TOP
我们如果设置了该模式,如果栈内存在该实例A则不会创建新的实例, 会将其上所有的Activity出栈。
如果在清单文件中活动A的launchMode是默认的,并且活动A又没有设置FLAG_ACTIVITY_SINGLE_TOP,此时活动A被销毁重新创建。
如果在清单文件中活动A的launchMode是默认的,并且活动A又设置FLAG_ACTIVITY_SINGLE_TOP,此时系统会将Intent发给这个已存在的活动A,然后调用onNewIntent()函数。