Activity的生命周期和启动模式

一.Activity的生命周期全面解析

这里介绍的生命周期分为两个部分,一部分是典型情况下的生命周期,另一部分是异常情况下的生命周期。所谓典型情况下的生命周期,是指在有用户参与的情况下,Activity所经过的生命周期的变化;而异常情况下的生命周期是指Activity被系统回收或者当前设备的Configuration(配置)发生改变从而导致Activity重建,异常情况下的生命周期的关注点和典型情况有所不同。

1.典型情况下的生命周期

  • onCreate:代表activity正在本创建,这是生命周期的第一个方法。在这个方法中我们做一些初始化的工作,比如加载界面布局资源,初始化activity所需数据。
  • onRestart:表示activity正在重新启动,一般情况下,activity从不可见变为可见状态,onRestart就会被调用,这一般是用户的行为导致的,用户点击home键回到桌面或者打开了另一个activity,都会让当前的activity执行onPause和onStop方法,接着用户回到这个activity是就会调用这个方法。
  • onStart:表示activity正在启动,即将开始,这时候activity已经可见了,但是这里的可见不是我们眼睛所看到的可见,这时候activity还没有出现在前台,还不能和用户进行交互。
  • onResume:这里表示activity真的可见了,可以和用户进行交互了,onStart和onResume都表示activity可见了,但是两者是有区别的,onStart的可见是在后台,onResume的可见才真正出现在前台。
  • onPause:表示activity正在停止,正常情况下onStop方法就会接着别调用,但是有时候快速在回到当前的activity时,onResume会被调用,但是这是极端情况,用户一般很难重现这种情况。这里我们可以做一些存储数据,停止动画的工作,但是不能太耗时,不然会影响新的activity的打开,因为只有onPause执行完,新的activity的onResume才会被执行。
  • onStop:表示activity即将停止,这里可以做一些稍微重量级的回收工作,但是同样不能太耗时。
  • onDestory:表示activity即将销毁,这是activity生命周期的最后一个回调方法,我们在这里做一些回收工作和资源的释放。
    这里写图片描述

针对上图做以下说明:

  • 针对一个特定的activity,第一次启动,回调顺序为onCreate—onStart—onResume
  • 当用户打开新的activity或者回到桌面,回调为onPause—onStop,但是有一种特殊情况,当activity为透明主题时,onStop不会被调用
  • 用户再次回到activity,回调为onRestart—onStart— onResume
  • 到用户按back键回退时,回调onPause—onStop—onDestory
  • 当activity被系统回收后再次打开,生命周期方法回到过程和第一次一样,注意只是生命周期方法一样,不代表所有过程都一样
  • 从整个生命周期来看,onCreate和onDestory是配对的,分别标示着activity 创建和销毁,并且只可能执行一次,从activity是否可见来说,onStart和onStop是一对,两者可以被多次调用,从activity是否在前台来说,onResume和onPause是一对。两者也能被多次调用
  • onStart和onStop,onResume和onPause有什么实质不同?这两对接口在回调的意义上有所不同,onStart和onStop是就是否可见的角度进行回调的,onResume和onPause是就是否在前台显示的角度进行回调的,除了这点区别,在实际应用中没有其他明显区别
  • 从activityA跳到activityB,是A的onPause方法先执行,还是B的onResume方法先执行?从源码来看,或者直接打日志来看,都是A的onPause方法先执行,然后在执行B的onResume方法,所以在onPause方法中不能进行重量级的操作

2.异常情况下的生命周期
(1)情况1:资源相关的系统配置发生改变导致activity被杀死并重新创建
当系统配置发生改变后,activity就会被销毁,其onPause,onStop,onDestory方法都会被调用,同时因为系统是在异常情况下终止的,系统还会调用onSaveInstanceState()方法来保存当前activity的状态,这个方法的调用时机是在onStop之前,他与onPause没有前后关系,可以在onPause前也可以在onPause后调用,在正常情况下onSaveInstanceState()不会被调用,当activity重建的时候,系统会调用onRestoreInstanceState()方法,并且会把activity销毁时onSaveInstanceState保存的bundle对象作为参数传给onRestoreInstanceState,因此我们可以通过判断onRestoreInstanceState和onStart来判断activity是否被重建,如果被重建了,则取出当时的数据并且恢复,从时序上来说onRestoreInstanceState调用在onStart之后。
同时我们知道,在onSaveInstanceState和onRestoreInstanceState方法中,系统自动为我们做了一些工作,当activity异常情况下呗重建,系统会默认为我们保存activity的视图结构,并且在activity重启后恢复这些数据,例如文本框中用户输入的数据,listview滚动的位置,这些view相关的状态系统都能默认为我们恢复。具体针对一个特定的view系统能帮我们恢复哪些数据,我们可以查看view的源码,和activity一样,每一个view都有onSaveInstanceState和onRestoreInstanceState方法,看一下他们的具体实现,就可以知道系统可以为我们自动恢复哪些数据了。
关于保存和恢复view的层次结构,系统的工作流程是这样的:首先activity被意外终止时,activity会调用onSaveInstanceState去保存数据,然后activity委托Window去保存数据,接着Window再委托它上面的顶级容器去保存数据,顶级容器是一个viewgroup,一般来说它更可能是一个DectorView。最后顶级容器再去一一通知它的子元素保存数据,这样整个数据保存过程就完成了,这是一个典型的委托思想,上层委托下层,父容器委托子元素去处理事情,这种思想在Android中有很多应用,比如view的重绘过程,事件分发等都采用类似思想。
那textview来看,分析一下它保存了哪些数据,通过源码发现textview保存了自己的文本选中状态和文本内容,并且在onRestoreInstanceState方法中进行了恢复。这里要注意的是在接受文本的位置可以是onRestoreInstanceState或者oncreate,两者的区别在于,onRestoreInstanceState一旦调用就一定是有值的,我们不用额外的去判断它是不是为空,但是在oncreate中我们需要对它判断是否为空,官方建议使用onRestoreInstanceState。
(2)情况2:资源内存不足导致优先级低的activity被杀死
这种情况不好模拟,但是其数据的保存和恢复过程与情况1相同,对于activity的优先级做说明:

  1. 前台activity:正在和用户交互的activity优先级最高
  2. 可见但非前台activity:例如activity中弹出一个dialog,导致activity可见但是位于后台无法和用户进行交互
  3. 后台activity:已经暂停的activity,比如执行了onStop,优先级最低

当系统内存不足的时候,就会按照上面的优先级去杀死activity,然后执行onSaveInstanceState和onRestoreInstanceState去保存和恢复数据了,如果一个进程没有四大组件在执行,那么进程很快就会被杀死,因此一些后台工作不能脱离四大组件,比较好的方法就是将后台工作放在service中执行。
上面说到了系统的数据存储和恢复机制,我们知道系统配置改变时activity会重新创建,我们能不能不要让它重新构建呢,这时我们可以修改activity的configChanges属性

android:configChanges=”orientation”//这里让屏幕旋转就不重建activity了

configChanges的常见项目和意义

  • mcc :SIM卡唯一标识码
  • mnc:运营商标识码
  • locale:切换了系统语言
  • keyboard:使用了外插键盘
  • keyboardHidden:隐藏键盘
  • screenLayout:屏幕布局发生改变,比如用户激活了另外一个显示设备
  • fontScale:用户选择新字号
  • uiMode:比如是否开启夜间模式
  • orientation:旋转屏幕
  • screenSize:屏幕尺寸发生变化,这个选项比较特殊,minSdkVersion忽然TargetSdkVersion都低于13不会发生重启,否则会
  • smallestScreenSize:设备物理屏幕尺寸还是改变
  • layoutDirection:布局方向发生改变

二、Activity的启动模式

1.Activity 的LaunchMode

  • standard:标准模式,也是系统的默认模式,每次启动一个activity都会重新创建一个新的实例,注意standard模式的activity默认会进入启动它的activity所属的任务栈中
  • singleTop:栈顶复用式。在这种模式下,如何新的activity已经位于任务栈的栈顶,那么该activity不会被重建,同时它的onNewIntent方法会被回调,通过该方法我们可以获取当前的请求信息.需要注意的是,这个activity的oncreate和onStart不会被调用,因为它没有发生改变。如果新activity不是位于栈顶,还是会被重建
  • singleTask:栈内复用模式:这是一个单例模式,在这种模式中,只要栈中存在这个activity,多次调用就不会创建新的实例,与singleTop一样,系统也会代用onNewIntent 方法.先是看有没有需要的任务栈,然后在看实例,没有任务栈先创建任务栈,然后看实例,同时singleTask有clearTop的效果,会把该栈上面的activity全部清除出栈
  • singleInstance:单实例模式:这是一种加强的singleTask模式,除了具有singleTask的特点以外还这种模式下的activity只能单独位于一个任务栈中

前面多次提到某个activity所需的任务栈,那么什么是activity所需的任务栈呢,这里要从一个参数说起:TaskAffinity(任务相关性)这个参数表示了activity所需任务栈的名字,默认情况下,所有activity所需的任务栈的名字都是应用的包名,当然,我们可以为每一个activity都指定TaskAffinity,这个属性值不能和包名相同,否则相当于没有设置,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义另外,任务栈分前台任务栈和后台任务栈,后台任务栈的activity处于暂停状态。用户可以通过切换将后台任务栈再次调到前台。

当TaskAffinity和allowTaskReparenting结合的时候会产生特殊的效果。比如现在有两个应用A和B,A启动了B的activityC(allowTaskReparenting属性为true),然后home键回到桌面,然后在点击B应用,发现这个时候并不是启动B应用的主activity,而是重新显示了A启动 的activityC,或者说C从A的任务栈转移到了B的任务栈中,这么理解,因为A启动了C,所有C应该处于A的任务栈中,但是呢C又属于B应用,正常情况下C的TaskAffinity肯定不可能和A的任务栈相同,因为包名不同,所有B被启动后,B创建 了自己的任务栈,这个时候系统发现C原本所想要的任务栈已经被创建 了,所以就把C从A中转移出来了。

如何制定Activity的启动模式呢

  1. 清单文件中
  2. 通过Intent设置占位符实现

这两种方式都可以制定activity的启动模式,但是是有区别的。首先优先级上第二种方式是高于第一中的,两者同时存在,一第二种为准。再者,两者设置的范围不一样,第一种不能为activity设置FLAG_ACTIVITY_CLEAR_TOP表示,第二种不能设置singleInstance模式

2.Activity的Flags

  • FLAG_ACTIVITY_NEW_TASK: 类似singleTask
  • FLAG_ACTIVITY_SINGLE_TOP:类似singleTop
  • FLAG_ACTIVITY_CLEAR_TOP:将它前面的activity都清除出栈,一般和FLAG_ACTIVITY_NEW_TASK配合使用
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:不会出现在activity的历史列表中

三、IntentFilter的匹配规则

我们知道activity的启动方式有两种,一种是显示调用,一种是隐式调用,显示调用需要明确的指定被启动对象的组件信息,包括包名和类名,而隐式调用则不需要指定组件信息。原则上一个intent不应该即使显示调用又是隐式调用,如果两者共存的话以显示调用为主。
显示调用很简单,下面介绍隐式调用,隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配则无法启动,IntentFilter过滤信息包括action,category,data。
为了匹配过滤列表,需要同时匹配过滤列表中的action,category,data信息,否则失败,一个过滤列表中action,category,data可以有多个,所有的action,category,data分别构成不同类别,同一类别的信息共同约束当前类别的匹配过程。只有一个intent同时匹配action,category,data类别才能算匹配成功。另外,一个activity可以有多个IntentFilter,一个intent只要匹配任意一组就可以启动对于的activity。

<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>

各个属性的匹配原则

  1. action:一个IntentFilter可以有多个action,只要intent匹配任意一个就算成功,intent中必须要有的且和过滤规则中某个相同,其区分大小写
  2. category:它要求如果intent中含有category,那么所有的category都必须和过滤规则中的其中一个category相同。换句话说,intent中出现了category,不管有几个category,对于每个category而言,它必须是过滤规则中已经存在的category,当然intent可以没有category,按照上面描述依然可以匹配成功。为什么不设置category也可以匹配成功呢?原因系统在startactivity或者startactivityforresult时添加了“android.intent.category.DEFAULT”这个默认的category。所有能够匹配成功。同时,为了我们的activity能够接受隐式调用,必须在intent-filter中加上“android.intent.category.DEFAULT”这个默认的category
  3. data: data匹配原则和action相似。data分两部分,mimeType和URI。miniType指媒体类型,比如image/jpeg,audio/mpeg4-gneric和video/*等,而URI中就包含很多消息了。

scheme:URI格式,如http,file,content等,如果URI没有直达scheme,那么整个URI无效
host:uri主机名,如:www.baidu.com,未指定也无效
port:端口号
path:代表完整路径信息
pathPattern:表示完整路径信息,包括通配符
pathPrefix:代表路径前缀信息

匹配原则:它要求intent中必须有data数据,并且data 数据能够完全匹配过滤规则中某一个data。注意intent要指定完整的data,必须用setDataAndType方法

 Intent intent = new Intent("com.ryg.charpter_1.c");
                intent.putExtra("time", System.currentTimeMillis());
                intent.addCategory("com.ryg.category.c");
                intent.setDataAndType(Uri.parse("file://abc"), "text/plain");
                startActivity(intent);

最后,当我们通过隐式调用一个activity的时候,可以先做一下判断,看是否有activity匹配我们的隐式intent,判断方法有两种

  1. PackageManager的resolveActivity方法
  2. Intent的resolveActivity方法

如果返回为空则代表找不到
PackageManager的queryIntentActivities可以返回所有匹配成功的activity信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值