安卓Activity生命周期相关
activity作为安卓四大组件之一,是我们在开发中使用的最频繁的组件之一。
在这里就个人所了解的一些东西,和大家分享一下下。有错误之处,多多指正。
典型生命周期
首先,activity的生命周期(从创建到被垃圾回收过程中一定会执行的方法就叫做生命周期方法)这里就放上官方文档上最经典的图吧。
大家对这幅图肯定也无比熟悉了。
一个正常典型情况下的activity启动,会经历如下生命周期:
- onCreate:activity创建时,会首先启动这个方法。一般这个方法中,我们会做一些初始化工作。
- onDestroy:activity即将被销毁时调用的方法,是生命周期的最后一个方法。一般,我们会做一些回收工作和最终的资源释放,或者是保存一些未保存的内容(比如保存信息为草稿等)
- onStart:activity正在被启动,即将开始时,这个时候activity界面可见,但是无法和用户交互,activity还在后台
- onStop:activity界面不可见时调用的方法。可以做些微重量级的回收工作。
- onResume:activity获取焦点时的方法,已经出现在前台并且开始活动。
- onResume:activity获取焦点时的方法,已经出现在前台并且开始活动。
- onPause:activity失去焦点时调用的方法,但是此时activity可见。这里可以做一些存储数据、停止动画的工作,但是不能太过耗时。因为一个新的activity的启动,onPause必须先执行完,新Activity的onResume才会调用
- onRestart:activity在重新启动。一般是用户点击home键回到桌面或者跳到新的activity时,接着用户又重新回到这个activity,界面由不可见变为可见,执行onRestart-onStart-onResume
在图中,我们发现这样的一段话[Another activity comes in front of the activity][6]调用时会首先执行onPause方法。
为什么呢?从源码角度上大概是这个流程:
首先,启动activity的请求会由Instrumentation(可以理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用Target Package声明)的工具类)来处理, 它会通过代理对象binder向ActivityManagerService(简称AMS,内部维护着一个ActivityStack)发送请求,AMS通过ActivityThread去同步activity的状态。新的activity启动时,会调用ActivityStack中的resumeTopActivityInnerLocked方法,使栈顶的activity需要先onPause变为后台后,新的activity才会被启动。
这也是为什么官方文档中对onPause的说明是,不能在onPause中做重量级的操作。
代码测试如下,其实很简单,当点击第一个activity的按钮时,会跳转到第二个activity(透明的主题)。当开启第二个activity时,会发现打印的log如下:
这里因为第二个activity是透明的主题,第一个activity虽然失去了焦点,不在前台,但仍处于可见状态,所以不会执行onStop方法。
异常情况下的生命周期
当资源相关的系统配置发生改变或者系统内存不足时,activity可能会被杀死。
譬如当前activity处于竖屏状态,如果旋转屏幕,由于系统配置发生了改变,在默认情况下,activity就会被销毁并且重新创建。其onPause、onStop、onDestroy都会被调用,系统还会调用onSaveInstanceState来保存当前activity的状态。这个方法在onStop之前调用,正常情况下系统不会调用这个方法,只有在被异常终止的情况下才会调用。当activity被重新创建后,将onSaveInstanceState保存的Bundle对象传递给onRestoreInstanceState和onCreate方法。如果被重建了,我们就可以通过bundle对象恢复之前的数据。会在onStart之后调用onRestoreInstanceState。
这样当异常情况下需要重新创建时,系统会默认为我们保存当前activity的视图结构,并且在activity重启后为每个View恢复数据。需要注意的是,系统只恢复那些被开发者指定过id的控件,如果没有为控件指定id,则系统就无法恢复了。
我们可以通过代码测试一下。首先当前的EditText控件并没有设置id,当输入内容后反转屏幕,EditText并未恢复其数据。
当设置id后,EditText才会恢复其数据。为了测试,自定义了一个EditText重写了其setText方法。
@override
public void setText(CharSequence text,BufferType type){
super.setText(text,type);
if(text.toString.equals("air")){
throw new RuntimeException("----air---");
}
}
首先,在文本输入框内,输入air,当屏幕反转后,activity恢复EditText数据时调用setText方法,会抛出异常。异常如下。
这里可以看出一个工作流程:首先Activity被意外终止时,Activity会调用onSavedInstanceState去保存数据,当需要恢复数据时,Activity会委托Window去恢复数据,接着Window再委托它上面的顶级容器去恢复数据,顶层容器是一个ViewGroup.最后顶层容器再去一一通知它的子元素来恢复数据,这样整个数据恢复过程就完成了,可以发现,这是一种典型的委托思想,上层委托下层,父容器委托子容器去处理一些事情。安卓中View的绘制机制、事件分发都是采用了类似的思想。
通过查看源码,我们不难得知,只有设置了id,才会调用view的onRestoreInstanceState方法去恢复数据。
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {//只有设置了id,才会去调用恢复状态的方法
Parcelable state = container.get(mID);
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
onRestoreInstanceState(state);
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
}
而当前的editText恢复数据时,会调用setText方法,发现反转前输入的文本为air时,抛出异常。
@Override
public void onRestoreInstanceState(Parcelable state) {//恢复状态
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
// XXX restore buffer type too, as well as lots of other stuff
if (ss.text != null) {
setText(ss.text);//会调用setText方法
}
if (ss.selStart >= 0 && ss.selEnd >= 0) {
if (mText instanceof Spannable) {
int len = mText.length();
if (ss.selStart > len || ss.selEnd > len) {
String restored = "";
if (ss.text != null) {
restored = "(restored) ";
}
Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
"/" + ss.selEnd + " out of range for " + restored +
"text " + mText);
} else {
Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
if (ss.frozenWithFocus) {
createEditorIfNeeded();
mEditor.mFrozenWithFocus = true;
}
}
}
}
既然系统配置发生改变后,activity会被重新创建。那么,当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。当屏幕旋转时,我们可以配置android:configChanges=”orientation”。如果想指定多个值,可以用“|”连接起来,例如android:configChanges=”orientation|keyboardHidden”。
系统配置中所包含的项目是很多,我们可以自行查找这些配置的意义,但是常用的一般有locale(设备本地位置发生改变),orientation(屏幕方向发生改变),keyboardHidden(键盘类型发生改变),screenSize(屏幕尺寸发生改变),其他很少使用。
screenSize和smallScreenSize比较特殊,他们的行为和编译选项有关,和运行环境无关。只有编译选项中minSdkVersion和TargetVersion有一项大于API13时,即4.0以后,会导致activity重启,需要加上screenSize。
如果配置了android:screenOrientation属性,则会使android:configChanges=”orientation”失效
进程的优先级:
* Foreground process
前台进程: 用户正在操作的应用程序所在的进程就是前台进程
Visible process
可视进程: 用户已经不能操作这个应用程序了,但是界面用户仍然可以看到
Service process
服务进程: 应用程序有一个服务代码正在运行
Background process
后台进程: 应用程序有界面,但是界面被用户最小化(home)
Empty process
空进程: 应用程序没有任何运行的Activity,service.
前台进程>可视进程>服务进程>后台进程>空进程
当系统内存不足时,系统就会按照上述的优先级顺序选择杀死Activity所在的进程,并在后续通过onSaveInstanceState缓存数据和onRestoreInstanceState恢复数据。
如果一个进程中没有四大组件在执行,那么这个进程将很快被杀死,因此,一些后台工作不适合脱离了四大组件工作,比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易的被系统杀死。
Over。
[暮春者,春服既成,冠者五六人,童子六七人,浴乎沂,风乎舞雩,咏而归][6]