再谈Activity
目录
- 生命周期
- 正常生命周期
- 非正常生命周期
- Activity的启动模式
- Activity的LaunchMode
- Activity的Flags
- IntentFilter的匹配规则
- 附加
生命周期
正常的生命周期
onCreate()->onStart()->onResume()->onPause()->onStop()->onDestroy()
- onCreate():生命周期的第一个方法,Activity在创建时调用
- onRestart():表示Activity正在重新启动,当Activity从不可见状态变为可见状态时调用。一般在调用了onStop但并没有销毁Activity,再次展示时会调用。
- onStart():表示Activity正在被启动,Activity创建完成后调用,此时Activity可见但不可与用户交互。可以理解成Activity已经显示出来但是并没有出现在屏幕上。
- onResume:表示Activity已经可见,并出现在屏幕上可与用户交互,注意与onStart的对比,onStart和onResume都已经可见,但是onStart还在后台,onResume的时候才渲染到前台
- onPause():表示Activity正在停止,之后一般会立刻调用onStop方法,可以在此方法进行数据保存和停止动画等操作,但是不能执行耗时操作,否则会影响第二个Activity的显示速度,因为新Activity的onResume方法会在第一个Activity的onPause执行完后才会执行。
- onStop():表示Activity即将停止,此时Activity处于后台,可以适当的做一些耗时操作
- onDestroy():表示Activity即将被销毁,是生命周期中的最后一个方法,可以做一些资源回收和释放
Activity的生命周期代表的是Activity所在的状态,调用是AMS通过调用ActivityThread,ActivityThread调用对应的Activity实现的,也就代表着自行手动调用生命周期方法无法完成Activity生命周期状态的切换。
根据上图,附加具体说明,分为以下几种情况:
- 一个Activity第一次启动时,回调onCreate->onStart->onResume
- 当打开新Activity时,onPause(old)->onCreate(new)->onStart(new)->onResume(new)->onStop(old)
- 当从新Activity返回旧Activity时,onPause(new)->onRestart(old)->onStart(old)->onResume(old)->onStop(new)->onDestroy(new)
- 当按home键时,onPause->onStop,重新打开时,onRestart->onStart->onResume
- 当按back键时,onPause->onStop->onDestroy
- 被系统回收后,再次打开,生命周期和1一样,具体过程会增加数据恢复的过程
- 从整体来说,除了onRestart外其他生命周期都是成对出现的,onCreate和onDestroy分别标识着创建和销毁,onStart和onStop分别标识着Activity是否可见,onResume和onPause分别标识着Activity是否可交互
非正常的生命周期
上面我们讲了正常的生命周期,那么非正常的生命周期是什么:当系统内存不足,或者资源相关的配置发生变化时Activity会被系统杀死。
情况1:资源相关的配置发生变化导致Activity销毁重建
默认情况下,如果我们的Activity不做特殊配置,那么当系统配置发生改变后,Activity就会被销毁并重建。生命周期:onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume
上面的生命周期可以看到没有onPause,这是因为onPause和onSaveInstanceState的执行顺序没有既定的时序关系。onSaveInstanceState在onStop之前执行,onRestoreInstanceState在onResume之前onStart之后执行。
保存和恢复View层次结构采用的典型委托思想,系统的工作流程是:首先Activity被意外停止时,Activity会调用onSaveInstanceState去保存数据,然后Activity委托Window去保存数据,接着Window委托它上面的顶层容器去保存数据。顶层容器是一个ViewGroup,一般是DecorView。最后顶层容器再去一一通知它的子元素来保存数据。
情况2:内存不足导致低优先级的Activity被杀死
这种情况的数据保存和恢复过程跟情况1是一样的。我们描述一下Activity的优先级情况。Activity的优先级从高到低可以分成如下三种:
- 前台Activity—正在和用户交互的Activity,优先级最高
- 可见但不可交互的Activity—比如Activity中弹出一个对话框,导致Activity可见但是处于后台无法与用户进行交互
- 后台Activity—已经被暂停的Activity,比如执行了onStop,优先级最低
上面的非正常周期说的没有任何其他配置的时候的生命周期,我们这里的其他配置指的是Activity的android:configChanges属性,比如横竖屏切换,正常的横竖屏切换会执行情况1的生命周期,但是如果给Activity的configChanges属性设置了orientation那么横竖屏切换就不会执行onPause->onStop->onDestroy->onCreate->onStart->onResume而只会执行onConfigurationChanged方法。
configChanges的项目和含义
项目 | 含义 |
---|---|
mcc | SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460 |
mnc | SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03 |
locale | 设备的本地位置发生了改变,一般指切换了系统语言 |
touchscreen | 触摸屏发生了改变,正常情况下无法发生 |
keyboard | 键盘类型发生了改变,比如用户使用了外接键盘 |
keyboardHidden | 键盘的可访问性发生了改变,比如用户调出了键盘 |
navigation | 系统导航方式发生了改变,比如采用了轨迹球导航 |
screenLayout | 屏幕布局发生了改变,比如用户激活了另外一个显示设备 |
fontScale | 系统字体缩放比例发生了改变,比如用户选择了一个新字号 |
uiMode | 用户界面模式发生了改变,比如是否开启了夜间模式(API 8新添加) |
orientation | 屏幕方向发生了改变,比如旋转了手机屏幕 |
screenSize | 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它和编译选项有关,当编译选项中的minSdkVersion和targetSDKVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API 13新添加) |
smallestScreenSize | 设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没有关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,比如用户切换到了外部的显示设备,这个选项和screenSize一样,当编译选项中的minSdkVersion和targetSDKVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API 13新添加) |
layoutDirection | 当布局方向发生改变,正常情况下无需修改布局的layoutDirection属性(API 17新添加) |
Activity的启动模式
Activity的LaunchMode
-
Activity为什么需要启动模式
默认情况下,当我们多次启动同一个Activity时,系统会创建多个实例并把它们一一压入任务栈中,当我们单机back键,会发现这些Activity会一一回退。任务栈是一种“后进先出”的栈结构,可以这样理解,每按一下back键就会有一个Activity出栈,直到栈空为止,当栈中无任何Activity时,系统就会回收这个任务栈。如果我们多次启动同一个Activity,系统重复创建多个实例,退出的时候还要逐一退出,这样不是很傻。所以Android为我们提供了启动模式来修改系统的默认行为。
-
四种启动模式
- standard:标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例。
- singleTop:栈顶复用,如果要启动的Activity已经在栈顶,则不会重新创建Activity,同时该Activity的onNewIntent方法会被调用。如果要启动的Activity不在栈顶,则会重新创建该Activity的实例。
- singleTask:栈内复用,如果要启动Activity已经在它想要归属的栈中,那么不会创建新的实例,将栈中位于该Activity上的所有Activity出栈,同时该Activity的onNewIntent会被调用。如果要启动的Activity在它想要归属的栈中,并且该栈存在,则会重新创建该Activity的实例。如果要启动的Activity想要归属的栈不存在,则首先创建一个新栈,然后创建该Activity实例并压入到新栈中。
- singleInstance:和singleTask类似,不同的是启动Activity时,首先要创建一个栈,然后创建该Activity的实例并压入栈中,新栈中只会存在这一个Activity实例。
-
多任务栈
-
任务栈是什么,在AMS中保存的所有TaskRecord统称任务栈,所有启动的Activity都保存在某一个任务栈中。
-
如何创建任务栈,我们无法在AMS外部自己创建任务栈,只能给Activity配置taskAffinity属性,AMS检测taskAffinity自动创建任务栈。没有配置taskAffinity的话,除singleInstance模式之外的Activity将自动创建名称为应用包名的任务栈。
-
Activity如何入栈,AMS中可以有同名任务栈
- standard与singleTop的Activity将压入启动该Activity的栈
- singleTask的Activity如果没有指定taskAffinity将压入应用主栈,如果指定了taskAffinity将压入指定名称的栈中
-
-
singleInstance的Activity只能单独存在与一个栈中,如果没有指定taskAffinity创建一个名称与包名一致的新栈并入栈,如果指定了taskAffinity将创建一个对应名称的新栈并入栈
-
allowTaskReparenting
allowTaskReparenting为true重新分配,false则不重新分配,默认为false。重新分配task标记。例:现在有2个应用A和B,A启动了B的一个ActivityC,然后按Home回到桌面,然后点击B的桌面图标,这个时候并不是启动B的主Activity,而是显示已经被A启动的ActivityC,或者说C从A的栈转移到B的栈中。
可以这样理解,如果C没有新建栈的能力,C是B的,但是A启动了C,此时没有B那它就只能入A的栈,B启动之后,系统发现C想要归属的栈被创建,直接将C从A转移到B
-
Activity的Flags
Activity的启动模式有两种方式可以指定
- 通过AndroidManifest给Activity指定启动模式
- 通过在Intent中设置标志位给Activity指定启动模式
常用的Flags
-
FLAG_ACTIVITY_NEW_TASK
指定“singleTask”启动模式,效果跟xml指定相同
-
FLAG_ACTIVITY_SINGLE_TOP
指定“singleTop”启动模式,效果跟xml指定相同
-
FLAG_ACTIVITY_CLEAR_TOP
具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都有出栈。这个标记位一般会和singleTask启动模式一起出现,在这种情况下,被启动的Activity的实例如果已经存在,那么系统就会调用它的onNewIntent。如果被启动的Activity采用standard模式启动,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶。singleTask启动模式默认具有此标记位的效果
-
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有这个标记位的Activity不会出现在历史Activity的列表中,等同于xml中指定Activity的属性android:excludeFromRecents=“true”
IntentFilter的匹配规则
Activity的启动分为两种:
-
显示调用
指定被启动对象的组件信息,包括包名和类名
-
隐式调用
不需要明确的指定组件信息,使用IntentFilter中的过滤信息action、category、data进行匹配
原则上,一个intent不应该既有显示调用又有隐式调用,如果二者并存,以显示调用为准
-
action的匹配规则
action是一个字符串,系统预定义了一些action,同时我们也可以在应用中定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里的匹配是指action的字符串值完全一样。一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。
action的匹配要求Intent中的action存在且必须和过滤规则中的某一个action相同,区分大小写。
-
category的匹配规则
category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category的匹配规则和action不同,不是必须的,但是如果Intent中指定了category,那么指定的所有category必须都跟过滤规则中的某一个的字符串值一样。为什么可以不知道category,因为系统会自动为Intent加上”android.intent.category.DEFAULT“这个category。我们在定义可隐式调用的Activity时也就必须在intent-filter中指定”android.intent.category.DEFAULT“这个category。
category的匹配要求Intent中可以不指定category,但是如果指定,则所有category都必须跟过滤规则中的某一个category相同。
-
data的匹配规则
data的规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。
data的语法
<data android:host="string" android:mimeType="string" android:path="string" android:pathPattern="string" android:pathPrefix="string" android:port="string" android:scheme="string" />
data由两部分组成,mimeType和URI。mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片、文本、视频等不同的媒体格式。
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的数据格式后,我们说一下匹配规则。data的匹配规则和action类似,它也要求Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个data。这里的完全匹配是指过滤规则中出现的data部分也出现在了Intent中的data中。
data的URI有默认值,默认值为content和file。所以在过滤规则中没有指定URI的时候,Intent中的URI部分的scheme必须为content或者file才能匹配。
如果过滤规则中有data则Intent中必须有data能与过滤规则中的任何一个data一致
附加
-
隐式调用Activity
当我们通过隐式调用启动一个Activity时,可以做一下判断,看是否有Activity能够匹配我们的隐式Intent。判断方式有两种:
- 采用PackageManager的resolveActivity方法
- Intent的resolveActivity方法
如果找不到匹配的Activity就会返回null。
PackageManager还提供了queryintentActivities方法,这个方法和resolveActivity方法不同的是,它返回的不是最佳匹配的Activity信息而是返回所有成功匹配的Activity信息
-
Activity各启动模式应用场景
-
standard
系统默认的启动模式,我们开发一个应用很多Activity都会使用此模式
-
singleTop
栈顶复用模式,Activity A启动Activity A可以使用此模式,降低性能消耗。例如:某个新闻客户端的新闻内容页面,如果收到10个新闻推送,每次都打开一个新闻内容页面就不合理了
-
singleTask
栈内复用,整个APP里只有一个的Activity可以使用此模式,例如:首页,不管是从哪进入首页都需要进入的是同一个首页,所以可以采用此模式
-
singleInstance
适用于需要于程序分离的界面,例如:闹钟提醒界面,需要将闹钟提醒界面与闹钟设置界面分离。singleInstance不要用于中间页,如果用于中间页,跳转会有问题,比如,A->B(singleInstance)->C,按返回键,会从C直接退到A,再按返回键会将A退出,将B展示出来
-