Activity的生命周期/启动模式/过滤规则
文章目录
1.1 Activity 的生命周期
1.1.1 典型情况下的生命周期分析
onCreate:该阶段可以做一些初始化工作:加载布局资源(setContentView)、初始化所需数据等。
onRestart:Activity重新启动。
onStart:Activity正在启动,即将开始。Activity已经显示了,但是我们还看不到,不能交互。
onResume:Activity可见,能交互。
onPause:Activity正在停止。可以做一些不太耗时的操作:存储数据、停止动画等。(特殊情况下快速切换会从onPause->onResume,几乎不能重现。)
onStop:Activity即将停止。可以做一些稍微重量级的回收,但不能太耗时。
onDestroy:Activity即将销毁。做回收工作和最终的资源释放。
Activity生命周期说明:
- 特定Activity第一次启动:onCreate->onStart->onResume。
- 用户打开新Activity或返回桌面:onPause->onStop。若新Activity采用透明主题,当前Activity不会回调onStop。
- 用户再次回到原Activity时:onRestart->onStart->onResume。
- 用户back键回退:onPause->onStop->onDestroy。
(onStart和onStop)与(onResume和onPause)的区别:
- onStart和onStop是从Activity是否可见这个角度来回调的。
- onResume和onPause是从Activity是都位于前台(交互与否)这个角度来回调的。
当前Activity为A,新Activity为B;发生的生命周期调用顺序:
- A:onPause->B:onCreate->B:onStart->B:onResume->A:onStop
- 所以不能在onPause里做耗时操作。应使新Activity尽快到前台。
1.1.2 异常情况下的生命周期分析
注意:1.1.2部分在新版本(测试API>28)时会有一定差异,差异参考1.1.3部分补充内容。
1.资源相关的系统配置发生改变导致Activity被杀死并重新创建:(手机横竖屏切换)
- onSaveInstanceState调用是在onStop之前,与onPause无时序关系。Activity周期正常的情况下不会调用该回调。onRestoreInstanceState调用是在onStart之后。
- Activity重建是,系统会调用onRestoreInstanceState,将Activity销毁时onSaveInstanceState方法保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。(可通过onRestoreInstanceState和onCreate方法判断Activity是否被重建。)
2.内存不足导致低优先级的Activity被杀死:
优先级:前台Activity>可见的非前台Activity>后台Activity。
当系统内存不足时,系统会按照优先级从低到高杀死进程,后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果进程中无四大组件会很快被系统杀死,所以后台程序不适合脱离四大组件独自运行在后台,最好的方式是放在Service中,从而保证进程有一定的优先级,不会轻易被系统杀死。
如果不希望Activity在屏幕横竖切换是被重建,可以指定configChange属性:
<!--可以使用'|'符号连接--> android:configChanges="orientation|screenSize"
注意:不会被重建不是被销毁;是不在通过保存状态->销毁->恢复状态这一流程;上述例子可能会导致不会根据屏幕横竖屏切换加载适合的资源文件(可能横屏竖时采用的是不同资源,hdpi<->xhdpi)。不重建情况下,系统会调用onConfigurationChanged方法。
configChanges项目及含义:
项目 含义 mcc SIM卡唯一标识IMSI ( 国际移动用户识别码)中的国家代码,由三位数字组成,中国为460。 mnc SIM卡唯-标识IMSI (国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。 locale 设备的本地位置发生了改变,一般指切换了系统语言 keyboard 键盘类型发生了改变,比如用户使用了外插键盘 touchscreen 触摸屏发生了改变。 keyboardHidden 键盘的可访问性发生了改变,比如用户调出了键盘。 navigation 系统导航方式发生了改变,比如采用了轨迹球导航。 screenLayout 屏幕布局发生了改变,很可能是用户激活了另外一个显示设备。 fontScale 系统字体缩放比例发生了改变,比如用户选择了一个新字号。 uiMode 用户界面模式发生了改变,比如是否开启了夜间模式。 orientation 屏幕方向发生了改变,比如旋转了手机屏幕。 screenSize 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化。当API大于13时会导致Activity重启。 smallestScreenSize 设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,比如用户切换到了外部的显示设备。当API大于13时会导致Activity重启。 layoutDirection 当布局方向发生变化。 常用locale、orientation、keyboardHidden。注意screenSize和smallestScreenSize,这两个项目与编译有关,与运行环境无关,当API大于13时会导致Activity重启,所以在例子中也要连接上它。
1.1.3 关于新版情况下的onSaveInstanceState补充
新版本(测试环境API>28)onSaveInstanceState被调用的情况分析:
首先onSaveInstanceState的调用的时间有所改变:改为onStop之后调用。(旧版本在onStop之前调用)
onSaveInstanceState的触发情况有所改变:在Activity有可能被恢复的情况下调用。
情况一:Activity A启动Activity B,那么A会在调用onStop后调用onSaveInstanceState;之后销毁B返回到A时:
- 如果A因内存不足销毁了就会调用onRestoreInstanceState重建A。
- 如果A没有被销毁就不调用onRestoreInstanceState。
情况二:Activity A启动Activity B,之后销毁B返回到A时,B调用onStop后不会调用onSaveInstanceState,这是因为销毁B的行为表示B不会被恢复。
情况三:对情况一的补充。如果Activity A启动Activity B,如果A仍在页面上有显示(透明模式的情况),那么A不会调用onSaveInstanceState。
新版旧版onRestoreInstanceState对比:
1:在 资源相关的系统配置发生改变导致Activity被杀死并重新创建 情况下并无明显差异。调用周期依然为:onResume->onPause->onStop->onSaveInstanceState->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume。这个不同于Activity A启动Activity B的生命周期,A调用onPause后,B开始onCreate,B在onResume后,A开始onStop。
2:在 内存不足导致低优先级的Activity被杀死 情况下有所差异。
- 新版本调用onSaveInstanceState的根据是Activity有没有被恢复的可能,如果有可能被恢复就会在调用onStop后直接调用onSaveInstanceState。并且,在A被恢复时,会跟据A是否被销毁选择性调用onRestoreInstanceState。
- 旧版本是在Activity在内存不足情况下,因优先级低要被销毁时,调用onSaveInstanceState。
1.2 启动模式
1.2.1 四种启动模式
1.standard:标准模式。该模式下,谁启动新Activity,那么新Activity就运行在启动它的那个Activity所在的栈中。
可能在低版本情况下可能会出现(Android9以上不会):
使用ApplicationContext启动standard模式的Activity时会报错;因为非Activity类型的Context并无任务栈,需要添加FLAG_ACTIVITY_NEW_TASK标记位。
2.singleTop:栈顶复用模式。会调用onNewIntent回调,通过该回调可以获得再请求当前Activity的信息。
3.singleTask:栈内复用模式。
- 系统会先寻找Activity A想要的任务栈,如果栈不存在,创建栈->创建实例A->压入栈。
- 如果栈存在,在栈中寻找是否存在A的实例,不存在,创建实例A->压入栈;存在,将A调到栈顶并调用onNewIntent回调。
- singleTask具有clearTop效果,会将栈内所有在A上的实例出栈。
- 栈顶为A,再启动A时会经历onPause->onNewIntent->onResume。
4.singleInstance:单实例模式。
1.2.2 TaskAffinity
TaskAffinity:任务相关性,标识一个Activity所需要的任务栈的名字。
默认情况下,所有Activity所需的任务栈的名字为应用的包名。
我们可以为每个Activity都单独指定TaskAffinity属性,要与包名不一样。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,其他情况无意义。任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
当TaskAffinity和singleTask启动模式配对使用时,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。
当TaskAffinity和allowTaskReparenting结合使用的时候,比如应用A启动应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。
1.2.3 指定启动模式
- 方式一:在Menifest文件中指定,
android:launchMode="singleTask"
- 方式二:在Intent中设置标志位,
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- 优先级上,方式二>方式一。限定范围有所不同:方式一无法设定FLAG_ACTIVITY_CLEAR_TOP标识;方式二无法指定singleInstance模式。
1.2.4 Activity的标识
- FLAG_ACTIVITY_NEW_TASK:当启动该标识的Activity A时,先查找是否存在A想要的任务栈(TaskAffinity判断),如果有,将这个栈移动到前台,并保持栈中的Activity原有顺序,然后创建实例A->压入栈;如果没有,创建栈->创建实例A->压入栈。
- FLAG_ACTIVITY_SINGLE_TOP:指定singleTop启动模式。
- FLAG_ACTIVITY_CLEAR_TOP:具有此标记的Activity启动时,同一个任务栈中所有位于它上面的Activity都要出栈。通常要配合FLAG_ACTIVITY_NEW_TASK使用。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的Activity不会出现在历史Activity的列表中,如果我们不希望用户通过历史列表回到我们的Activity时使用。等同于Menifest指定:
android:excluedeFromRecents="true"
。
1.3 IntentFilter 的过滤规则(intent-filter)
- 启动Activity时隐式调用需要Intent匹配目标组件的IntentFilter中的过滤信息,过滤信息有action、category、data。每种过滤信息可包含多个,当action、category、data完全匹配时才启动Activity。
- 一个Activity可以有多个intent-filter,匹配任意一个即可启动Activity。
- 补充:原则上一个Activity不应该既是隐式启动又是显式启动,但两种启动方式并存时,以显式启动为主。
1.3.1action的匹配规则
- action是一个字符串,系统预定义了一部分,也可自定义。action区分大小写。
- 一个过滤规则中可以有多个action,只要Intent中的action能够匹配过滤规则中的任何一个即视为匹配成功。
- 通过
intent.setAction("String")
方法设置Intent的action。- Intent中必须有action。
1.3.2 category的匹配规则
- category是一个字符串,系统预定义了一部分,也可自定义。category区分大小写。
- Intent中如果有category,那么Intent中的所有category都必须和过滤规则中的某个或某几个category匹配。
- Intent中如果没有category,视为通过category匹配。系统调用startAcivity或startActivityForResult时会默认为Intent加上
"android.intent.category.DEFAULT"
这个category,而我们设置intent-filter时必须指定"android.intent.category.DEFAULT"
这个category。- 通过
intent.addCategory("String")
方法设置Intent的category。
1.3.3 data的匹配规则
与action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。
data在过滤规则中存在两种写法:
写法一:
<intent-filter ...> <android:scheme="file" android:host="www.baidu.com" /> ... </intent-filter>
写法二:
<intent-filter ...> <android:scheme="file" /> <android:host="www.baidu.com" /> ... </intent-filter>
补充:scheme默认值为
content
和file
。通过
intent.setDataAndType(Uri.parse("URI"),"mimeType")
方法设置Intent的data。
- 注意:
intent.setData(Uri.parse("URI"))
和intent.setType("mimeType")
方法会互相覆盖(源码中会将另一个属性设为null),如果要为Intent指定完整的data,要调用intent.setDataAndType(Uri.parse("URI"),"mimeType")
方法。补充: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:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
。Scheme:URI的模式,比如
http
、file
、content
等。如果URI没有指定scheme,那么URI无效。Host:URI的主机名,比如
www.baidu.com
。如果URI没有指定host,那么URI无效。Port:URI的端口号,比如
80
。URI指定scheme和host时,port才有意义。Path、pathPattern、pathPrefix:路径信息。path表示完整的路径信息;pathPattern表示完整的路径信息,但是可以包含通配符
*
;pathPrefix表示路径的前缀信息。Intent-filter的匹配规则对于Service和BroadcastReceiver相同,不过对Service的建议是尽量使用显示方式启动服务。
1.3.4 判断隐式启动Activity是否匹配成功
方法一:PackageManager的resolveActivity方法:
/** * 1.MATCH_DEFAULT_ONLY标识位表示匹配在intent-filter中声明了<category android:name="android.intent.category.DEFAULT" />的Activity。 * 2.如果不使用该标识位,会返回不含DEFAULT的那些Activity,这些Activity不支持隐式启动,从而导致startActivity或startActivityForResult方法失败。 */ if(null!=getPackageManager().resolveActivity(intent,PackageManager.MATCH_DEFAULT_ONLY)){ try { startActivity(intent); }catch (ActivityNotFoundException e){ e.printStackTrace(); } }
- 补充:PackageManager还提供queryIntentActivities方法,该方法会返回所有匹配成功的Activity的信息;而resolveActivityf方法返回最佳匹配成功的Activity的信息。
方法二:Intent的resolveActivity方法:
if(null!=intent.resolveActivity(getPackageManager())){ try { startActivity(intent); }catch (ActivityNotFoundException e){ e.printStackTrace(); } }
对于Service和BroadcastReceiver,PackageManager提供类似的方法去获取成功匹配的组件信息。
1.3.5 其他
有一类重要的action和category:
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
二者共同表明这是一个入口Activity(二者缺一不可),并且会出现在系统的应用列表中。
1.4 参考资料
- Android 开发艺术探索