Activiy的生命周期和启动模式
1. Activty的生命周期全面分析
1.1 典型情况下的生命周期分析
1. onCreate: 表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化的工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。
2. onRestart: 表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前Activity就会暂停,也就是onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况。
3. onStart: 表示Activity正在被启动,即将开始,这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候可以理解为Activity已经显示出来了,但是我们还看不到。
4. onResume: 表示Activity已以可见了,并且出现在前台并开始活动,要注意这个和onStart的对比,onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。
5. onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地再回到当前Activity,那么onResume会被调用。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity才会执行。
6. onStop: 表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。
7. onDestory: 表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。
从整个生命周期来说,onCreate和onDestroy是配对的,分别标识着Activity的创建和销毁,并且只可能有一次调用。从Activity是否可见来说,onStart和onStop是配对的。随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次;从Activity是否在前台来说,onResume和onPause是配对的,随着用户操作或者设备屏幕的点亮和熄灭,这两个方法可能被调用多次。
不能在onPause中做重量级的操作,因为必须onPause执行完成以后新Activity才能Resume。通过分析这个问题,我们知道onPause和onStop都不能执行耗时操作,尤其是onPause,这也意味着,我们应当尽量在onStop中做操作,从而使得新Activity尽快显示出来并切换到前台。
1.2 异常情况下的生命周期分析
Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。下面我们具体分析这两种情况。
情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity.
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "onCreate");
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG,"onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG,"onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG,"onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG,"onStop");
}
@Override
protected void onRestart() {
super.onRestart();
Log.i(TAG,"onRestart");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.i(TAG,"onSaveInstanceState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.i(TAG,"onRestoreInstanceState");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG,"onDestroy");
}
}
当MainActivity启动后旋转屏幕,打印如下Log:
10-22 15:13:54.652 13405-13405/? I/MainActivity: onCreate
10-22 15:13:54.652 13405-13405/? I/MainActivity: onStart
10-22 15:13:54.652 13405-13405/? I/MainActivity: onResume
10-22 15:13:57.182 13405-13405/? I/MainActivity: onPause
10-22 15:13:57.182 13405-13405/? I/MainActivity: onSaveInstanceState
10-22 15:13:57.182 13405-13405/? I/MainActivity: onStop
10-22 15:13:57.182 13405-13405/? I/MainActivity: onDestroy
10-22 15:13:57.232 13405-13405/? I/MainActivity: onCreate
10-22 15:13:57.232 13405-13405/? I/MainActivity: onStart
10-22 15:13:57.242 13405-13405/? I/MainActivity: onRestoreInstanceState
10-22 15:13:57.242 13405-13405/? I/MainActivity: onResume
当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,它和onPause没有既定的时序关系,它即可能在onPause之前调用,也可能在onPause之后调用。需要强调的一点是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。
情况2:资源内存不足导致低优先级的Activity被杀死
Activity按照优先级从高到低,可以分为如下三种: 1. 前台Activity——正在和用户交互的Activity,优先级最高。 2. 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。 3. 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,系统会按照上述优先级去杀死目标Activity所在的进程,并后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。
为了避免系统配置发生改变后Activity重建,可以给Activity指定configChanges属性。比如不想让Activity在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation这个值,如下所示。 android:configChanges="orientation"
项目 | 含义 |
---|---|
mcc | SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460.此项标识mmc代码发生了改变 |
mnc | SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03.此项标识mnc发生改变 |
locale | 设备的本地位置发生了改变,一般指切换了系统语言 |
touchscreen | 触摸屏发生了改变,这个很费解,正常情况下无法发生,可以忽略它 |
keyboard | 键盘类型发生了改变,比如用户使用了外插键盘 |
keyboardHidden | 键盘的可访问性发生了改变,比如用户调出了键盘 |
navigation | 系统导航方式发生了改变,比如采用了轨迹球导航,这个有点费解,很难发生,可以忽略它 |
screenLayout | 屏幕布局发生了改变,很可能是用户激活了另外一个显示设备 |
fontScale | 系统字体缩放比例发生了改变,比如用户选择了一个新的字号 |
uiMode | 用户界面模式发生了改变,比如用户开启夜间模式 |
orientation | 屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕 |
screenSize | 当屏幕尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它和编译选项有关,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加) |
smallestScreenSize | 设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没有关系,仅仅表示在实际物理屏幕的尺寸改变的时候发生,比如用户切换到了外部显示设备,这个选项和screenSize一样,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加) |
layoutDirection | 当部局方向发生变化时,这个属性用的比较少,正常情况下无须修改部局的layoutDirection属性(API17新添加) |
2.Activity的启动模式
2.1Activity的LaunchMode
目前有四种启动模式:standard,singleTop,singleTask,singleInstance.
1. standard: 标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity的生命周期。这是一种典型的多实例实现,一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如Activity A 启动了Activity B(B是标准模式),那么B就会进入到A所在的任务栈中。
2. singleTop:栈顶利用模式。在这种情况下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果新Activity的实例已存在但不是位于栈顶,那么新Activity仍然会重新重建。举个例子,假设目前栈内的情况为ABCD,其中ABCD为四个Activity,A位于栈底,D位于栈顶,这个时候假设再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD。
3. singleTask:栈内复用模式。这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent方法。具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需的任务栈,这时要看A是否在栈中有实例存在,如果有实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并把A压入栈中。
- 比如目前任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其入栈到S2。
- 另外一种情况 ,假设D所需的任务栈为S1,其他情况如上面例子1所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1.
- 如果D所需的任务栈为S1,并且当前任务栈S1的情况为ABCD,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。
4. singleInstancd:单实例模式。这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中,换句话说,比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。
Activity的Flags
- FLAG_ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定singleTask启动模式,其效果和在XML中指定该启动模式相同。 - FLAG_ACTIVITY_SINGLE_TOP
这个标记位的作用是为Activity指定singleTop启动模式,其效果和在XML中指定该启动模式相同。 - FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般需要和FLAG_ACTIVITY_NEW_TASK配合使用,在这种情况下,被启动的Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。 - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记位的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们的Activity的时候这个标记比较有用。它等同于在XML中指定Activity的属性android:excludeFromRecents=”true”。
3.IntentFilter的匹配规则
- action的匹配规则
action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里说的匹配是指action的字符串完全一样。一个过滤中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。action区分大小写。 - category的匹配规则
category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。换句话说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义了的category。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。这里要注意下它和action匹配过程不同,action是要求Intent中必须有一个action且必须能够和过滤规则中的某个action相同,而catetory要求Intent可以没有category,但是如果你一旦有catetory,不管有几个,每个都要能够和过滤规则中的任何一个catetory相同。为什么不设置category也可以匹配呢?原因是系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上”android.intent.category.DEFAULT”这个category,所以这个category就可以匹配匹配前面的过滤规则中的第三个category。同时,为了我们的activity能够接收隐式调用,就必须在intent-filter中指定”android.intent.category.DEFAULT”这个category。 - data的匹配规则
data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。
data的语法如下所示。
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
data由两部分组成,mimeType和URI。mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同的媒体格式,而URI中包含的数据就比较多了,下面是URI的结构
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
几个实例,如下所示
content://com.example.project:200/folder/subfolder/etc
http://www.baidu.com:80/search/info
下面介绍一下每个数据的含义。
Scheme: URI的模式,比如http、file、content等。如果URI中没有指定scheme,那么整个URI的其它参数无效,这也意味着URI是无效的。
Host: URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI是无效的。
Port: URI中的端口号,比如80,仅当URI中指定了scheme和host参数的时候port参数才是有意义的。
Path、pathPattern和pathPrefix: 这三个参数表述路径信息,其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“*”,“*”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“*”要写成“\\*”,”\”要写成”\\\\”;pathPrefix表示路径的前缀信息。
分情况说明data的匹配规则
1. 如下过滤规则:
<intent-filter>
<data android:mimeType="image/*"/>
</intent-filter>
这种规则指定了媒体类型为所有类型的图片,那么Intent中的mimeType属性必须为“image/*”才能匹配,这种情况下虽然过滤规则没有指定URI,但是却有默认值,URI的默认值为content和file。也就是说,虽然没有指定URI,但是Intent中的URI部分的schema必须为content或者file才能匹配,这点是需要注意的。为了匹配1中的规则,我们可以写出如下示例:
intent.setDataAndType(Uri.parse("file://abc"),"image/png")
另外,如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值,比如setData:
public Intent setData(Uri data) {
mData = data;
mType = null;
return this;
}
- 如下过滤规则:
<intent-filter>
<data android:mimeType="video/mpeg" android:scheme="http" ... />
<data android:mimeType="audio/mpeg" android:scheme="http" ... />
</intent-filter>
这种规则指定了两组data规则,且每个data都指定了完整的属性值,即有URI又有mimeType。为了匹配2中规则,我们可以写出如下示例:
intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg")
或者
intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg")