这几天一直在看“Android开发艺术探索”这本书,从中学到了一些很不错的知识,这篇博文就当做我的读书笔记吧
Activity是安卓四大组件之一,也是使用最频繁的组件,这里就来记录下在使用Activity的过程中应该注意的一些小细节
一、Activity的生命周期
- "onStart与onResume"、"onPause与onStop"在描述上来看都是类似的,当Activity开始创建调用了onCreate方法后,会依次调用"onStart与onResume",当Activity切换时到另一个界面时,会依次调用"onPause与onStop"
那么,它们之间有什么不同呢?可不可以只保留其中之一呢?
其实,onStart与onResume的差异在于要启动的Activity是否可见,两者都是Activity从创建到显示在前台这之间被调用的,不过onStart方法被调用时Activity还在后台,onResume被调用时表示Activity已经可见并开始活动了
onPause与onStop在用户切换Activity或者打开一个Dialog时调用。当用户直接切换到一个非透明Activity时,onPause与onStop会依次调用,如果下一个Activity是透明主题,或者打开的是一个Dialog(只遮挡了一部分当前Activity的界面)时,只会调用onPause,而不会调用onResume - 当前台Activity A切换到Activity B时,两者之间的回调函数调用顺序是怎样的呢?
首先新建一个MainActivity,重写其各个回调函数,添加一句Log输出语句,类似如下所示
private final String TAG = "MainActivity";
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart被调用");
}
再在主布局文件中添加一个按钮,用于启动第二个Activity——Main2Activity
也重写Main2Activity的各大回调函数,与MainActivity类似
private final String TAG = "第二个Activity";
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart被调用");
}
启动MainActivity,输出语句如下:
com.czy.myapplication E/MainActivity: onCreate被调用
com.czy.myapplication E/MainActivity: onStart被调用
com.czy.myapplication E/MainActivity: onResume被调用
点击按钮切换Activity
com.czy.myapplication E/MainActivity: onPause被调用
com.czy.myapplication E/第二个Activity: onCreate被调用
com.czy.myapplication E/第二个Activity: onStart被调用
com.czy.myapplication E/第二个Activity: onResume被调用
com.czy.myapplication E/MainActivity: onStop被调用
点击back键回退到MainActivity
com.czy.myapplication E/第二个Activity: onPause被调用
com.czy.myapplication E/MainActivity: onRestart被调用
com.czy.myapplication E/MainActivity: onStart被调用
com.czy.myapplication E/MainActivity: onResume被调用
com.czy.myapplication E/第二个Activity: onStop被调用
com.czy.myapplication E/第二个Activity: onDestroy被调用
可以看出来,当启动新的Activity或回退到前一个Activity时,执行顺序是:之前的Activity的onPause方法被调用——要切换到的Activity的onResume方法被调用——之前的Activity的onStop方法被调用
因此,原Activity的onPause不能用于执行耗时操作,否则会影响新的Activity的显示
二、Activity异常情况下的数据保存
- 当Android系统资源相关的配置发生变化(如横竖屏切换)时,默认情况下当前Activity就会被销毁并重新创建
此时Activity是由于异常情况而销毁的,所以系统会调用如下方法来保存当前数据
protected void onSaveInstanceState(Bundle outState)
即我们可以向Bundle中存入需要恢复的数据
当Activity被重新创建后,以下方法会被自动调用
protected void onRestoreInstanceState(Bundle savedInstanceState)
该Bundle就是在onSaveInstanceState中所使用的,我们可以从中取出数据,用于恢复Activity的状态
此外,该Bundle同时也会传递给
protected void onCreate(Bundle savedInstanceState)
因此我们可以选择在两个方法其中之一来恢复数据,一般是在onRestoreInstanceState(Bundle savedInstanceState)当中执行恢复操作比较合理
且onRestoreInstanceState方法只在Bundle不为空时才会调用,而在onCreate方法中还需要再次判断Bundle的值
例如,如果当前Activity中有一个EditText,当屏幕发生旋转时,可以将其文本内容存入Bundle中,当重新创建Activity时再将其从Bundle取出并显示在EditText当中
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
EditText editText = (EditText) findViewById(R.id.editText);
if (!TextUtils.isEmpty(editText.getText())) {
outState.putString("TextContent", editText.getText().toString());
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String content = savedInstanceState.getString("TextContent", "");
EditText editText = (EditText) findViewById(R.id.editText);
editText.setText(content);
}
View本身也包含有该两个方法,某些View的数据保存与恢复操作系统已经自动为我们完成了,这也是我们旋转屏幕重新创建Activity后某些状态依然一样的原因
从Activity第一次创建到旋转屏幕,各回调函数的执行顺序如下所示:
com.czy.myapplication E/MainActivity: onCreate被调用
com.czy.myapplication E/MainActivity: onStart被调用
com.czy.myapplication E/MainActivity: onResume被调用
com.czy.myapplication E/MainActivity: onPause被调用
com.czy.myapplication E/MainActivity: onSaveInstanceState被调用
com.czy.myapplication E/MainActivity: onStop被调用
com.czy.myapplication E/MainActivity: onDestroy被调用
com.czy.myapplication E/MainActivity: onCreate被调用
com.czy.myapplication E/MainActivity: onStart被调用
com.czy.myapplication E/MainActivity: onRestoreInstanceState被调用
com.czy.myapplication E/MainActivity: onResume被调用
2 . 禁止因系统配置改变而重新创建Activity
如果不想在系统配置被改变后而导致Activity重新创建,可以在Activity声明时为之指定configChanges属性
android:configChanges="orientation|screenSize"
这样,当屏幕旋转时Activity就不会重新创建也不会去进行数据保存和数据恢复操作了,此时系统会改为去调用以下方法
public void onConfigurationChanged(Configuration newConfig)
重写之
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e(TAG, "onConfigurationChanged被调用,屏幕方向:" + newConfig.orientation);
}
这样,当多次旋转屏幕时,onSaveInstanceState和onRestoreInstanceState均不会被调用,调用的只是onConfigurationChanged方法,且Activity并没有销毁重建
com.czy.myapplication E/MainActivity: onCreate被调用
com.czy.myapplication E/MainActivity: onStart被调用
com.czy.myapplication E/MainActivity: onResume被调用
com.czy.myapplication E/MainActivity: onConfigurationChanged被调用,屏幕方向:2
com.czy.myapplication E/MainActivity: onConfigurationChanged被调用,屏幕方向:1
com.czy.myapplication E/MainActivity: onConfigurationChanged被调用,屏幕方向:2
com.czy.myapplication E/MainActivity: onConfigurationChanged被调用,屏幕方向:1
三、Activity的启动模式
Android共有四种Activity启动模式,分别为:standard、singleTop、singleTask、singleInstance
Activity位于任务栈当中,栈是一种“后进先出”的结构,当点击back键时,栈顶的Activity会一一出栈,直到栈为空后退出程序
Activity的默认栈名为包名,可以通过taskAffinity属性来设置,该属性必须包含有包名分隔符“.”,而不能仅仅是一个单词
<activity
android:name=".AnotherActivity"
android:launchMode="singleTask"
android:taskAffinity="com.custom.task" />
taskAffinity属性主要和singleTask模式和allowTaskReparenting属性配合使用,在其他情况下没有作用
(1) standard
此为标准模式,即Activity的默认启动模式
在该启动模式下,每启动一个Activity,不管该Activity是否已存在,均会创建一个新的Activity实例,并将该Activity压入启动它的Acivity所在的任务栈的栈顶(singleInstance模式除外),不管该栈的栈名和新启动的Activity在AndroidManifest中指定的taskAffinity属性是否相同
(2) singleTop
此为栈顶复用模式
如果要启动的Activity已位于任务栈的栈顶,则此Activity不会被重新创建
不过,此时该Activity的以下方法会被调用
protected void onNewIntent(Intent intent)
可以通过该intent来传输数据
例如,在启动Activity时,向intent传入文本信息
public void startAnotherActivity(View view) {
Intent intent = new Intent(this, AnotherActivity.class);
Bundle bundle = new Bundle();
bundle.putString("Content", "要传输的内容");
intent.putExtras(bundle);
startActivity(intent);
}
然后在onNewIntent方法中再来获取数据
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e(TAG, "onNewIntent被调用");
Bundle bundle = intent.getExtras();
if (bundle != null) {
String text = bundle.getString("Content", "默认文本");
Toast.makeText(AnotherActivity.this, text, Toast.LENGTH_SHORT).show();
}
}
需要注意的是:如果要启动的Activity并不位于栈顶,则会创建该Activity实例,且onNewIntent方法不会被调用
(3) singleTask
即栈内复用模式
这是一种单实例模式,在该模式下,只要Activity存在于栈中,不管是否位于栈顶,均不会重新创建Activity实例,且系统也会回调其onNewIntent方法
如果该Activity存在但不位于栈顶,则系统会弹出该Activity之上的所有Activity,将该Activity置于栈顶
(4)singleInstance
即单实例模式
该模式与singleTask模式类似,不过该模式下的Activity只能单独地位于一个任务栈中
例如,如果Activity A是singleInstance模式,当A第一次启动时,系统会为之创建一个任务栈,然后将A单独置于该栈中,除非A被销毁了,否则之后每次启动A,都不会重新创建实例
指定启动模式有两种方法:
1.在AndroidManifest中直接为Activity指定启动模式
<activity
android:name=".AnotherActivity"
android:launchMode="singleTask"
android:taskAffinity="com.custom.task" />
2.在Intent中设置标志位Flags来为Activity指定启动模式
Intent intent = new Intent(this, AnotherActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
在优先级上,第二种方式的优先级要高于第一种,如果两者同时存在,则以第二种为准
两者在限定范围上有所差别,第一种方式无法设定“Intent.FLAG_ACTIVITY_CLEAR_TOP”模式,第二种方式无法设定“singleInstance”模式