深入理解Activity的启动模式

面试的时候,面试官经常会和你聊聊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有点复杂,它涉及到的情况较多。

  • 如果单独使用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也有相应的方法可以用来判断。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容