深入理解Activity的启动模式

2019-08-20  本文已影响0人  不会游泳的金鱼_

面试的时候,面试官经常会和你聊聊Activity的启动模式,但Activity启动牵扯的知识点其实很多,并非能单单用四个启动模式就能概括的,默认的启动模式的表现会随着Intent Flag的设置而改变。今天就来深入聊聊Activity的启动模式。

基本的启动模式

学Android的都知道,Activity有4种启动模式:

  1. 标准模式 standard : 每次都会启动一个新的Activity实例,这是默认的启动模式。此模式下启动的Activity会进入启动它的Activity所在的任务栈中。
  2. 栈顶复用模式 singleTop :如果Activity实例位于当前任务栈顶,就重用栈顶实例,而不新建,并回调该实例onNewIntent()方法,否则走新建流程。
  3. 栈内复用模式 singleTask : 只要该Activity在一个任务栈中存在,就不会重新创建,而是将目标Activity上面的Activity全部出栈,然后回调 onNewIntent(intent)。 方法。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,并把该Activity放进去;如果存在,就会创建到已经存在的栈中。
  4. 单例模式 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有点复杂,它涉及到的情况较多。

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也有相应的方法可以用来判断。

上一篇下一篇

猜你喜欢

热点阅读