一. Activity的生命周期
1. 正常情况
当用户打开新的Activity或者切换到桌面时,回调为:onPause > onStop。 假如新的Activity为采用透明主题时,则当前Activity不会回调onStop。
当用户按back回退时,回调为:onPause > onStop > onDestroy。
不能在onPause中做重量级的操作,因为onPause执行完成后新Activity才能启动。
2. 异常情况
转屏
默认情况下,Activity会被销毁然后重建。生命周期为:onPause > onSaveInstanceState(该方法在onStop之前调用,与onPause没有确定的时序关系,只会在Activity被异常终止即将被销毁并且有机会重新显示的情况下调用,正常状态不会调用 ) > onStop > onDestroy > onCreate > onStart > onRestoreInstanceState>onResume资源内存不足导致优先级低的Activity被kill
该情况的数据存储和恢复过程与上面情况一致。
当系统配置发生改变后,Activity会被重建,系统配置中有很多内容,当某项内容发生改变后,可以通过给Activity指定configChanges属性,不让系统重建Activity。具体属性如下:
mcc :The IMSI mobile country code (MCC) has changed — a SIM has been detected and updated the MCC. (IMSI-国际移动用户识别码中 国家代码发生改变,该代码由三位数组成,中国为460)
mnc :The IMSI mobile network code (MNC) has changed — a SIM has been detected and updated the MCC.(IMSI中运营商代码,由两位数组成)
locale:The locale has changed — the user has selected a new language that text should be displayed in.(本地设置改变,一般指切换了系统语言)
touchscreen:The touchscreen has changed. (This should never normally happen.)
keyboard:The keyboard type has changed — for example, the user has plugged in an external keyboard. (键盘类型发生改变,例如,用户使用了外部键盘)
keyboardHidden:The keyboard accessibility has changed — for example, the user has revealed the hardware keyboard.)(键盘的可访问性发生改变,例如,用户调出了键盘)
navigation:The navigation type (trackball/dpad) has changed. (This should never normally happen.) (导航发生改变,这通常不应该发生。举例:连接蓝牙键盘,连接后确实导致了navigation的类型发生变化。因为连接蓝牙键盘后,我可以使用方向键来navigate了)
screenLayout:The screen layout has changed — this might be caused by a different display being activated. (屏幕的布局发生改变,这可能导致激活了另一个显示设备)
fontScale:The font scaling factor has changed — the user has selected a new global font size.
orientation:The screen orientation has changed — that is, the user has rotated the device. (设备旋转,横向显示和竖向显示模式切换。)
screenSize: 屏幕大小改变了,转屏也会改变,当minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启
smallestScreenSize: 屏幕的物理大小改变了,如:连接到一个外部的屏幕上,当minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启
layoutDirection(4.2新增):当改变语言设置后,该属性也会成newConfig中的一个mask位。所以ActivityManagerService(实际在ActivityStack)在决定是否重启Activity的时候总是判断为重启。需要在android:configChanges 中同时添加locale和layoutDirection。
如:android:configChangeds="orientation | keyboardHidden | screenSize" 添加在AndroidManifest.xml的Activity的声明里。Activity不会被重建,也没有调用onSaveInstanceState和onRestoreInstanceState方法,系统调用了Activity的onConfigurationChanged方法。
二. Activity的启动方式
默认情况下,当我们多次启动同一个activity时,系统会创建多个实例并一一放入任务栈中。任务栈的工作原理是后进先出。
什么是任务栈:
task是存在于framework层的一个概念,用于控制界面的跳转和返回。这个task存在于一个称为back stack的数据结构中,所以framework是以栈的形式管理用户开启的activity。
当app中存在A,B,C三个Activity,点击图标启动主Activity A,接着A启动B,B启动C,这是栈中有三个Activity,并且这三个Activity默认在同一个task中,当一直按返回键时,C,B,A相继被弹出,task被移除。
task是可以跨应用的,也可以跨进程。有的Activity虽然不在同一个app中,但为了保持用户操作的连贯性,可以把它们放在同一个task中。比如某个应用中Activity A点击发送短信,会启动短信app的一个Activity B来发送邮件,此时AB是在同一个task中的, 当发送完短信后,按back键返回,可以返回到Activity A中。
任务栈分为前台和后台任务栈,后台任务栈中的Activity处于暂停状态,用户可以通过切换讲后台任务栈再次调到前台。
TaskAffinity:
可以理解为任务相关性,通过设置该属性可以标识一个Activity所需要的任务栈名字。
当Activity以FLAG_ACTIVITY_NEW_TASK标志启动时,它会被分配到相同的TaskAffinity的任务栈中。
默认情况下,所有Activity所需的任务栈名字应为应用的包名。也可以为每个Activity单独指定TaskAffinity属性,这个属性值不能和包名相同。
为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。
假如Activity A未设置taskAffinity,在Activity A中以singleTask方式启动Activity B,但Activity B未设置不同的taskAffinity,Activity A跟Activity B会在同一个任务栈中。因为Activity B所需的任务栈已经在启动Activity A时创建,所以不需要重新创建。
当跟allowTaskReparenting一起使用时,会产生特殊的效果。当应用A启动了应用B的某个Activity后,如果这个activity的allowTaskReparenting设为true,当应用B启动后,该activity会直接从应用A的任务栈转移到应用B的任务栈中。举个例子,在电子邮件中打开一个web网页,按home键回到主页界面后,再打开浏览器,就能看到之前的web网页。
(allowTaskReparenting的主要作用是activity的迁移,即从一个task迁移到另一个task,这个迁移跟activity的taskAffinity有关,必须是从一个跟该activity taskAffinity不同的task中迁移到跟它taskAffinity相同的task中,该属性在AndroidManifest中设置)
四种启动模式(launchMode):
standard:标准模式即默认模式。每次启动一个activity都会重新创建一个实例,不管该实例是否存在。谁启动了这个Activity,这个Activity就运行在启动它的那个Activity的任务栈中。假如用Application Context去启动standard模式的activity,则会报错,因为非Activity类型的context(如Application context)并没有所谓的任务栈。
singleTop:栈顶复用模式。该模式下,如果新Activity已经位于任务栈的栈顶,那么该Activity不会被重新创建,同时它的onNewIntent()方法会被回调,通过此方法的参数可以取出当前请求的信息。该Activity的onCreate和onStart不会被调用,因为它并没有发生改变。适用界面如搜索界面。
singleTask:栈内复用模式。这是一种单实例模式。该模式下,只要Activity在一个栈中存在,那多次启动该Activity都不会重新创建实例,跟singleTop一样,系统也会回到onNewIntent()方法。
eg:
1.任务栈S1中的情况为ABC,Activity D以singleTask模式请求启动,所需任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例将其入栈到S2。
2.假如D所需的任务栈为S1,其它如1,则系统会直接创建D的实例并入栈到S1,S1的则变成ABCD。
3.如果D所需的任务栈为S1,S1为ADBC,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent()方法,同时singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,所以S1变为AD。singleInstance:单实例模式。具有此种模式的Activity只能单独得位于一个任务栈中。如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中。比如闹钟响起后弹出的提示界面。
还有一种情况,假设现在有2个任务栈,前台任务栈的情况为AB,后台任务栈的情况为CD,且CD的启动模式均为singleTask。现在请求启动D,则整个后台任务栈都会被切换到前台,此时整个后退列表变成了ABCD,当用户按back键时,ABCD会依次出栈,假如请求启动C,则整个后退列表变成ABC。
Flags
FLAG_ACTIVITY_NEW_TASK
当Intent对象包含这个标记时,系统会寻找或创建一个新的task来放置目标Activity,寻找时依据目标Activity的taskAffinity属性进行匹配,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如果查找无果,则创建一个新的task,并将该task的taskAffinity设置为目标Activity的taskActivity,将目标Activity放置于此task。效果不完全等同于设置“singleTask”。singleTask启动模式默认具有该效果。FLAG_ACTIVITY_SINGLE_TOP
效果同在xml中设置Activity启动模式为singleTop。FLAG_ACTIVITY_CLEAR_TOP
具有此标志位的Activity,当它启动时,在同一个任务栈中所有位于它上面的activity都要出栈。一般需要和FLAG_ACTIVITY_NEW_TASK配合使用。singleTask启动模式默认具有该效果。FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有该标记的Activity不会出现在历史Activity的列表中。等同于Activity属性android:excludeFromRecents="true"。
三. Activity的调用方式
Activity的启动分为显式调用和隐式调用两种。
显式调用
直接指定需要打开的activity对应的类,明确指定被启动对象的组件信息,包括包名和类名。
- 构造方法传入Component
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
- setComponent方法
ComponentName componentName = new ComponentName(this, SecondActivity.class);
Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);
- setClass/setClassName方法
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
startActivity(intent);
隐式调用
需要Intent能匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity,会抛出ActivityNotFoundException,可以使用try catch,当然最好的提前判断,可以使用PackageManager的resolveActivity方法或Intent的resolveActivity方法,如果找不到匹配的Activity就会返回null,通过判断返回值就可以避免异常的出现。
Intent intent = new Intent(Intent.ACTION_XXX);
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null) {
startActivity(intent);
}
只有一个Intent同时匹配action(动作)、category(类别)、data(数据)才算完全匹配。
一个Activity中可以有多个intent-filter,一个intent只要能匹配任一组intent-filter就能启动对应的Activity。
- action的匹配规则
intent中action必须能够和过滤规则中的任一个action相同(字符串值一样,区分大小写)即匹配成功。
若intent中缺少action,则不会匹配。
- category的匹配规则
intent中如果有category,那么所有的category必须和过滤规则中的其中一个category相同。
若intent中没有category,intent也可能匹配成功。因为系统在调用startActivity或startActivityForResult时,会默认为intent加上“android.intent.category.DEFAULT”,此时必须在intent-filter中指定category为“android.intent.category.DEFAULT”
- data的匹配规则
data由两部分组成,mimeType和URI。
mimeType:指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,表示图片,文本,视频等不同的媒体格式。
URI:由三部分组成。scheme://host:post/path ( 模式://主机:端口/路径 )
如果intent-filter中只有mimeType,那intent中的mimeType必须一致才能匹配。虽然过滤规则没有指定URI,但URI的默认值为content和file,所以intent中URI部分的scheme必须为content或file才能匹配。
如果intent-filter有两组data规则,那只要intent的data满足其中某一组就可以匹配成功。
注:为intent指定完整的data时,必须调用setDataAndType方法,不能先调用setData再调用setType,反之也不行,因为这个两个方法会相互消除对方的值。
内容参考:《Android开发艺术探索》