深入理解Activity的启动模式
面试的时候,面试官经常会和你聊聊Activity的启动模式,但Activity启动牵扯的知识点其实很多,并非能单单用四个启动模式就能概括的,默认的启动模式的表现会随着Intent Flag的设置而改变。今天就来深入聊聊Activity的启动模式。
基本的启动模式
学Android的都知道,Activity有4种启动模式:
- 标准模式 standard : 每次都会启动一个新的Activity实例,这是默认的启动模式。此模式下启动的Activity会进入启动它的Activity所在的任务栈中。
- 栈顶复用模式 singleTop :如果Activity实例位于当前任务栈顶,就重用栈顶实例,而不新建,并回调该实例onNewIntent()方法,否则走新建流程。
- 栈内复用模式 singleTask : 只要该Activity在一个任务栈中存在,就不会重新创建,而是将目标Activity上面的Activity全部出栈,然后回调 onNewIntent(intent)。 方法。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,并把该Activity放进去;如果存在,就会创建到已经存在的栈中。
- 单例模式 singleInstance : 具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例。singleInstance算是加强型的singleTask模式,也就是说它也有栈内复用的特性,多次请求不会重复创建,也会调用onNewIntent(intent)方法。
相信上面的内容大家都知道,不过上面的场景仅仅适用于Activity启动Activity,并且采用的都是默认Intent,没有额外添加任何Flag,否则表现就可能跟上面的完全不一致。
其他启动模式
大家都知道,启动Activity是通过Intent的,而Intent是有一个Flags属性的,可以用intent.setFlags()方法来设置。当我们设置了一个或多个Flag后,Activity的启动方式有可能就会和之前的描述不同了。
taskAffinity
每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。
如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。而Task也有自己的affinity属性,它的值等于它的根Activity的taskAffinity的值。
Flags
Flag 有很多,我就重点说几个重要的。
FLAG_ACTIVITY_NEW_TASK
Intent.FLAG_ACTIVITY_NEW_TASK是启动模式中最关键的一个Flag。是栈内复用模式的关键属性。
我们知道,非Activity的Context(如Service)是没有任务栈的,那么用standard模式启动Activity时就会出现问题,需要给Intent设置flag,Intent.FLAG_ACTIVITY_NEW_TASK,强制Activity选择新的任务栈(这就和singleTask一样了)。
不设置会直接报异常,在startActivity()方法的底层ContextImpl.startActivity()中是有检验的。不过,在Android7.0、8.0系统源码有Bug,不会报异常,9.0恢复正常。
//ContextImpl Android6.0
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
//检验是否添加了 FLAG_ACTIVITY_NEW_TASK
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
依据该Flag启动模式可以分成两类,设置了该属性的与未设置该属性的。singleTask及singleInstance在AMS中被预处理后,会自动设置为Intent.FLAG_ACTIVITY_NEW_TASK。standard及singletTop的Activity不会被设置Intent.FLAG_ACTIVITY_NEW_TASK,可以通过显示的Intent.setFlags进行设置。
Intent intent = new Intent(TestService.this, SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
FLAG_ACTIVITY_CLEAR_TASK
个属性必须同FLAG_ACTIVITY_NEW_TASK配合使用。
设置了FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK后,如果目标task已经存在,将清空已存在的目标Task,否则,新建一个Task栈,之后,新建一个Activity作为根Activity。
Intent.FLAG_ACTIVITY_CLEAR_TASK的优先级非常高,基本可以无视所有的配置,包括启动模式及其他的Intent Flag,哪怕是singleInstance也会被finish,并重建。
FLAG_ACTIVITY_CLEAR_TOP
这个Flag有点复杂,它涉及到的情况较多。
-
如果单独使用Intent.FLAG_ACTIVITY_CLEAR_TOP,并且没有设置特殊的launchmode,那么,Google官方的示例是:如果ABCD Task中的D采用FLAG_ACTIVITY_CLEAR_TOP唤起B,这个时候首先会将CD出栈,但是至于B是否会重建,要视情况而定。
-
如果没有设置FLAG_ACTIVITY_SINGLE_TOP,则会将B finish掉,之后创建新的入栈。如果当前栈中没有目标Activity则直接新建,而不会去其他栈中寻找。
-
如果同时设置了FLAG_ACTIVITY_SINGLE_TOP,则在当前栈已有的情况下就不会重建,而是直接回调B的onNewIntent()。当前栈没有依旧是直接新建。
-
如果同时使用了FLAG_ACTIVITY_NEW_TASK,这个时候,目标是Activity自己所属的Task栈,如果在自己的Task中能找到一个Activity实例,则将其上面的及自身清理掉,之后重建。
-
如果同时在加上FLAG_ACTIVITY_SINGLE_TOP,则会更特殊一些,如果topActivity不是目标Activity,就会去目标Task中去找,并唤起。如果topActivity是目标Activity,就直接回调topActivity的onNewIntent,无论topActivity是不是在目标Task中。
FLAG_ACTIVITY_SINGLE_TOP
这个Flag和singleTop作用是一样的,在Task栈顶有的话,就不新建,栈顶没有的话,就新建,这里的Task可能是目标栈,也可能是当前Task栈。
IntentFilter相关
大家都知道,启动Activity分为两种方式,显式和隐式调用。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配是无法启动目标组件的。
IntentFilter中过滤的信息有action、category、data,保存在Activity配置文件的intent-filter标签下。只有同时匹配有action、category、data才算是匹配成功。但是一个Activity可以有多个intent-filter标签,只要有一组intent-filter标签匹配,即可启动对应的Activity。
一个普通的 <intent-filter>标签如下:
<activity android:name=".demo.MyActivity">
<intent-filter>
<action android:name="bupt.edu.demo.a1"/>
<action android:name="bupt.edu.demo.a2"/>
<category android:name="bupt.edu.demo.c1"/>
<category android:name="bupt.edu.demo.c2"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
action的匹配
action是一个字符串,系统本身是有些预定义的action的,不过我们也可以自己指定action。action的匹配规则是:
Intent中的action必须和action标签下的字符串完全一致,但是当<intent-filter>标签下有多个action标签时,只需要匹一个action就算是成功匹配。例如在上面的例子中,只要我们的action值为"bupt.edu.demo.a1"或者"bupt.edu.demo.a2"即可算是匹配成功。
不过,需要注意的是Intent中如果没有指定action,那么匹配失败。另外,action是区分大小写的,大小写不同也会匹配失败。
即,action的匹配要求Intent中的action必须存在且和过滤规则中的一个action完全一致。
category
category也是一个字符串,系统本身也有些预定义的category,我们也可以自己指定category。category的匹配规则是:
Intent中如果存在category,无论存在几个,每个都必须能够匹配到<intent-filter>标签下的category才算匹配成功。不过,Intent如果没设置category是可以匹配成功的。因为系统在调用startActivity或startActivityForResult时会默认在intent中添加android.intent.category.DEFAULT这个category。
因此我们在配置activity时,必须加上android.intent.category.DEFAULT这个category,才能被隐式调用。
data
data的语法有些复杂,主要由mimeType(用于指定媒体类型)和URI组成。在匹配时通过intent.setDataAndType(Uri data, String type)方法对data进行设置。
setData()方法和setType()方法会将对方的值清除设为null,因此不能同时调用两个方法来设置data。
另外,如果我们要启动隐式启动Activity,应该先判断是否有对应的Activity可以启动。可以用PackageManager的resolveActivity方法或者Intent的resolveActivity方法判断是否有Activity匹配该隐式Intent。
PackageManager的resolveActivity方法会返回最佳匹配的Activity信息,没匹配到的话会返回null。
Intent的resolveActivity方法,会返回所有匹配到的Activity信息,没匹配到的话会返回null。
同样,针对Service、BoardcastReceiver也有相应的方法可以用来判断。