activity启动模式你所不知道的异常情况

前言

虽然了解activity的四种启动模式,但是在一些复杂场景下,各种启动模式会出现的现象,以及现象的原因并不清楚,再加上个taskAffinity launchMode clearTaskOnLaunch 这些参数会使得更加懵逼。所以根据在实际应用中遇到的问题总结一下。

主要内容

要讲启动模式需要从Task ,taskAffinity 以及launchMode,还有标签四个方面入手,看这四个之前的关联以及影响。
在这里插入图片描述

Task

task跟activity的启动息息相关,因为activity启动后都是放在task里面进行管理的,task的数据结构是stack的,先进后出,新创建的activity放在task的顶部,如下图打开ActivityA->activityB->activityC:
在这里插入图片描述

task的特点:

  1. activity的集合
  2. 以栈的形式对activity进行管理(back stack)
  3. task里面至少包含一个activity
  4. 新创建的activity放在栈顶。
  5. 每一个task都有称为Affinity的name。

TaskAffinity

taskAffinity是activity可以在manifest文件里面设置的属性.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.coroutinescopedemo">

    <application android:allowBackup="true">
        <activity
            android:name=".MainActivity"
            android:taskAffinity="hanking.edu">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
复制代码

用来确定启动的activity属于哪个task,或者确定task的名称。具体的功能如下:

  1. 用来决定持有activity的task是哪个。

  2. 默认情况下一个app里面的activity都有相同的affinity值(package name)

  3. task的affinity值由触发创建task的activity的affinity值决定。(也被称为root activity)

taskAffinity用来确定activity所在栈的名字,是不是任何时候都会生效?看下默认情况下的两个activity设置不同的affinity会发生什么情况。

1、给activity设置task affinity 如下创建了activityA和activityB,其中给activityB设置了task Affinity值为com.something.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myApp">

    <application
        android:allowBackup="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ActivityA">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ActivityB"
            android:taskAffinity="com.something" />
    </application>

</manifest>
复制代码

流程:打开activityA 从activityA跳转到ActivityB,然后打印task的情况。 通过adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'打印情况如下

adb shell dumpsys activity activities | sed -En -e '/Running activities/,/Run #0/p'
    Running activities (most recent first):
      TaskRecord{5938ae7 #1669 A=com.example.coroutinescopedemo U=0 StackId=287 sz=2}
        Run #1: ActivityRecord{5d93c09 u0 com.myApp/.ActivityB t1669}
        Run #0: ActivityRecord{5ce5f59 u0 com.myApp/.ActivityA t1669}

复制代码
在这里插入图片描述

有上面流程可以知道,activity默认启动情况下加task affinity属性并不会新建task,也不会改变task名称,task的名称和taskRoot的activity中设置的task affinity值一致,如果没设置默认就是包名,这里taskRoot Activity是activityA。

2、添加FLAG_ACTIVITY_NEW_TASK 由上面可见,默认模式下就算给activity添加了taskAffinity属性也不会多创建一个task,原因是这个taskAffinity应该和FLAG_ACTIVITY_NEW_TASK一起使用才会创建新task。

 val i = Intent(this, ActivityB::class.java)
            i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            startActivity(i)
复制代码

在ActivityA中启动ActivityB的时候加上Intent.FLAG_ACTIVITY_NEW_TASK的flag,再尝试从ActivityA打开ActivityB。

  Running activities (most recent first):
      TaskRecord{59b097b #1675 A=com.something U=0 StackId=293 sz=1}
        Run #0: ActivityRecord{5d93041 u0 com.example.myApp/.ActivityB t1675}
    Running activities (most recent first):
      TaskRecord{59b09a0 #1674 A=com.example.myApp U=0 StackId=292 sz=1}
        Run #0: ActivityRecord{5d2de41 u0 com.example.myApp/.ActivityA t1674}

复制代码

由上面的信息可以看到有两个task,ActivityB所在的task 名称是com.something, stackId=293, ActivityA所在的task名称是com.example.myApp stackId=292
在这里插入图片描述

思考:加上Intent.FLAG_ACTIVITY_NEW_TASK的tag后由于启动了一个新的task,这时候退到任务管理器可以看到activityA和activityB所在的task,如果这个时候点开activityB再点击返回还会返回到activityA吗? 答案是不会?因为从activityA打开activityB后再切到后台,这个时候这两个activity的task都属于background状态,再打开activityB的task,activityB的task属于foreground task,返回会直接返回到桌面。

activity启动模式

activity的启动模式一般分为以下四种,四种模式的特点如下:
在这里插入图片描述

Standard:

activity默认的启动模式,在standard模式下每次打开一个activity的时候都会生成一个新的实例。 如下已经有了A,B,C,D在stack中,再启动B,B是standard模式。 A →B→ C→D 启动B, A → B → C→D→ B 可以看到会再次生成B的实例,并放到栈顶。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myApp">

    <application
        android:allowBackup="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ActivityA">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ActivityB"
            android:taskAffinity="com.something" />
    </application>

</manifest>
复制代码
 val i = Intent(this, ActivityB::class.java)
            startActivity(i)
复制代码

在标准模式下启动ActivityA->ActivityB->activityB如下图:
在这里插入图片描述

standard模式看起来非常简单,每次生成activity实例并放在栈顶,但是当standard模式和flag一起使用的时候又会产生很多不一样的效果。 1、standard+Intent.FLAG_ACTIVITY_NEW_TASK 按照下面的方式启动activity

启动 Activity A 
ActivityA 启动 ActivityB (no flags)
ActivityB 启动 Activity A
ActivityA 启动 Activity B (with flag NEW_TASK)
ActivityB 启动 ActivityA
复制代码
在这里插入图片描述

由上图可知当activityA启动activityB,并且此时intent加上NEW_TASK标签时,会生成一个新的task com.something,并且activityB作为taskRootActivity,此时activityB再启动activityA,activityA也会在com.something的栈上生成实例。

SingleTop

如果需要打开的activity的实例已经处于当前栈顶,那么会复用当前栈顶的activity,不会重新创建activity,但是会通过调用onNewIntent().所以如果需要刷新页面数据,就要在onNewIntent().进行处理。如果栈顶的activity和需要打开的activity不相同,那么会重新创建一个activity,并进栈。 假设栈里面已经有A ,B,C几个activity, . A →B →C 如果需要再打开activity B那么如下: A →B →C →B 如果这个时候再调用打开activity B会直接复用栈顶的B,并且调用B的onNewIntent()方法,栈如下 A →B →C →B

Step 1: Launch A -> A
Step 2: Launch B -> A-B 
Step 3: Launch C -> A-B-C 
Step 4: Launch B -> A-B-C-B
Step 5: Launch B -> A-B-C-B
复制代码

singleTop总结一句:就是复用栈顶activity。

SingleTask

如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。

Step 1: Launch A -> A
Step 2: Launch B -> A-B 
Step 3: Launch C -> A-B-C 
Step 4: Launch B -> A-B
Step 5: Launch B -> A-B*
复制代码

如上当栈中有A-B-C此时再启动B,会遍历栈,然后找到B,将B上面的C出栈。

singleInstance

singleInstance模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。 假设有三个activity:A,B,C,B是singleInstance的,

Step 1: Launch A -> Task 1: A
Step 2: Launch B -> Task 1: A
                   Task 2: B    // Visible to the user
Step 3: Launch C -> Task 1: A-C  // Visible to the user
                   Task 2: B
Step 4: Launch B -> Task 1: A-C
                   Task 2: B*   // Visible to the user
复制代码

FLAG_ACTIVITY_NEW_TASK

Start the activity in a new task. If a task is already running for the activity you are now starting, that task is brought to the foreground with its last state restored and the activity receives the new intent in onNewIntent().

复杂场景分析

上面都是简单的场景,如果选择更加复杂的场景,又会出现意想不到的现象。
在这里插入图片描述

1、task切换后打不开activity

  1. 启动 ActivityA
  2. Activity A 启动 Activity B
  3. Activity B 启动 Activity C with FLAG_NEW_TASK
  4. Activity C 启动 Activity D
  5. 用户切换到 com.myApp
  6. Activity B 启动 Activity C with FLAG_NEW_TASK
在这里插入图片描述

上面有个比较奇怪的现象activityB启动activityC,activityC 启动activityD,都是new_task方式,C,D,都是在com.something的task里面,这里正常,但是activityB,再次调用activityC的时候却没有启动activityC。 2、task切换后打开多个activity

在这里插入图片描述


 1\. 启动 Activity A
 2\. Activity A 启动 Activity B
 3\. Activity B 启动 Activity C with FLAG_NEW_TASK
 4\. Activity C 启动 Activity D
 5\. User switches to com.myApp
 6\. Activity B 启动 Activity D with FLAG_NEW_TASK

复制代码

上面流程启动activity的,task是什么情况?
在这里插入图片描述

看上图,最后当activityB其次启动activityD的时候又创建了一个activityD,

当activityB启动activityD的时候为什么会新创建一个activityD的实例? 这里重新创建activityD的原因是:通过activityC创建activityD的intent和通过activityB创建activityD的intent的不一样导致的。也就是说只有当intent的一样时才不会创建多个实例。

startActivityForResult 异常

在这里插入图片描述

activityA是singleInstance,activityB是standard模式,当activityA调用startActivityForResult打开activityB时,activityA收到的回调函数onActivityResult能接受activityB的返回结果吗? 正常情况下activityA通过startActivityForResult打开activityB时的流程如下:

  1. activityA 重写onActivityResult接受activityB中的返回值。
  2. activityB通过setResult确定返回值。

3.当activityB返回时activityA的onActivityResult才会被回调。

但是当activityA定义为singleInstance时通过startActivityForResult打开activityB时如下:
在这里插入图片描述

因为activityA是singleInstance的所以独享一个task,当activityA打开activityB时,也创建一个新的task,但是onActivityResult是立马就回调,不是activityB finish的时候回调的。这是什么原因? 注意:当一个activity 调用startActivityForResult打开另一个activity当时另一个activity在另一个创建的task里面的时候,onActivityResult就会立刻回调。

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

推荐阅读更多精彩内容