1.1 Activity的生命周期全面分析
1.1.1 典型情况下的生命周期分析
(1)onCreate 创建Activity,可做初始化工作如调用setContentView加载界面布局,初始化所需数据。
(2)onRestart 重新启动Activity 。
(3)onStart 正在启动Activity,已经可见了,但是没有出现在前台,不可交互。
(4)onResume 已经可见Activity,且出现在前台开始活动,可以交互。
(5)onPause 正在停止Activity。可以做一些存储数据,停止动画等工作,不能耗时,会影响到新的Activity的显示,旧Activity的onPause必须先执行完,新的Activity的onResume才会执行。
(6)onStop 即将停止。可以做稍微重点的回收工作,同样不能太耗时。
(7)onDestory 即将销毁。做一些回收工作和最终的资源释放。
针对上图说明:
(1)针对一个特定Activity,第一次启动回调:onCreate->onStart->onResume
(2)当用户打开新的Activity或切换到桌面:onPause->onStop。特殊情况:新的Activity采用了透明主题,那么前Activity不会回调onStop。
(3)再次回到原Activity:onRestart->onStart->onResume
(4)当用户按Back键回退:onPause->onStop->onDestory
特别说明:
onStart和onStop、onPause和onResume有什么不同?前者:是否可见的角度,后者:是否位于前台,可交互
1.1.2 异常情况下的生命周期分析
1 资源相关的系统配置发生改变导致Activity被杀死并重新创建
系统会调用onSaveInstanceState来保存当前Activity的状态(在onStop之前,和onPause没有既定的时序关系);当这个Activity重新创建后,系统会调用onRestoreInstanceState方法并把销毁前保存的信息传回, (onRestoreInstanceState的调用时机在onStart之后)。仅异常情况会调用到onSaveInstanceState和onRestoreInstanceState。
我们通过onCreate和onRestoreInstanceState的Bundle都可以取出数据并恢复,但onCreate需判断Bundle是否为null,与正常启动情况区分。
异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,并在Activity重启或为我们恢复,如文本框中输入的数据、ListView滚动的位置等。
关于保存和恢复View的层次结构,系统的工作流程:首先Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window会委托它上面的顶级容器去保存数据,顶层容器一般是DecorView(ViewGroup);最后顶层容器再去一一通知它的子元素保存数据,这样整个数据的保存过程就完成了。
2 资源内存不足导致低优先级的Activity被杀死
数据存储和恢复过程与情况1一致。Activity的优先级从高到低:
(1)前台Activity——正在和用户交互优先级最高;
(2)可见但非前台的Activity ——比如Activity中弹了一个对话框,导致Activty可见但位于后台,无法交互;
(3)后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,系统会按照上述优先级杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件在执行,这个进程很快被系统杀死,因此一些后台工作不适合脱离四大组件独自运行在后台,这样进程很容易被杀死,较好的方法是将后台工作放入service从而保证进程有一定优先级,不会轻易被系统杀死。
*当系统配置发生变化时,如何防止Activity被重新创建?
给Activity制定configChanges属性。如不想让Activity在屏幕旋转时冲洗你创建,就可以给configChanges属性添加orientation这个值,设置后,系统不会调用onSaveInstanceState和onRestoreInstanceState,而是会调用onConfigurationChanged方法,我们可以在这里做特殊处理。
android:configChanges="orientation"
除此之外的配置项还有很多,如locale、keyboardHidden等。
1.2 Activity的启动模式
1.2.1 Activity的LaunchMode
任务栈是一个“后进先出”的栈结构,每次单击back键,处于前台的Activity就会出栈,直到栈为空为止,当栈中无任何Activity的时候,系统就会回收这个任务栈。
(1)Standard 标准模式,默认启动模式。
简单的说:就是每次启动新的activity,都会重新创建一个新的activity实例,并在栈中处于栈顶位置,谁启动了这个Activity,这个Activity就在谁的栈里。
当我们用ApplicationContext去启动standard模式的Activity时,报错:“requires the FLAG_ACTIVITY_NEW_TASK flag.” 由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,需为待启动Activity制定FLAG_ACTIVITY_NEW_TASK 标记位,启动时为它创建一个创新的任务栈,即它实际是以singleTask模式启动的。
(2)singleTop 栈顶复用模式。
如果新的Activity已经位于栈顶,那么此Activity不会创建,同时他的onNewIntent方法会被回调,onCreate、onStart不会被调用。如果新的Activity的实例已存在但不是位于栈顶,那么新的Activty依然会重新创建。
(3)singleTask 栈内复用模式。
这是一种单实例模式,Activity在一个栈中存在,那么多次启动该Activity都不会重新创建实例,和sinleTop一样,系统会回调其onNewIntent。如果启动的Activity没有所需要的任务栈,就会先创建任务栈再创建Activity。singleTask默认具有clearTop的效果,具有该模式的Activity会让其之上的Activity全部出栈。
(4)singleInstance 单实例模式。
这是一种加强的singleTask模式,除了具备singleTask的特性之外,具有该模式的Activity只能单独位于一个任务栈中:比如Activity A是singleInstance模式的,当A启动后,系统会为它创建一个新的任务栈,后续的启动均不会创建新的Activity,除非这个任务栈被系统销毁了。
一种情况:
若目前有两个任务栈,前台任务栈的情况为AB,后台任务栈为CD,假设CD的启动模式均为singleTask。现在请求启动D,那么整个后台任务栈都被切换到前台,整个后退列表变为ABCD。
如果请求的是C,情况会不一样。
任务栈:
参数TaskAffinity用于指定Activity栈,TaskAffinity属性经常和singleTask启动模式或allowTaskReparenting属性配对使用,其他情况没有意义。当应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。
如何指定启动模式
通过AndroidMenifest
<activity android:name=".SecondActivity" android:launchMode="singleTask" />
通过Intent设置标志位
Intent intent = new Intent(this,ThirdActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
两种方式有区别。第二种方式的优先级高于第一种。第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,而第二种方式无法为Activity指定singleInstance模式。
1.2.2 Activity的Flags
FLAG_ACTIVITY_NEW_TASK
为Activity指定“singleTask”启动模式,其效果和xml中指定该启动模式相同。
FLAG_ACTIVITY_SINGLE_TOP
为Activity指定“singleTop”启动模式,其效果和xml中指定该启动模式相同。
FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当他启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。这个模式一般与FLAG_ACTIVITY_NEW_TASK配合使用,singleTask启动模式默认具有此标记为的效果。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记为的Activity不会出现在历史Activity的列表中,当某些情况不希望用户通过历史列表回到我们Activity的时候这边标记比较有用。等同于在XML中指定Activity的属性android:excludeFromRecents="true"。
1.3 IntentFilter的匹配规则
启动Activity分为两种,显式调用和隐式调用。IntentFilter的过滤信息有action、category、data。为了匹配过滤列表,需同时匹配过滤列表中的action、category、data信息,否则匹配失败;一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。
1.action的匹配规则
action的匹配要求Intent中action存在且必须和过滤规则中的其中一个相同。
一个过滤规则中可以有多个action,Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。如果Intent没有指定action,那么匹配失败。action区分大小写。
category的匹配规则
Intent中可以没有category;如果有,不管有几个,每个都要能和过滤规则中的任何一个category相同;如果没有,依然可以匹配成功,原因是因为如果没有指定category,在调用startActivity或startActivityForResult时系统会默认加上“android.intent.category.DEFAULT”这个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中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data;这里的完全匹配是指过滤规则中出现的data部分也出现在了Intent中的data中。
(2)若没有指定URI,URI的默认值为content和file。也就是说,虽然没有指定URI,但Intent中的URI部分的schema必须为content或者file才能匹配。
(3)如果要为Intent指定完整的data,必须调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法都会清除对方的值置为null。
其他说明
(1)Intent-filter的匹配规则对于Service和BroadcastReceiver也是同样道理。系统建议Service尽量使用显示调用来启动服务。
(2)在intent-filter中声明了<category android:name="android.intent.category.DEFAULT"/>这个category的Activity,才可以接收隐式意图。
(3)当我们隐式启动一个Activity的时候,可以做一下判断,看是否能匹配到我们的隐式Intent,如果不做判断没找到对应的Activity系统就会抛出异常:采用PackageManager的resolveActivity方法或者Intent的resolveActivity方法,如果找不到匹配的Activity就会返回null,我们通过判断返回值就可以规避上述错误了。
(4)特别的,有一类action和category的共同作用是表明此为入口Activity,并且会出现在系统的应用列表中,少一个都没有任何意义,也不会出现在系统的应用列表中。
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />