Activity 的生命周期和启动模式

Activity的生命周期

生命周期和启动模式以及 IntentFilter 的匹配规则分析。

Activity 的生命周期分为两个部分:

  • 典型情况下的生命周期
  • 异常情况下的生命周期

典型情况下的生命周期分析

  • onCreate:首次创建 Activity 时调用。 您应该在此方法中执行所有正常的静态设置 — 创建视图、将数据绑定到列表等等。始终后接 onStart()
  • onRestart:在 Activity 已停止并即将再次启动前调用。始终后接 onStart()
  • onStart:在 Activity 即将对用户可见之前调用。如果 Activity 转入前台,则后接 onResume(),如果 Activity 转入隐藏状态,则后接 onStop()
  • onResume:在 Activity 即将开始与用户进行交互之前调用。此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。始终后接 onPause()
  • onPause:当系统即将开始继续另一个 Activity 时调用。此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()
  • onStop:在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()
  • onDestroy:在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。当 Activity 结束(有人对 Activity 调用了 finish()),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。可以通过 isFinishing() 方法区分这两种情形。

Activity 的生命周期

注意:

  • onStart和onStop是从Activity是否可见这个角度来回调的
  • onResum和onPause是从Activity是否在前台这个角度来回调的

异常情况下的生命周期分析

情况 1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

比如说横屏手机和竖屏手机会拿到两张不同的图片(设定了 landscape 或者 portrait 状态下的图片)。本来手机在竖屏状态,突然旋转屏幕,由于系统配置发生了变化,在默认情况下,Activity 会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的 Activity。

当系统配置发生改变后,Activity 会调用 onPause -> onStop -> onDestroy

由于是异常情况终止,系统会在 onStop 之前调用 onSaveInstanceState 来保存当前 Activity 的状态。(与 onPause 没有时序关系)

Activity 被系统重新创建后,系统会调用 onRestoreInstanceState,把之前 onSaveInstanceState 方法所保存的 Bundle 对象作为参数同时传给 onRestoreInstanceStateonCreate 方法。(从时序来说,onRestoreInstanceState 的调用时机在 onStart 之后)
异常情况下 Activity 的重建过程

而在视图方面,当 Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前 Activity 的视图结构,并且在 Activity 重启后为我们恢复这些数据。

其实每个 View 都有 onSaveInstanceState 和 onRestoreInstanceState,关于保存和恢复 View 层级结构,系统的工作流程如下:
关于保存与恢复 View 层级结构

onSaveInstanceState 方法,系统只会在 Activity 即将被销毁并且有机会重新显示的情况下才会去调用它。

情况 2:资源内存不足导致低优先级的Activity被杀死

其实这种情况的数据存储与恢复过程与情况 1完全一致。

Activity的优先级情况:

  • 前台的 Activity —— 正在和用户交互的 Activity,优先级最高
  • 可见但非前台的 Activity —— 比如 Activity 中弹出了一个对话框,导致 Activity 可见但是位于后台,无法和用户进行直接交互
  • 后台的 Activity —— 已经被暂停的 Activity,比如执行了 onStop,优先级最低

当系统内存不足时,系统就会按照上述优先级去杀死目标 Activity 所在的进程,并在后续通过 onSaveInstanceStateonRestoreInstanceState 来存储和恢复数据。而将后台工作放入 Service 中是一个比较好的方法。

当系统配置改变后 Activity 如何不被重新创建

由于系统配置中有很多内容,如果当某项内容发生改变后,不想系统重新创建 Activity,可以给 Activity 指定 configChanges 属性:

   android:configChanges="orientation|keyboardHidden"
参数 含义
mcc SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由3位数组成,中国为460.此项 标识mcc代码发生了改变
mnc SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。此项标识mnc发生改变
locale 设备的本地位置发生了改变们一般指切换了系统语言
touchscreen 触摸屏发生了改变,正常情况下无法发生,可以忽略它
keyboard 键盘类型发生了改变,比如用户使用了外插键盘
keyboardHidden 键盘的可访问性发生了改变,比如用户调出了键盘
navigation 系统导航方式发生了改变,比如采用了轨迹球导航,很难发生,可以忽略
screenLayout 屏幕布局发生了改变,很可能是用户激活了另一个显示设备
fontScale 系统字体缩放比如发生了改变,比如用户选择了一个新字号
uiMode 用户界面模式发生了改变,比如是否开启了夜间模式(API8新添加)
orientation 屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕
screenSize 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它和编译选项有关,当编译选项中的minSdkVersion和targetSdkVersion 均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加)
smallestScreenSize 设备的物理屏幕尺寸发生了改变,这个项目和屏幕的方向没有关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,比如用户切换到了外部的显示设备,这个选项和screenSize一样,当编译选项中的minSdkVersion和targetSdkVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API13新添加)
layoutDirection 当布局方向发生变化,这个属性用的比较少,正常情况下无须修改布局的layoutDirection属性(API17新添加)

如果我们没有在 ActivityconfigChanges 属性中指定该选项的话,当配置发生改变后就会导致 Activity 重新创建。

最常用的只有 localeorientationkeyboardHidden。需要修改的代码很简单,只需要在 AndroidMenifest.xml 中加入 Activity 的声明即可:

<activity
    android:name="com.dimon.MainActivity"
    android:configChanges="orientation|screenSize"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
@Override
public void onConfigurationChanged(Configuration newConfig){
    super.onConfigurationChanged(newConfig);
  Log.d(TAG,"onConfigurationChanged,newOrientation:" + newConfig.orientation);
}

Activity 没有重新创建,并且没有调用 onSaveInstanceStateonRestoreInstanceState 来存储和恢复数据,而是系统调用了 ActivityonConfigurationChanged 方法,这个时候我们可以加入一些自己的特殊处理了。

Activity的启动模式

Activity 的 LaunchMode

复习一点:启动 Activity 时,系统会创造实例并把他们放入任务栈里,而任务栈是一种“后进先出”的栈结构。

Activity的四种启动模式:

  • standard:标准模式、默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。在这种模式下,某个Activity启动了一号Activity,那么一号Activity就运行在启动它的那个Activity所在的栈中。
  • singleTop:栈顶复用模式。如果新的Activity已经位于任务栈的栈顶,那么此Activity就不会被重新创建,同时它的onNewIntent方法会被回调,并且可以根据此方法的参数获得当前请求的信息。
  • singleTask:栈内复用模式。在这种单实例模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,系统也会调用其onNewIntent。
  • singleInstance:单实例模式。这是一种加强的singleTask模式,除了具有singleTask模式的所有特性外,还加强了一点,那就是具体此种模式的Activity只能单独地位于一个任务栈中。

注:在任何跳转的时候,首先调用本 Activity 的 onPause,然后跳转。如果被跳转的 Activity 由于启动方式而没创建新的实例,则会先调用 onNewIntent ,然后按照正常的生命周期调用。

如:

  • A→B,A:onPause;B:onCreate,onStart,onResume。
  • A(singleTop)→A,A:onPause;A:onSaveInstanceState;A:onResume。

一些具体问题与情况

  1. 首先要说明:任务栈分为前台任务栈和后台任务栈,后台任务栈中的 Activity 位于暂停状态。singleTask 模式的 Activity 切换到栈顶会导致在它之上的栈内的 Activity 出栈。
  2. TaskAffinity:任务相关性。标识一个 Activity 所需要的任务栈的名字。
    adnroid:taskAffinity="com.dimon.task1"
    
    默认情况下 Activity 所需要的任务栈的名字为应用的包名。
    TaskAffinity

给 Activity 指定启动模式

  • 方法一:通过 AndroidMenifest 为 Activity 指定启动模式

    <activity
     android:name="com.dimon.SecondActivity"
     android:configChanges="screenLayout"
     adnroid:taskAffinity="com.dimon.task1"
     android:launchMode="singleTask"
     android:label="@string/app_name"/>
    
  • 方法二:通过 Intent 中设置标志位为 Activity 指定启动模式

    Intent intent = new Intent();
    intent.setClass(MainActivity.this, SecondActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
    

区别:
优先级:第二种方法比第一种优先级高,两种都存在时,以第二种为准。
限定范围:第一种方法比无法设置 FLAG_ACTIVITY_CLEAR_TOP 标识,而第二种方法比无法指定 singleInstance 模式。

Acticity 中常用的 Flags

  • FLAG_ACTIVITY_NEW_TASK:这个标记位的作用是为 Activity 指定 singleTask 启动模式,其效果和在 XML 中指定该启动模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP:这个标记位的作用是为 Activity 指定 singleTop 启动模式,其效果和在 XML 中指定该启动模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP:具有次标记位的 Activity,当它启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈,这个标记位一般会和 singleTask 启动模式一起出现。如果被启动的 Activity 的实例已经存在,那么系统就会调用它的 onNewIntent。如果被启动的Activity采用了 standard 启动模式,那么它以及它之上的 Activity 都要出栈,系统会创建新的 Activity 实例并放入栈顶。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的 Activity 不会出现在历史 Activity 的列表中,当某种情况下我们不希望用户通过历史列表回到我们的 Activity 的时候这个标记比较有用。它等同于在 XML 中指定 Activity 的属性 android:excludeFromRecents="true"

IntentFilter 的匹配规则

Intent 解析机制主要是通过查找已注册在 AndroidManifest.xml 中的所有 IntentFilter 及其中定义的 Intent,最终找到匹配的 Intent。在这个解析过程中,Android 是通过 Intent 的 actiontypecategory 这三个属性来进行匹配判断的。一个过滤列表中的 actiontypecategory 可以有多个,所有的 actiontypecategory 分别构成不同类别,同一类别信息共同约束当前类别的匹配过程。只有一个 Intent 同时匹配 action、type、category 这三个类别才算完全匹配,只有完全匹配才能启动 Activity。另外一个组件若声明了多个 Intent Filter,只需要匹配任意一个即可启动该组件。

action 的匹配规则

action 是一个字符串,如果 Intent 指明定了 action,则目标组件的 IntentFilter 的 action 列表中就必须包含有这个 action,否则不能匹配。一个 Intent Filter 中可声明多个 action,Intent 中的 action 与其中的任一个 action 在字符串形式上完全相同(注意,区分大小写,大小写不同但字符串内容相同也会造成匹配失败),action 方面就匹配成功。可通过 Intent 的 setAction 方法为 Intent 设置 action,也可在构造 Intent 时传入 action。需要注意的是,隐式 Intent 必须指定 action。

Android 系统预定义了许多 action,这些 action 代表了一些常见的操作。常见action如下(Intent类中的常量):

Intent.ACTION_VIEW
Intent.ACTION_DIAL
Intent.ACTION_SENDTO
Intent.ACTION_SEND
Intent.ACTION_WEB_SEARCH

category 的匹配规则

category 也是一个字符串,但是它与 action 的过滤规则不同,它要求 Intent 中如果含有 category,那么所有的 category 都必须和过滤规则中的其中一个 category 相同。也就是说,Intent 中如果出现了 category,不管有几个 category,对于每个 category 来说,它必须是过滤规则中的定义了的 category。当然,Intent 中也可以没有 category(若Intent中未指定category,系统会自动为它带上“android.intent.category.DEFAULT”),如果没有,仍然可以匹配成功。category 和 action 的区别在于,action 要求 Intent 中必须有一个 action 且必须和过滤规则中的某几个 action 相同,而 category 要求 Intent 可以没有 category,但是一旦发现存在 category,不论你有多少,每个都要能够和过滤规则中的任何一个 category 相同。我们可以通过 addCategory 方法为 Intent 添加 category。
特别说明:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

这二者共同出现,标明该 Activity 是一个入口 Activity,并且会出现在系统应用列表中,二者缺一不可。

data 的匹配规则

如果 Intent 没有提供 type,系统将从 data 中得到数据类型。同 action 类似,只要 Intent 的 data 只要与 Intent Filter 中的任一个 data 声明完全相同,data 方面就完全匹配成功。

data由两部分组成:mimeTypeURI

  • mimeType:媒体类型,例如 imgage/jpeg、auto/mpeg4 和 viedo/* 等,可以表示图片、文本、视频等不同的媒体格式。
  • uri:由 scheme、host、port、path | pathPattern | pathPrefix 这 4 部分组成。

URI 的结构如下:

<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern >]
  • scheme:URI 的模式,比如 http、https、file、content 等。
  • host:URI 的主机名,比如 www.baidu.com
  • port:URI 的端口号。
  • path、pathPattern 和 pathPrefix:这三个表示路径信息。path:用来匹配完整的路径;pathPrefix:用来匹配路径的前缀部分;pathPattern:用表达式来匹配整个路径。

IntentFilter 常见问题汇总

path、pathPattern 和 pathPrefix 的区别

  • path:用来匹配完整的路径,如:http://example.com/blog/abc.html ,这里将 path 设置为 /blog/abc.html 才能够进行匹配;
  • pathPrefix:用来匹配路径的前缀部分,拿上来的 Uri 来说,这里将 pathPrefix 设置为 /blog 就能进行匹配了;
  • pathPattern:用表达式来匹配整个路径,但是它里面可以包括通配符 *,注意正则表达式。

匹配符号:
* 用来匹配0次或更多,如:a* 可以匹配“a”、“aa”、“aaa”…
. 用来匹配任意字符,如:. 可以匹配“a”、“b”,“c”…
.* 用来匹配任意字符0次或更多,如:.*html 可以匹配 “abchtml”、“chtml”,“html”,“sdf.html”…

转义:因为当读取 Xml 的时候,“\” 是被当作转义字符的(当它被用作 pathPattern 转义之前),因此这里需要两次转义,读取 Xml 是一次,在 pathPattern 中使用又是一次。如:“” 这个字符就应该写成 “\”,“\” 这个字符就应该写成 “\\”。

查询是否有 Activity 可以匹配指定 Intent 的组件

  • 采用 PackageManagerresolveActivity 或者 IntentresolveActivity 方法会获得最适合 Intent 的一个 Activity
  • 调用 PackageManagerqueryIntentActivities 会返回所有成功匹配 Intent 的 Activity

android.intent.action.MAIN 与 android.intent.category.LAUNCHER 的区别

  • android.intent.action.MAIN:决定应用程序的入口 Activity,也就是决定一个应用程序最先启动那个组件。
  • android.intent.category.LAUNCHER:决定应用程序是否被列入系统的启动器,也就是说是否在桌面上显示一个图标。Launcher 是安卓系统中的桌面启动器,是桌面UI的统称。

这两个属性组合情况:

  • 第一种情况:有MAIN,无LAUNCHER,程序列表中无图标。原因:android.intent.category.LAUNCHER 决定应用程序是否显示在程序列表里。
  • 第二种情况:无MAIN,有LAUNCHER,程序列表中无图标。原因:android.intent.action.MAIN 决定应用程序最先启动的 Activity,如果没有 Main,则不知启动哪个 Activity,所以也不会有图标出现。

所以这两个属性一般成对出现。

如果一个应用中有两个组件 intent-filter 都添加了 android.intent.action.MAIN 和 android.intent.category.LAUNCHER 这两个属性,则这个应用将会显示两个图标,写在前面的组件先运行。

intent-filter 匹配优先级

首先查看 Intent 的过滤器(intent-filter),按照以下优先关系查找:action->data->category


评论
 上一篇
Android Jetpack 简介 Android Jetpack 简介
Android Jetpack 深受支持库的启发,支持库包含的组件可以让开发者轻松利用 Android 新功能,同时保持向后兼容性;现在,应用商店中 99% 的应用都使用支持库。在支持库取得成功后,Google 推出了架构组件,让开发者在面
2018-11-04
下一篇 
Java 设计模式之责任链模式 Java 设计模式之责任链模式
模式动机很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如审批工作流等,他们可以构成一条处理采购单的链式结构,采购单(可以看作是要处理的信息)沿着这条链进行传递,这条链就称为责任链。责任链可以是一条直线、一个环或者一个树形结构
2018-10-28
  目录