Android进阶知识——Activity的生命周期和启动模式

Posted ABded

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶知识——Activity的生命周期和启动模式相关的知识,希望对你有一定的参考价值。

文章目录


Activity作为四大组件之首,是使用最为频繁的一种组件。而本节我们就来讲解一些Activity在使用过程中的一些不容易搞清楚的概念,主要包括生命周期和启动模式以及IntentFilter的匹配规则分析。

1.Activity的生命周期全面分析

首先Activity的生命周期分为典型情况下的生命周期以及异常情况下的生命周期两种。

  • 典型情况下的生命周期:指在有用户参与的情况下,Activity所经历过的生命周期的改变。

  • 异常情况下的生命周期:指Activity被系统回收或者由于当前设备的Configuration(布局)发生改变从而导致Activity被销毁重建。

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

在正常情况下,Activity会经历如下生命周期。

  • onCreate():该方法会在活动第一次被创建时调用,应该在该方法中完成活动的初始化操作。

  • onRestart():该方法会在活动由停止状态变为运行状态之前调用,也就是活动会被重新启动。(当Activity从不可见重新变为可见状态时,该方法就会被调用 )

  • onStart():表示Activity正在被启动,即将开始。(此时Activity已经可见了,但是还没有出现在前台,还无法和用户交互)

  • onResume():该方法在活动准备与用户进行交互时调用,此时活动一定处于栈顶且处于运行状态。(此时Activity出现在前台并开始活动,此时已经可以和用户交互了)

  • onPause():该方法在系统准备去启动或恢复另一个活动时调用。(此时可以做一些存储数据、停止动画等工作,但不能太耗时,因为onPause必须先执行完,新Activity的onResume才会执行)

  • onStop():该方法在活动完全不可见时调用,与onPause()方法的区别在于,若启动的新活动是一个对话框活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。(此时可以做一些稍微重量级的回收工作,同样不能太耗时)

  • onDestroy():该方法会在活动销毁前执行,之后活动的状态就会变为销毁状态。(此时可以做一些回收工作和最终的资源释放)

具体情形下的Activity生命周期情况分析:

  • 针对一个特定的Activity,第一次启动,回调如下:onCreat->onStart->onResume

  • 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause->onStop(这里有一个特殊情况,如果新Activity采用了透明主题,那么当前Activity不会回调onStop)

  • 当用户再次回到原Activity时,回调如下:onRestart->onStart->onResume

  • 当用户按back键回退时,回调如下:onPause->onStop->onDestroy

  • 当Activity被系统回收后再次打开,回调如下:onCreat->onStart->onResume

  • 在整个生命周期来说,onCreate和onDestory是配对的,分别标志着Activity的创建和销毁;从Activity是否可见来说,onStart和onStop是配对的,分别标志着Activity的可见和不可见;从Activity是否在前台来说,onResume和onPause是配对的,分别标志着Activity可交互和不可交互

现在我们来分析几个比较常见的问题:

  • 问题1:onStart和onResume、onPause和onStop从描述上看起来差不多,当我们来说有什么实质的不同呢?

  • 问题2:假设当前Activity为A,如果这时用户打开一个新ActivityB,那么B的onResume和A的onPause哪个先执行?

问题1,答:onStart和onStop是从Activity是否可见这一角度来回调的,而onResume和onPause是从Activity是否位于前台这一角度来回调的,除了这种区别,在实际使用中没有其它明显的区别。

问题2,答:旧Activity的onPause先调用,然后新Activity才启动。究其原因我们可以在android源码里得到解释,在这里我们就只简单介绍以下Activity的启动过程,对源码有兴趣的同学可以自行了解;启动Activity的请求会由Instrumentation来处理,然后通过Binder向AMS(ActivityManagerService)发请求,AMS内部维护着一个ActivityStack并负责栈内的Activity状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。

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

能影响Activity生命周期的异常共有两种:1.资源相关的系统配置发生改变导致Activity被杀死并重新创建;2.资源内存不足导致低优先级的Activity被杀死。接下来我们来具体分析这两种情况。

情况1

我们先来举一个这种情况的例子:比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建。

这种情况下的生命周期如下所示:

当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestory均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。(该方法的调用时机是在onStop之前,而与onPause没有既定的时序关系,它既有可能在onPause之前调用,也有可能在onPause之后调用;注:该方法只会出现在Activity被异常终止的情况下,正常情况下系统不会调用该方法)

当Activity被重新创建时,系统会调用onRestoreInstanceState,并把Activity销毁时onSaveInstanceState保存的Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法,那么我们就可以取出之前保存好的数据并恢复。(该方法的调用时机是在onStart注:该方法只会出现在Activity被异常终止的情况下,正常情况下系统不会调用该方法,因此onRestoreInstanceState中的Bundle一定是有值的)

特别说明:和Activity一样,每个View都有onSaveInstanceState和onRestoreInstanceState这两个方法,从而在Activity异常情况下重建时,系统帮助我们恢复一定的数据。

关于保存和恢复View中数据,系统的工作流程如下:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window在委托它上面的顶级容器去保存数据。顶级容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。

情况2

该情况下的数据存储和恢复过程和情况1完全一致。这里我们描述以下Activity的优先级情况:

  • 前台Activity——正在和用户交互的Activity,优先级最高。(onResume)

  • 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户交互。(onPause)

  • 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。(onStop)

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

经过上述的分析,我们知道,当系统配置发生改变时,Activity会被重新创建,那么有没有办法不重新创建呢?系统配置中有很多内容,如果当某项内容发生改变后,我们不想重新创建Activity,可以给Activity指定configChanges属性。例如:不相让Activity在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation这个值。如下所示:

android:configChanges="orientation"

更多configChanges的项目和含义

上面表格中的项目很多,但是我们常用的只有locale(设备的本地位置发生改变)、orientation(屏幕方向发生了改变)和keyboardHidden(键盘的可访问性发生了改变)这三个选项。

值得说明的是:倘若我们的Activity由于设置了configChanges属性,而导致Activity没有重新创建时。那么也就不会调用onSaveInstanceState和onRestoreInstanceState方法,取而代之的是系统调用了Activity的onConfiguratonChanged方法,这个时候我们就可以做一些自己的特殊处理了。

2.Activity的启动模式

本小节我们将介绍Activity的启动模式和标志位

2.1Activity的LaunchMode

目前Activity共有四种启动模式:standard、singleTop、singleTask和singleInstance四种。

  • standard:标准模式,也就是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例的生命周期符合典型情况下Activity的生命周期。(一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种启动模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如Activity A启动了 B(B是标准模式),那么B就会进入到A所在的栈中)

    特别说明:当我们用·ApplicationContext去启动standard模式的Activity时,系统会报错,这是因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,因此就会报错。解决这个问题的方法是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动时就会为它创建一个新的任务栈,这个时候启动Activity实际上是以singleTask模式启动的。

  • singleTop:栈顶复用模式。在该模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。注意:这个Activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。

  • singleTask:栈内复用模式。这是一种单实例模式,在该模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent。具体来说:当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需的任务栈,这时要看A是否在栈中有实例存在,如果有实例存在,那么系统就会把A调用到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并把A压入栈中。

    特别说明:如果D所需的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法。同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。(singleTask模式的Activity切换到栈顶会导致在它之上的栈内的Activity出栈)

  • singleInstance:单实例模式。这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Actiivty只能单独地位于一个任务栈中。(比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了)

关于某个Activity所需的任务栈。这个问题要从TaskAffinity参数说起,该参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用包名。另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调动前台。

为Activity指定启动模式的两种方式:

  • 通过AndroidMenifest为Activity制定启动模式。
<activity android:name=".MainActivity"
    android:launchMode="singleTask" />
  • 在Intent中设置标志位来为Activity指定启动模式。
Intent intent=new Intent();
intent.setClass(MainActivity.this,SecoundActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

2.2Activity的Flags

标记为的作用有很多,有的标记位可以设定Activity的启动模式,还有的标记位可以影响Activity的运行状态,下面我们主要来介绍几个比较常用的标记位。

  • FLAG_ACTIVITY_NEW_TASK:该标记位的作用是为Activity指定“singleTask”启动模式。

  • FLAG_ACTIVITY_SINGLE_TOP:该标记为的作用是为Activity指定“singleTop”启动模式。

  • FLAG_ACTIVITY_CLEAR_TOP:具有此标记位的Activity,当它启动时,在同一任务栈中所有位于它上面的Activity都要出栈。(当Activity具有此标记位,启动它时若实例已存在,那么系统就会调用它的onNewIntent方法)

  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的Activity不会出现在历史Activity的列表中。(设置后在任务管理器中就看不到当前的应用了,该属性会对整个栈有效)

3.IntentFilter的匹配规则

隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。而IntentFilter中的过滤信息有action、category、data。

一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。(匹配上一组intent-filter的含义为:需要同时匹配过滤列表中的action、category、data信息)

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        
        <data android:mimeType="text/plain" />
    </intent-filter>
    
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        
        <action android:name="android.intent.action.SEND_MULTIPLE" />
        
        <category android:name="android.intent.category.DEFAULT" />
        
        <data android:mimeType="application/vnd.google.panorama360+jpg" />
        
        <data android:mimeType="image/*" />
        
        <data android:mimeType="video/*" />
    </intent-filter>
</activity>

接下来我们详细分析各种属性的匹配规则。

  • action的匹配规则:action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里说的匹配是指action的字符产值完全一样。一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。(注意:如果Intent中没有指定action,那么匹配失败,另外action区分大小写,大小写不同字符串相同的action会匹配失败)

  • category的匹配规则:category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的一个category相同。而且,Intent中可以没有category,如果没有category的话,这个Intent仍然可以匹配成功。(这里要注意它和action匹配过程中的不同,action是要求Intent中必须有一个action且必须能够和过滤规则中的某个action相同;而category要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能够和过滤规则中的任何一个category相同)

  • data的匹配规则:data的匹配规则和action类似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。

    我们先来了解一下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>]
    

    下面再给出几个URI的实际例子,来帮助大家理解。

    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的匹配规则。data的匹配规则和action类似,并且data数据能够完全匹配过滤规则中的某一个data,这里说的完全匹配是指过滤规则中出现的data部分也出现在了Intent中的data中。(也就是说Intent中的data只要符合过滤规则中的某个data即可)

    举例说明:
    (1)

    <intent-filter>
    	<data android:mimeType="image/*" />
    </intent-filter>
    

    上述规则指定了媒体类型为所有类型的图片,那么Intent中的mimeType属性必须为“image/*”才能匹配,这种情况下虽然过滤规则没有指定URI,但是却有默认值,URI默认值为content和file。也就是说,虽然没有指定URI,但是Intent中的URI部分的scheme必须为content或file才能匹配。为了匹配上述的过滤规则,现给出如下示例:(scheme若未指定,则其默认为file或content)

    Intent intent=new Intent();
    intent.setDataAndType(Uri.parse("file://abc"),"image/png");
    

    注意:如果要为Intent指定完整的data,必须要调用setDataAndType方法,不能先调用setData再调用setType,因为这两个方法彼此会清除对方的值。

    setData源码展示:

    public @NonNull Intent setData(@Nullable Uri data) 
        mData = data;
        mType = null;//清除type的值
        return this;
    
    

    (2)

    <intent-filter>
        <data android:mimeType="video/mpeg" android:scheme="http" ... />
        <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    </intent-filter>
    

    上述规则指定了两组data规则,且每个data都指定了完整的属性值,既有URI又有mimeType。为了匹配上述的过滤规则,现给出如下示例:

    intent.setDataAndType(Uri.parse("http://abc"),"video/mpeg");
    

    intent.setDataAndType(Uri.parse("http://abc"),"audio/mpeg");
    

    注意:关于data还有一个特殊情况需要说明,如下的两种特殊写法,他们的作用是一样的:

    <intent-filter>
    	<data android:scheme="file" android:host="www.baidu.com" />
    </intent-filter>
    
    <intent-filter>
    	<data android:scheme="file" />
    	<data android:host="www.baidu.com" />
    </intent-filter>
    

最后值得我们来说的是,当我们通过隐式方式启动一个Activity的时候,可以做一下判断,看是否有Activity能匹配我们的隐式Intent。判断方法共有两种:采用PackageManager的resolveActivity方法或者Intent的resolveActivity方法,如果它们找不到匹配的Activity就会返回null。另外,PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity方法不同的是:前者不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息。下面我们来看一下两者的方法原型:

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

上述两个方法的第二个参数需要注意,我们要使用MATCH_DEFAULT_ONLY这个标记位,这个标记位的含义是仅仅匹配那些在intent-filter中声明了< category android:name="android.intent.category.DEFAULT />这个category的Activity。(这样做的意义在于,可以避免不含DEFAULT这个category的Activity无法接收隐式Intent所带来的问题)

以上是关于Android进阶知识——Activity的生命周期和启动模式的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin基础从入门到进阶系列讲解(进阶篇)Android之Activity的生命周期

进阶之路 | 奇妙的Activity之旅

❤️Android精进之路-04Android核心组件Activity,必须掌握的知识点(Activity是什么,生命周期是怎样的)❤️

Android之Activity全面解析,有些知识点容易忘记

android知识点

Android进阶之Fragment与Activity之间的数据交互