Activity是Android应用程序直接与用户交互的组件,在Activity中,除了它的用法,像生命周期和启动模式,这些内容没有深入学习很难理解其中的奥秘,接下来揭示Activity中那些不为人知的秘密。

Activity的生命周期

Activity的生命周期分为两部分,一部分是正常情况下的生命周期,另一部分是异常情况下的生命周期

正常情况下的生命周期

正常情况下的生命周期是指在有用户参与的情况下Activity经过的生命周期的改变
在【Activity的生命周期】这里详细说明了正常情况下Activity的生命周期。
Activity生命周期切换过程如下图所示:
Activity生命周期切换过程

对于上图有几种情况:

  1. 某一个特定的Activity。第一次启动,调用方法:onCreate()onStart()onResume()
  2. 当用户打开新的Activity或切换到桌面时,调用方法:onPause()onStop()。当新Activity使用透明主题,则当前Activity不调用onStop()
  3. 当用户再次回到原Activity时,调用方法:onRestart()onStart()onResume()
  4. 当用户按back键回退时,调用方法:onPause()onStop()onDestory()
  5. onCreate()onDestory()是配对的,分别标识Activity的创建和销毁,有且只有可能调用一次;onStart()onStop()是配对的,标识Activity的可见性,这两个方法可能会被调用多次;onResume()onPause()是配对的,标识Activity是否在前台,这两个方法可能会被调用多次

若当前Activity为A,用户此时打开一个新的Activity B,那A会先执行onPause()方法,然后B执行onCreate()onStart()onResume(),最后A再执行onStop()方法

异常情况下的生命周期

Activity被系统回收或当前设备Configuration发生改变从而导致Activity被销毁重建。
异常情况有两种:

资源相关的系统配置发生改变导致Activity被回收并重新创建

例如当前Activity处于竖屏状态,此时突然旋转屏幕,由于系统配置发生了改变,默认情况下,Activity会被销毁并重新创建。
默认情况下如果Activity不做特殊处理,当系统配置发生改变后,Activity的生命周期如下图所示:
异常情况下的生命周期

Activity在异常情况下终止,系统会调用onSaveInstanceState()来保存当前Activity的状态,这个方法在onStop()之前执行。正常情况下系统不会调用onSaveInstanceState()。当Activity被重新创建后,系统会调用onRestoreInstanceState(),并将Activity销毁时onSaveInstanceState()方法所保存的Bundle对象作为参数传递给onRestoreInstanceState()onCreate()方法。

onSaveInstanceState()只会在Activity即将被销毁且有机会重新显示的情况下才会调用,Activity是正常销毁时,系统不会调用onSaveInstanceState()

onRestoreInstanceState()一旦被调用,其参数Bundle saveInstanceState一定是有值的不需要额外判断非空;
onCreate()正常启动时,其参数Bundle saveInstanceState为空,使用onCreate()恢复数据需要判断非空。
官方建议采用onRestoreInstanceState()恢复数据

当Activity在异常情况下需要重新创建时,系统会默认保存当前Activity的视图结构,并且在Activity重启之后恢复这些数据。保存和恢复View层次结构,系统的工作流程如下:Activity被意外终止,Activity会调用onSaveInstanceState()去保存数据;然后Activity会委托Window去保存数据;Window再委托上面的顶级容器去保存数据。顶层容器是ViewGroup,很可能是DecorView;最后顶层容器通知其子元素保存数据。

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

android:configChanges="orientation"

指定多个值用“|”连接。
configChanges属性有如下几种:
configChanges属性

内存不足导致低优先级的Activity被销毁

在这种情况下数据存储与恢复与第一种情况完全一致。Activity按照优先级从高到低分为三类:

  1. 前台Activity:与用户交互的Activity,优先级最高
  2. 可见非前台Activity:Activity弹出一个对话框,导致Activity可见但位于后台无法与用户交互
  3. 后台Activity:已经被暂停的Activity,优先级最低

当系统内存不足时,系统会按照上述优先级去杀死目标Activity所在进程并在后续通过onSaveInstanceState()onRestoreInstanceState()来存储与恢复数据,如果一个进程没有四大组件在执行,那这个进程会很快被杀死。

Activity启动模式

launchMode

Activity的启动模式】这里介绍了Activity的启动模式的含义以及类型。
这里补充一些内容:

  • 当使用ApplicationContext去启动standard模式的Activity会报错,因为standard模式的Activity默认会进入启动它的Activity所属的任务栈总,但非Activity类型的Context(ApplicationContext)没有任务栈,就会出错。解决办法是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动就会为其创建一个新的任务栈
  • 任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈调回前台
  • singleTask启动模式中,有一个参数TaskAffinity,这个参数标识了一个Activity所需要的任务栈名字,默认情况下,所有Activity所需任务栈的名字为应用包名。TaskAffinity属性主要和singleTask启动模式或allowTaskReparenting属性配对使用:
    • 当TaskAffinity与singleTask启动模式配对使用时,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中
    • 当TaskAffinity与allowTaskReparenting属性配对使用时,当应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true时,那当应用B被启动之后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。
  • singleTask模式的Activity切换到栈顶会导致在它之上的Activity出栈

给Activity指定启动模式有两种方式:
xml方式:

<activity
    android:name="cn.chenjianlink.android.FirstActivity"
    android:launchMode="singleTask"/>

代码方式:

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

第二种方式优先级高于第一种,两种方式在限定范围上有所不同:第一种方法无法为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法为Activity指定singleInstance模式

Activity的Flag

标记位的作用很多,常用的标记位有:

  • FLAG_ACTIVITY_NEW_TASK:为Activity指定singleTask模式启动
  • FLAG_ACTIVITY_SINGLE_TOP:为Activity指定singleTask模式启动
  • FLAG_ACTIVITY_CLEAR_TOP:有该标记的Activity启动时,在同一任务栈中所有位于它之上的Activity都要出栈。这个标记位一般和singleTask启动模式一起出现,在这种情况下被启动的Activity实例已经存在,那系统会调用它的onNewIntent()方法。如果被启动的Activity采用standard模式启动,那它连同它之上的Activity都要出栈,系统会创建新的Activity实例并放入栈中。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:有该标记的ACtivity不会出现在历史Activity的列表中,等同于xml属性:
    android:excludeFromRecents="true"

IntentFilter匹配规则

启动Activity分为两种,显式调用和隐式调用。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,不匹配则无法启动。为了匹配过滤列表,必须同时匹配过滤列表中的action、category、data信息。一个Activity可有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。

action匹配规则

action是一个字符串,它的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,与action字符串值完全一样,而且action区分大小写。
action的匹配要求Intent中的action存在且必须和过滤规则中的其中一个action相同。

category的匹配规则

category的匹配规则与action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同,Intent中可以没有category。

data匹配规则

data的匹配规则和action类似,如果过滤规则中定义了data,那Intent中也要定义可匹配的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指媒体类型;URI包含数据较多,其结构如下:

<scheme>://<host>:<port>/[<path>|<pathPattern>|<pathPrefix>]

  • Scheme:URI的模式(http、file、content等),若没有指定,则整个URI无效
  • Host:URI主机名,若没有指定,则整个URI无效
  • Port:URI中的端口号,URI指定了scheme和host,port才是有意义的
  • path:完整路径信息
  • pathPattern:表示完整路径,可包含通配符
  • pathPrefix:表示路径前缀信息

采用PackageManager的resolveActivity()方法或Intent的resolveActivity()方法可以判断是否存在能匹配Intent的Activity,若不存在则返回值为空.PackageManager还提供了queryIntentActivities(),这个方***返回所有成功匹配的Activity信息

public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, @ResolveInfoFlags int flags);
public abstract ResolveInfo resolveActivity(@NonNull Intent intent, @ResolveInfoFlags int flags);