Android活动——Activity生命周期与启动模式

1. Activity生命周期分析

这里生命周期分为两部分内容:

  • 一是典型情况下的生命周期
    指在有用户参与的情况下,Activity所经过的生命周期的改变
  • 另一部分是异常情况下的生命周期
    Activity被系统回收或由于当前设备的Configuration发生改变从而导致Activity被销毁重建,与典型情况下的关注点略有不同。

1.1 典型情况下的生命周期

正常情况下会经过如下生命周期:

  • onCreate :表示Activity正在被创建,做一些初始化工作,如加载布局,初始化一些数据
  • onRestart:表示Activity正重新启动。当当前Activity从不可见重新变为可见状态,就会被调用
  • onStart:表示Activity正在启动,此时Activity已经可见,未在前台,无法交互
  • onResume:表示Activity已经可见在前台,可交互了
  • onPause:表示Activity正在停止,可做一些不耗时操作,如停止动画,存储数据。注意onPause必须先执行完,新Activity的onResume才会执行
  • onStop:表示Activity即将停止,不能做太耗时操作。
  • onDestroy:表示Activity即将销毁,可以做一些回收工作与资源释放

Activity典型生命周期图
附加情况

  • 用户打开新Activity或者切换到桌面,回调:onPause -> onStop 如果新Activity采用透明主题,当前Activity不会回调onStop
  • 用户按back键回退,回调为:onPause -> onStop -> onDestroy
  • Activity被系统回收再次打开,生命周期回调一样,但是不代表所有过程一样
  • 整个生命周期,onCreate与onDestroy只可能有一次调用

问题一:onStart和onResume,onPause和onStop有什么实质性不同?

这两个配对回调的意义不同,从是否可见,是否在前台,其他基本无差别

问题二:当前Activity为A,新开打B,B的onResume和A的onPause哪个先执行?

这个可以从源码获得解释,在新Activity启动之前,栈顶的Activity需要先onPause后,新的Activiy才能启动

1.2 异常情况下的生命周期

1.2.1 资源相关系统配置发生改变导致Activity被杀死并重新创建

典型例子本来手机屏幕是竖屏状态,突然旋转屏幕,系统配置发生了变化,默认情况下,Activity就会被销毁并且重新创建,当然也可以阻止系统重新创建我们的系统
异常情况下Activity的重建过程
当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,系统会调用onSaveInstanceState来保存当前的Activity状态,在onStop之前调用,与onPause无既定时序关系。Activity被重新创建后,系统会调用onRestoreInstanceState,并且把onSaveInstanceState保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。onRestoreInstanceState调用时机在onStart之后

系统自动为我们做了一定的恢复工作,系统默认为我们保存当前Activity的视图结构,如文本框中输入数据,ListView滚动位置等,具体针对哪个View系统能为我们恢复哪些数据,可以查看View的源码。

在onSaveInstanceState中存储一个字符串,Activity销毁重建可以在onCreate或onRestoreInstanceState中接收,二者区别是onCreate需要判空而后者不需要,onRestoreInstanceState被调用则一定有值

Activity正常销毁时,系统不会调用onSaveInstanceState。

1.2.2 资源内存不足导致低优先级的Activity被杀死

优先级从高到低分为三种:

  • 前台Activity:正在和用户交互,优先级最高
  • 可见非前台Activity:
  • 后台Activity:优先级最低

内存不足,按照上述优先级去杀死目标Activity所在进程,后续通过onSaveInstanceState和onRestoreInstanceState存储和恢复数据。一个进程如果没有四大组件在执行,很容易被系统杀死。较好方法是将后台工作放入Service中从而保证进程有一定的优先级。

1.2.3 系统异常销毁Activity不想重建

系统配置发生改变,如果不想系统重新创建Activity,可以给Activity指定configChange属性。
示例:如果不想Activity在屏幕旋转时重新创建就可以给configChanges属性添加orientation这个值。

android:configChanges="orientation"

如果想要指定多个值,可以用“|”连接起来。一般常用的只有local,orientation和keyboardHidden这三个选项,其他很少使用

2. Activity启动模式

2.2.1 Activity的LaunchMode

目前有四种启动模式:standard、singleTop、singleTask和SingleInstance

  • standard:标准模式,系统默认模式,每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。当用ApplicationContext去启动standard模式的Activity,会报错,因为非Activity类型的Context没有所谓的栈,解决方法是将待启动Activity指定为FLAG_ACTIVITY_NEW_TASK标记位。
  • singleTop:栈顶复用模式。新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。
  • singleTask:栈内复用模式。这是一种单实例模式。只要Activity在一个栈中存在,多次启动此Activity都不会重新创建实例,并把这个活动之上的所有活动统统出栈。如原来栈是ABC,现在启动B,则栈为BC。
  • singleInstance:单实例模式。具有此模式的Activity只能单独地位于一个任务栈中。

在这里插入图片描述
在这里插入图片描述

对于一个具有singleTask模式的Activity请求启动后,系统首先会寻找是否存在A想要的任务栈。那么什么是Activity所需要的任务栈?需要从一个参数说起:TaskAffinity。这个参数标识了一个Activity所需要的任务栈的名字。默认所有Activity所需的任务栈名字为应用的包名,也可以为每个Activity都单独指定TaskAffinity属性,这个属性必须不能和包名相同。TaskAffinity主要和singleTask启动模式或allowTaskReparenting属性配对使用。

任务栈分为前台任务栈和后台任务栈,用户可以通过切换将后台任务栈再次调到前台。

TaskAffinityallowTaskReparenting结合时,这种情况比较复杂,会产生特殊
的效果。当一个应用 A 启动了应用 B 的某个 Activity 后,如果这个 Activity 的
allowTaskReparenting 属性为 true 的话,那么当应用 B 被启动后,此 Activity 会直接从应用
A 的任务栈转移到应用 B 的任务栈中。

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

  • 第一种是通过AndroidMenifest为Activity指定启动模式:
<activity
 android:name="com.ryg.chapter_1.SecondActivity"
 android:configChanges="screenLayout"
 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); 

二者的区别是:第二种优先级高于第一种;其次,二者限定范围不同:第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP 标识,而第二种方式无法为 Activity 指定 singleInstance 模式。

2.2.2 Activity的Flags

Activity的标记位很多,这里给出一些常用的标记位,具体使用可以查看官方文档:

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

3. IntentFilter匹配规则

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

示例:

<activity
 android:name="com.ryg.chapter_1.ThirdActivity"
 android:configChanges="screenLayout"
 android:label="@string/app_name"
 android:launchMode="singleTask"
 android:taskAffinity="com.ryg.task1" >
 <intent-filter >
 <action android:name="com.ryg.charpter_1.c"/>
 <action android:name="com.ryg.charpter_1.d"/>
 <category android:name="com.ryg.category.c"/>
 <category android:name="com.ryg.category.d"/>
 <category android:name="android.intent.category.DEFAULT"/>
 <data android:mimeType="text/plain"/>
 </intent-filter>
</activity> 

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

<activity android:name="ShareActivity">
 <!-- This activity handles "SEND" actions with text data -->
 <intent-filter>
 <action android:name="android.intent.action.SEND"/>
 <category android:name="android.intent.category.DEFAULT"/>
 <data android:mimeType="text/plain"/>
 </intent-filter>
 <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media
 data -->
 <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 是一个字符串,系统预定义了一些 action,同时我们也可以在应用中定义自己的action。action 的匹配规则是 Intent 中的 action 必须能够和过滤规则中的 action 匹配,这里说的匹配是指 action 的字符串值完全一样。一个过滤规则中可以有多个 action,那么只要Intent 中的 action 能够和过滤规则中的任何一个 action 相同即可匹配成功。Intent没有指定action,则匹配是被。action区分大小写
  • category匹配规则
    category 是一个字符串,系统预定义了一些 category,同时我们也可以在应用中定义自己的 category。category 的匹配规则要求Intent中如果含有category,那么所有的 category 都必须和过滤规则中的其中一个 category 相同。Intent 中如果出现了 category,不管有几个 category,对于每个 category 来说,它必须是过滤规则中已经定义了的 category。Intent 中可以没有 category,这个Intent仍可以匹配成功。
  • data匹配规则
    如果过滤规则中定义了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 由两部分组成,mimeTypeURI

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参数都是无效的

Host:URI模式,比如www.baidu.com,同样未指定整个URI无效

Port:URI中端口号,比如80,仅当URI指定了scheme和host参数是有意义

path、pathPattern,pathPrefix:表示路径信息,path表示完整路径信息,pathPattern也是,不过可以包含通配符“*”;pathPrefix表示路径前缀信息

data匹配规则与action类似。URI的默认值为content和file,即没有指定URI,但是Intent中URI部分scheme必须为content或file才能匹配

如果要为Intent指定完整的data,必须调用setDataAndType方法,先后调用会彼此清除对方的值

intent.setDataAndType(Uri.parse("file://abc"),"image/png")

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:可采用PackageManager的resolveActivity 方法或者 Intent 的 resolveActivity 方法。找不到匹配的返回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 的 Activity。使用这个标记位的意义在于,只要上述两个方法不返回 null,那么 startActivity 一定可以成功。如果不用这个标记位,就可以把 intent-filter 中 category 不含 DEFAULT 的那些 Activity 给匹配出来,从而导致 startActivity 可能失败。因为不含有 DEFAULT 这个 category 的 Activity是无法接收隐式 Intent 的

猜你喜欢

转载自blog.csdn.net/weixin_43499030/article/details/89884174