Activity的生命周期
Activity的生命周期一般分为两部分,一是正常情况下的生命周期,也就是一般就是这样,而是异常情况下的生命周期。
典型情况下
典型情况下是指在有用户参与的情况下,activity所经过的生命周期的改变。这时activity会经历如下生命周期:
- onCreate:表示activity正在被创建。做初始化工作,例如调用setContentView加载界面布局资源、初始化activity所需的数据等。
- onRestart:表示activity正在重新启动。一般情况下当当前activity从不可见到重新变为可见状态时,会被调用。这种情形一般是用户行为所致。
- onStart:表示activity正在被启动,即将开始。这时的activity已经可见了,但是还没有出现在前台,还无法和用户交互。可以理解为:activity已经显示出来了,但是我们还看不到
- onResume:表示activity已经可见了,并且出现在前台并开始活动。和onStart的区别,onStart这时在后台,onResume才显示到前台
- onPause:表示activity正在停止。正常情况下,onStop会紧接着被调用。但有可能这个时候突然快速回到当前activity,onResume就会被调用。
onPause的时候可以做一些存储数据(将数据存储到数据库,存储到本地文件等)、停止动画等工作。这些操作不能太耗时(可以开一个线程),因为会影响到新activity的显示。onPause必须先执行完,新activity的onResume方法才会执行
- onStop:表示activity即将停止。可以做一些轻量级的工作,不可以太耗时。例如保存页面上的一个字符串之类的。
- onDestory:表示activity即将被销毁。最后一个回调,可以做一些回收工作和最终资源的释放。
异常情况下
异常情况下是指activity被系统回收或由于当前设备的configuration发生改变而导致activity被销毁重建。
情况1:资源相关的系统配置发生改变
发生这种情况的典型例子就是屏幕处于竖屏时突然被旋转。
我之前写过一个例子,底部导航栏+fragment旋转后三个fragment重叠在一起了。原因就是activity重建,把三个fragment重新按顺序创建并显示了。解决办法就是使用onSaveInstanceState保存当前所在的fragment页面,在旋转后从onRestoreInstanceState()方法中恢复旋转之前所在页面,其他页面重新创建。就是保留之前所在页面的fragment对象。
这两个保存和恢复数据的方法的调用流程:
- onPause-->onSaveInstanceState-->onStop
- onstart-->onRestoreInstanceState-->onResume
情况2:资源内存不足导致低优先级activity被杀死
当系统内存不足是就会按照优先级杀死activity所在的进程。
activity的优先级有三种:
- 前台activity:正在和用户交互的activity,优先级最高
- 可见非前台activity:可见但无法交互。例如:activity上弹出一个对话框
- 后台activity:不可见,被暂停,比如执行了onStop(),优先级最低。
如果一个进程中没有四大组件在执行,那么这个进程很快会被系统杀死。因此后台工作不应该脱离四大组件,最好的方法是将后台工作放在Service中从而保证一定优先级。
Activity的启动模式
为什么需要启动模式
在默认情况下,当我们多次启动同一个activity时,系统会创建多个实例并把他们加入到任务栈中,当我们单击back键的时候,这些activity又会从任务栈中依次退回。是一种“后进先出”的栈结构。当栈中无任何activity时,系统就会回收这个任务栈。这是android的默认启动模式。但是多次启动同一个activity,系统重复创建多个实例,这样是很不合理的。因此它提供了启动模式来修改系统的默认行为。
standard 标准模式
系统的默认启动模式。它的启动具有如下特点:
- 每次启动一个activity都会创建一个新的实例,不管这个实例是否已经存在
- 被创建的实例的生命周期符合典型情况下activity的生命周期
- 一个任务栈中有多个实例,每个实例也可以在不同的任务栈中(谁启动了这个activity,这个activity就运行在启动它的activity的任务栈中)
当用ApplicationContext去启动standard模式的activity的时候会报错。因为standard模式下的activity会进入启动它的activity所在的任务栈中,但是非activity类型的context,就像ApplicationContext并没有所谓的任务栈,所以就会有问题。解决问题的方法就是,为待启动的acitivity指定FLAG_ACTIVITY_NEW_TASK标志位,这样activity启动的时候就会为它自己创建一个新的任务栈。这个时候的启动实际是以singleTask启动的。
singleTop 栈顶复用模式
它有如下特点:
- 新activity已经位于任务栈栈顶,此activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数可以取出当前请求的信息
- 这个activity的onCreate()、onStart()方法不会被系统调用
- 新activity实例已存在但不是位于栈顶,新activity仍旧会被重建
singleTask 栈内复用模式
它有如下特点:
- 是一种单例模式,只要activity在一个栈中存在,activity多次启动实例不会重复创建
- 和singleTop一样,系统也会回调onNewIntent方法
- 如果当前的activity已被创建且在栈中,但不位于栈顶,系统会将此activity之上的activity都出栈。
例如一个栈中有四个activity从底到顶分别为ADBC,现在要创建D,发现栈中有,根据栈内复用原则,D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法。同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的activity全部出栈,最终栈内情况为AD。
流程如下:
image
singleInstance 单实例模式
它有如下特点:
- 单实例模式。是一种加强的singleTask模式,具有singleTask的所有特性还有一点,activity只能单的地位于一个任务栈中。
- 系统会为这个activity创建一个新的任务栈,其独自运行在这个栈中。根据栈内复用特性,后续的请求均不会创建activity,除非这个独特的任务栈被销毁了。
一个参数:TaskAffinity
-
TaskAffinity可以翻译为任务相关性。这个参数标识了一个activity所需要的任务栈的名字。
-
可以为每个activity都单独指定TaskAffnity,这个属性值必须不能和包名相同,否则就没有意义了。
-
TaskAffinity属性主要和singleTask启动模式或者allowTaskReparent属性配对使用,在其他情况下没有意义。
-
任务栈分为前台任务栈和后台任务栈,后台任务栈中的activity位于暂停状态,用户可以通过切换将后台任务切换到前台任务。
-
当TaskAffinity和singleTask同时存在,当前activity会运行在名字和TaskAffinity一样的任务栈中
在书上看到这的时候对任务栈分为前台和后台不是很理解,android系统会为每一应用程序都设置一个后台栈么?前台栈的东西都移到后台?这样不会太麻烦了吧。。。查了下,事实并不是我想的那样,当前应用通过home键回到主屏幕的时候,之前的前台栈就会变成后台栈。系统会根据这个应用在后台待的时长去判断是否只保留最初的MainActivity,还是从栈顶的activity进行恢复。
这里有两个例子,感觉会加深一些理解:
- 应用A启动了应用B的某个activity,若这个acitivty的属性allowTaskReparent = true。那么再次启动B后,此Activity会直接从A的任务栈中转移到B的任务栈中。
这主要就是属性allowTaskReparent的功劳,这个属性大概意思就是允许当前任务从新找一个父母。也就是重新启动的时候可以到另一栈中。
- 假设目前有两个栈,前台任务栈的情况为AB,后台任务栈的情况为CD。假设CD的启动模式均为singleTask,现在请求启动D。
结果是后台任务都会被切换到前台,也就是CD任务都到了前台任务栈中,前台任务栈现在的情况为ABCD。
- 假设现在有三个activity。activity A的启动模式为standard,activity B和activity C的启动模式都为singleTask,且B C的TaskAffnity的属性都一样。做如下操作:A启动B,B启动C,C再启动A,A再启动B,最后再按两次返回键,结果会如何?
结果是回到了桌面。其实这个问题画个图想想就很简单。A启动B,A因为是standard模式,所以A会在自己的包名命名的栈中。而B C因为是singleTask模式,在启动B的时候就创建好了以B的TaskAffnity命名的栈中,创建C的时候,系统发现已经有了所需要的栈,因此现在栈中有BC。C再启动A,A是standard模式,系统会再次创建一个新的A对象,A进入启动它的C所在的栈中,其实栈里有BCA。再次A启动B,B有自己的栈,且根据singleTask启动模式,此时这个栈中有B对象,因此B对象上的所有对象都会弹出,B执行onNewIntent方法。此时这个栈中只剩下了B。按下back键后,B弹出,任务栈为空,栈被销毁,此时还剩下最初的A所在的栈,这时候回到后台任务栈将A显示出来,再back就回到了桌面。
通过这三个例子就能进一步理解activity的启动模式。
指定启动模式的两种方法
1. 在AndroidMenifest.xml中
<activity
android:name = "com.lxy.test.MainActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"/>
2. 在Intent中设置标志位
Intent intent = new Intent();
intent.setClass(MainActivity.this,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
这两种方式的区别:
- 第二种的优先级高于第一种。两者同时存在时,以第二种为准。
- 限定范围上有所不同。第一种无法设置FLAG_ACTIVITY_CLEAR_TOP标志,第二种无法指定singleInstance模式。
Activity Flags
这里会展示一些比较常用的标记位
- FLAG_ACTIVITY_NEW_TASK:指定singleTask模式
- FLAG_ACTIVITY_NEW_TOP:指定singleTop模式
- FLAG_ACTIVITY_CLEAT_TOP:启动时,位于当前activity之上的所有activity都会出栈。这个标记位一般默认和singleTask模式默认出现
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的activity不会出现在历史activity的列表中。等同于在xml中的属性android:excludeFromRecents="true"。不太懂这个历史activity列表,是从这个activity到另一个activity,再返回的时候这个activity已经不存在了么,相当于被finish了?在网上没有查到解释这个的意思。
IntentFilter匹配规则
在书上看到这一部分才会想起来,原来IntentFilter是用在隐式调用的。刚开始学android的时候就学过这,不过后来因为每次启动一个活动的时候都用的是显示启动把这块就淡忘了。趁着这个机会再复习一下。
首先先向下隐式启动一般可以用在什么时候,它可以弥补显示启动的哪些。比如说我现在想调用百度的网页,就可以使用显示启动。通过intent的setData将百度网址传进去,调用系统浏览器就可以打开了。下面看看这个IntentFilter需要哪些规则。
intent-filter匹配规则
- intent-filter中所设置的过滤信息,如果不匹配将无法启动目标activity.intent-filter的过滤信息有action、category、data.
- 一个过滤列表中的action category data可以有多个,所有的action category data分别构成不同的类别。但只有一个Intent同时匹配这三个规则才算完全匹配。
action匹配规则
action是一个字符串,系统预定义了一些action,同时也可以在应用中定义自己的action。
- intent中必须有action
- 匹配是指字符串完全相同,区分大小写
- 一个过滤中可以有多个action,intent能够与任意一个action相同则匹配成功。
- 如果过滤中有action,intent中action不存在时,过滤失败
category匹配规则
- 如果intent中有category,那么不管有几个,每个category都必须是intent-filter中定义的
- 如果intent中没有category,那么必须在intent-filter中指定“android.intent.category.DEFAULT”
- 系统在调用startActivity和startActivityForResult的时候,会默认为intent加上“android.intent.category.DEFAULT”
data匹配规则
data的匹配规则和action类似,如果过滤规则中定义了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由两部分组成,mimeType和URI。
mimeType
媒体类型:image/jpeg audio/mpeg4-generic video/*等 可以表示图片、文本、视频等不同的媒体格式
URI
URI的结构:
<scheme:>//host:port/[<path>|<pathPrefix>|<pathPattern>]
实际的例子:
content://com.example.project:200/folder/subfolder/ect
http://www.baidu.com:80/search/info
每个数据的含义:
- Scheme:URI模式,比如:http、file、content等。若URI中没有指定这个,则无效。
- Host:主机名。
- Port:端口号
- Path、pathPrefix、pathPattern:路径信息
- path:完整的路径信息
- pathPattern:可以包含通配符,表示0个或多个任意字符
- pathPrefix:路径前缀信息
启动前判断
通过隐式Intent启动一个activity的时候,可以做下判断,看是否有activity能够匹配隐式intent。
- PackageManager的resolveActivity()/Intent的resolveActivity()
返回最佳匹配的activity信息,否则返回null。
- PackageManager的queryIntentActivities()
返回所有成功匹配的activity信息。
public abstract ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags)
public ComponentName resolveActivity(@NonNull PackageManager pm)
public abstract List<ResolveInfo> queryIntentActivities(Intent intent, int flags)
lags一般填MATCH_DEFAULT_ONLY,表示只匹配intent-filter中声明default category的Activity。
一般在MainActivity注册的时候系统都会自动生成这个:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
他们的共同作用是表示这是一个activity的入口,并且会出现在activity的应用列表中,二者缺一不可。Service BroadCastReceiver,PackManager同样提供了类似的方法获取成功匹配的信息。
网友评论