一直对Activity的各种生命周期方法、创建和销毁时机以及Activity的4种启动模式没有彻底的了解清楚,详细整理记录一下这些知识,以后哪再不清楚时不会再到处查阅资料了。
首先,从网上拷过来一张图片,对这张图片,相信很多人都非常熟悉,它包括了Activity从创建到销毁的全部过程,如下图所示:
1、Activity的创建与销毁
- 从Activity的创建开始说起,当启动一个Activity时,生命周期方法调用:
onCreate()→onStart()→onResume()
此时,Activity显示在前台界面和用户交互,开始运行
从Activity点击Back键回到桌面,生命周期方法调用:
onPause()→onStop()→onDestroy()
此时,Activity被销毁,这是最常见的创建与销毁流程
在当前Activity点击Home键回到桌面,Activity进入后台运行,再次启动该Activity,生命周期方法调用:
onPause()→onStop()→onRestart()→onStart()→onResume()
如果在Activity点击Back键退出Activity,并且在当前Activity的onStop()方法还没有被调用之前,再次启动Acitivity,生命周期方法调用:
onPause()→onResume()
这种情况属于很极端的情况,在正常的用户操作下很难重现这一场景。
- 从当前Activity启动一个新的Activity:
当前显示的Activity为A,要启动的Activity为B,生命周期方法调用:
Activity A的onPause()→Activity B的onCreate()→Activity B的onStart()→Activity B的onResume()→Activity A的onStop()→Activity A的onDestroy()
此时,Activity A被销毁,Activity B显示到前台。如果Activity B的主题是透明主题,则Activity A不会被销毁,即Activity A的onStop()和onDestroy()不会被执行
可以看到,Activity A的onPause()方法执行完毕后,Activity B才会被创建,所以在onPause()方法中不可以有特别耗时的操作,否则将会影响新Activity的显示
2、Activity重新创建时数据的保存与恢复
由于资源内存不足或者系统配置发生变化导致Activity被销毁时,需要保存Activity中的数据,例如在Activity界面启动新的Activity、在Activity界面接到来电,在回到前一个Activity时,希望可以恢复原来的效果,这时就需要在Activity销毁时保存必要的数据,以便再次启动时恢复。
- 通过Activity的onSaveInstanceState()和onRestoreInstanceState()来保存和恢复数据
Activity被异常终止时,系统会调用onSaveInstanceState()方法来保存当前Activity的状态,调用时机在onStop()方法之前,和onPause()方法没有既定的时序关系,尽管大多数情况下都是在onPause()方法之后调用。onSaveInstanceState()方法接受一个Bundle参数,用于保存数据。
在这里,就用手机切换横竖屏时的场景进行测试,手机在由横屏切换到竖屏时,Activity会进行销毁并重新创建,如果不进行处理,界面上的数据就不会保存。
重写Activity的生命周期方法和onSaveInstanceState()、onRestoreInstanceState()方法并打印log。
public class MainActivity extends ListActivity {
public static final String TAG = "MainActivity";
private List<String> datas = new ArrayList<String>();
private ArrayAdapter<String> adapter;
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
initAdapter();
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate");
new Thread() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i = 0; i < 10; i ++) {
datas.add("This is test item " + i);
}
Log.i(TAG, "loadData");
handler.sendEmptyMessage(0x123);
};
}.start();
}
public void initAdapter() {
adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, datas);
setListAdapter(adapter);
}
@Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
Log.i(TAG, "onStart");
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
Log.i(TAG, "onResume");
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
Log.i(TAG, "onPause");
}
@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
Log.i(TAG, "onStop");
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.i(TAG, "onDestroy");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
Log.i(TAG, "onSaveInstanceState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
Log.i(TAG, "onRestoreInstanceState");
}
}
在Activity中启动了一个子线程来加载10条数据,运行程序
2秒后数据加载完显示,此时将屏幕切换为横屏
可以看到,切换屏幕方向后,Activity被重新创建了,由于没有在onSaveInstanceState()方法中保存数据,所以在最后又加载了一遍
现在修改onSaveInstanceState方法,把数据保存到Bundle中
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
Log.i(TAG, "onSaveInstanceState");
outState.putSerializable("data", datas);
}
在Activity再次创建的时候判断是否有数据传入,如果有的话不需要再次加载
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate");
if(savedInstanceState != null) {
datas = savedInstanceState.getStringArrayList("data");
}
if(datas.size() == 0) {
new Thread() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i = 0; i < 10; i ++) {
datas.add("This is test item " + i);
}
Log.i(TAG, "loadData");
handler.sendEmptyMessage(0x123);
};
}.start();
} else {
initAdapter();
}
}
再次运行程序
第一次启动Activity,执行数据加载,切换横屏
可以看到,由于在onSaveInstanceState方法中保存了data,Activity被重新创建后没有打印loadData的log,证明没有重新加载
本程序是在onCreate()中获取保存的数据,onCreate()和onRestoreInstanceState()中都有一个Bundle参数,都可以用来恢复,区别在于如果调用了onRestoreInstanceState()方法,Bundle中一定是有数据的,而onCreate()中却不一定,因为如果是第一次启动Activity,Bundle中是没有数据的,所以在onCreate()中恢复的话需要判断Bundle是否为空
3、通过设置配置项控制屏幕切换时的生命周期
当屏幕发生旋转时,可以通过在AndroidManifest.xml中配置Activity的android:configChanges来控制Activity是否销毁并重新创建
重写Activity的生命周期方法和onConfigurationChanged()方法
不设置android:configChanges项
启动Activity,从竖屏切换到横屏
从横屏再切换为竖屏
从log中看到,切换横竖屏时都会销毁并重新创建Activity,各调用一次Activity的生命周期
- 设置android:configChanges=”orientation|screenSize”,启动Activity,从横屏切换到竖屏
从竖屏切换回横屏
配置android:configChanges=”orientation|screenSize”后,屏幕在旋转时不会重新创建Activity,只会调用Activity的onConfigurationChanges()方法,所以可以通过选项来控制屏幕旋转时的生命周期
4、Activity的4种启动模式
- 任务栈
要彻底弄清楚Activity的启动模式,首先要了解Task栈的概念,Android采用Task栈来管理多个Activity,Activity的任务栈是一种“先进先出”的栈结构,先启动的Activity放在任务栈底,后启动的放在任务栈顶
启动一个应用时,Android系统会为之创建一个Task栈,默认为应用的包名,可以通过指定android:taskAffinity来指定任务栈的名字
- standard模式
标准模式,Activity启动的默认模式。每一次启动一个Activity都会重新创建一个实例,不管这个实例是否已经存在。在这种模式下,被启动的Activity会运行在启动它的Activity所在的栈中。
创建工程,新建两个Activity:MainActivity和SecondActivity
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button firstButton = (Button)findViewById(R.id.first);
firstButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
SecondActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button secondButton = (Button)findViewById(R.id.second);
secondButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent(SecondActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
运行程序,启动MainActivity,使用adb shell dumpsys activity命令查看应用中的任务栈情况
MainActivity在TaskRecord{410aee30 #3 A com.example.testlaunchmode U 0}这个任务栈中
点击MainActivity中的Button启动SecondActivity,查看任务栈
SecondActivity被加入MainActivity所在的栈中,位于栈顶。点击SecondActivity中的Button再次启动SecondActivity,查看任务栈
在TaskRecord{410aee30 #3 A com.example.testlaunchmode U 0}中再次加入了一个SecondActivity实例,最先启动的MainActivity在栈底
在开发有时会遇到使用ApplicationContext启动standard模式的Activity会报错,这是因为非Activity类型的Context没有所谓的任务栈
singleTop模式
顶单例模式,如果新Activity位于栈顶,不会重新创建实例,会回调Activity的onNewIntent()方法,通过android:launchMode=”singleTop”指定
设置上例中的SecondActivity的启动模式为singleTop,重写onNewIntent()方法
在MainActivity中启动SecondActivity,查看任务栈
在SecondActivity中再次启动SecondActivity
由于SecondActivity实例已经位于栈顶,再次启动该Activity不会创建新的实例,只会回调onNewIntent()方法。如果启动的Activity实例没有位于栈顶,会重新创建并加入栈
新建ThirdActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.third);
Button thirdButton = (Button)findViewById(R.id.third);
thirdButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent(ThirdActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
在ThirdActivity中点击Button启动SecondActivity,修改SecondActivity,点击Button启动ThirdActivity,运行程序
当SecondActivity没有位于栈顶时,再次启动会重新创建实例
- singleTask
Task内单例模式,只要Activity在一个栈中存在,多次启动Activity不会重新创建实例,会回调onNewIntent()方法
启动Activity时,系统首先会寻找是否存在此Activity所需要的任务栈,如果不存在,创建任务栈,并创建Activity实例放入栈中。若存在任务栈,查看该Activity在栈中是否存在,如果存在,把实例调到栈顶,回调onNewIntent()方法。如果不存在实例,创建实例并压入栈中
这种模式包含的情况较多,一个一个来分析
(1)被启动的Activity和启动它的Activity属于同一个栈
还使用上一个例子说明,使用MainActivity启动SecondActivity时,由于两个Activity属于同一应用,而且都没有指定android:taskAffinity属性,所以这两个Activity属于同一个栈中,所以会创建SecondActivity的实例加入到栈中
在SecondActivity中再次启动SecondActivity时,由于SecondActivity已经存在栈中,所以不会创建实例,会回调SecondActivity的onNewIntent()方法
如果在ThirdActivity中启动SecondActivity,这时栈中的情况
再次启动SecondActivity时,由于SecondActivity已经存在于栈中,但是没有位于栈顶,所以也不会创建实例,而是将SecondActivity调到栈顶,即让ThirdActivity出栈
同样会调用SecondActivity的onNewIntent()方法
(2)被启动的Activity和启动它的Activity不属于同一个栈,这种情况分为同一个应用中两个Activity的taskAffinity属性不用和两个不同应用中的两个Activity,不过性质是相同的
为SecondActivity指定android:taskAffinity属性,和当前包名不一样,这样,MainActivity和SecondActivity就不属于同一个任务栈,在MainActivity中启动SecondActivity
SecondActivity没有被加入到MainActivity所在的任务栈中,而是被加入了新创建的任务栈TaskRecord{410c0e48 #8 A com.example.testlaunchmode.secondtask U 0},在SecondActivity再次启动SecondActivity
没有再次创建SecondActivity的实例
在SecondActivity中启动ThirdActivity
在ThirdActivity中启动SecondActivity
没有再次创建SecondActivity的实例,而是将ThirdActivity出栈,并会回调SecondActivity的onNewIntent()方法
- singleInstance模式:
全局单例模式,这种模式具有singleTask模式的所有特性,而且具有此模式的Activity只能单独的位于一个栈中
(1)如果要启动的Activity不存在实例,会新创建一个全新的栈,并创建Activity的实例加入到栈中,以后再启动此Activity都不会创建新的实例
修改SecondActivity的启动模式为singleInstance,在MainActivity中启动SecondActivity
不管MainActivity和SecondActivity所属的栈名是否相同,都会创建一个新的栈来放置singleInstance模式的Activity
(2)如果启动的Activity已经存在于某个栈中,会将此栈调入到前台显示
新建另一个工程,将SecondActivity放置到工程中,这样MainActivity和SecondActivity就不属于同一个应用,在MainActivity中启动SecondActivity,点击Home键将SecondActivity置于后台
此时MainActivity所在的任务栈位于前台,SecondActivity所在的任务栈位于后台,在MainActivity中再次启动SecondActivity
并没有再次创建SecondActivity的实例,而是将SecondActivity所属的任务栈调到了前台显示
5、Activity的Flags
可以在AndroidManifest.xml中为Activity设置启动模式,也可以在程序中通过指定Activity的Flags来设置
- FLAG_ACTIVITY_NEW_TASK
为Activity指定“singleTask”启动模式
- FLAG_ACTIVITY_SINGLE_TOP
为Activity指定“singleTop”启动模式
- FLAG_ACTIVITY_CLEAR_TOP
具有此标志位的Activity启动时,位于它上面的Activity都要出栈,根据前面的例子可以得知singleTask模式具有此效果
- FLAG_ACTIVITY_EXCLUED_FROM_RECENT
标记的Activity不会出现在历史列表中,当用户不希望通过历史列表回到Activity时使用这个标记,相当在指定android:excludeFromRecent=”true”
当然,Activity的Flags不仅限于以上几种,只是列出了比较常用的几种,还有其他的标志位,不在此一一总结了
以上就是所了解的关于Activity生命周期及数据保存,还有启动模式的知识内容