深入理解Activity启动模式

1.前言

  • 启动模式在面试中经常被问到,实际开发中应用广泛,所以是必须掌握的一种技能
  • 下面,我将详细介绍启动模式的相关知识,希望你们会喜欢
  • 第一篇文章,若有纰漏,欢迎指出,必改

2.目录

  • Activity启动模式

  • ActivityStack,TaskRecord,ActivityRecord,Activity关系

  • taskAffinity

  • allowTaskReparenting

  • 实例

  • 总结

3.Activity启动模式

3.1 standard(标准模式)

  • 不复用Activity,startActivity会启动一个新的Activity实例并且置于栈顶,即使栈内已经有这个Activity实例
  • 举例:
    当前Activity栈已经存在ABC三个Activity,需要新启动C,则Activity栈变成ABCC,并不会对原来的C进行复用

3.2 singleTop(栈顶复用模式)

  • 栈顶复用,startActivity启动一个新的Activity时,当前栈顶已经存在这个Activity实例,不会启动新的Actiivty,直接复用当前栈顶Activity,并且回调Activity.onNewIntent方法,如果栈内已有这个Activity但是不在栈顶,则不复用直接启动一个新的Activity实例
  • 举例:
    1. 当前Activity栈已经存在ABC三个Activity,需要新启动D(singleTop),则Activity栈变成ABCD
    2. 当前Activity栈已经存在ABC三个Activity,需要新启动C(singleTop),则Activity栈还是ABC,C被复用并且回调了C的onNewIntent方法
    3. 当前Activity栈已经存在ABC三个Activity,需要新启动B(singleTop),则Activity栈变成ABCB,不会对B进行复用

3.3 singleTask(栈内复用模式)

  • 栈内复用,startActivity启动一个新的Activity时,当前栈内已经存在这个Activity实例,不会启动新的Actiivty,直接复用当前栈内Activity,将这个Activity之上的Activity出栈,并且回调Activity.onNewIntent方法
  • 举例:
    0. 前提是taskAffinity相同
    1. 当前Activity栈已经存在ABC三个activity,需要新启动D(singleTask),则Activity栈变成ABCD
    2. 当前Activity栈已经存在ABC三个activity,需要新启动C(singleTask),则Activity栈还是ABC,C被复用并且回调了C的onNewIntent方法
    3. 当前Activity栈已经存在ABC三个activity,需要新启动B(singleTask),则Activity栈变成AB,B被复用并且回调了B的onNewIntent方法,C被出栈(也就是执行了onPause,onStop,onDestory生命周期)

3.4 singleInstance(单例模式)

  • 不复用Activity,startActivity会启动一个新的Activity栈并且启动一个新的Activity实例,也就是一个栈内有且仅有一个Activity实例
  • 举例:
    当前Activity栈已经存在ABC三个activity,需要新启动D(singleInstance),则新建Activity栈,并将D放入新的Activity栈

4 ActivityStack,TaskRecord,ActivityRecord,Activity关系

  • 4.1 源码

package android.content.pm;
public class ActivityInfo extends ComponentInfo implements Parcelable {
     ...//省略无关代码
    public String taskAffinity;
    ...//省略无关代码
    }
package com.android.server.wm;
/**
 * An entry in the history stack, representing an activity.
 *历史堆栈中的一个条目,代表Activity
 */
final class ActivityRecord extends WindowToken implements WindowManagerService.AppFreezeListener {
    ...//省略无关代码
    // activity info provided by developer in AndroidManifest
     final ActivityInfo info; 
    ...//省略无关代码
    }
package com.android.server.wm;
class Task extends WindowContainer {
     ...//省略无关代码
      // The affinity name for this task, or null; may change identity.
      String affinity;       
      /** ActivityRecords that are exiting, but still on screen for animations. */
      final ArrayList mExitingActivities = new ArrayList<>();
    ...//省略无关代码
    }
package com.android.server.wm;
/**
 * State and management of a single stack of activities.
 * 负责一个activity栈的状态和管理
 */
class ActivityStack extends Task {
     ...//省略无关代码
      // The affinity name for this task, or null; may change identity.
      String affinity;       
      /** ActivityRecords that are exiting, but still on screen for animations. */
      final ArrayList mExitingActivities = new ArrayList<>();
    ...//省略无关代码
    }
package com.android.server.wm;
/**
 * State and management of a single stack of activities.
 * 负责一个activity栈的状态和管理
 */
class RecentTasks {
     ...//省略无关代码
    // List of all active recent tasks
    private final ArrayList mTasks = new ArrayList<>();
    /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */
    private final ArrayList mHiddenTasks = new ArrayList<>();
    ...//省略无关代码
    }
  • 4.2 源码分析

    • ActivityInfo拥有taskAffinity属性,也就是说每个Activity都有taskAffinity属性
    • ActivityRecord持有一个ActivityInfo对象,也就是说一个ActivityRecord对应一个Activity
    • Task拥有taskAffinity属性,也就是说Task对应一种taskAffinity。同时Task持有一个ActivityRecord列表,也就是说一个Task对应多个ActivityRecord
    • ActivityStack继承自Task,与Task具有同样的功能和属性,负责ActivityRecord || Activity栈的状态和管理,一个ActivityStack对应一个Task对应多个ActivityRecord
    • RecentTasks持有Task列表,也就是我们常见的最近任务列表
  • 4.3 关系图

    image.png

5 taskAffinity

  • 5.1 源码

//package android.content.pm;
public class ActivityInfo extends ComponentInfo implements Parcelable {
     ...//省略无关代码
    /**
     * The affinity this activity has for another task in the system.  The
     * string here is the name of the task, often the package name of the
     * overall package.  If null, the activity has no affinity.  Set from the
     * {@link android.R.attr#taskAffinity} attribute.
     * 大概翻译一下,该属性表明任务相关性,默认是应用包名
     */
    public String taskAffinity;
    ...//省略无关代码
    }
//package android.content.pm;
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
     ...//省略无关代码
    /**
     * Default task affinity of all activities in this application.
     * 当前应用所有activity的默认taskAffinity
     */
    public String taskAffinity;
    ...//省略无关代码
    }
  • 5.2 概念

    • 物以类聚,人以群分,该属性表明Activity之间的相关性
    • ApplicationInfo.taskAffinity默认是包名(com.xxx.xxx)
    • 当前应用的Activity.taskAffinity默认是ApplicationInfo.taskAffinity
    • 该属性主要和SingleTask启动模式或者allowTaskReparenting属性配对使用

6 allowTaskReparenting

  • 6.1 源码

//package android.content.pm;
public class ActivityInfo extends ComponentInfo implements Parcelable {
     ...//省略无关代码
    /**
     * Bit in {@link #flags} that indicates that the activity can be moved
     * between tasks based on its task affinity.  Set from the
     * {@link android.R.attr#allowTaskReparenting} attribute.
     * 不同Task之间的Activity可以移动,依赖于taskAffinity属性
     */
    public static final int FLAG_ALLOW_TASK_REPARENTING = 0x0040;
    ...//省略无关代码
    }
  • 6.2 概念

    • 一个flag
    • allowTaskReparenting设置为true代表该Activity可以在ActivityStack/Task之间移动
    • 需要和taskAffinity属性配对使用
  • 6.3 举例

    • 路上捡了流浪狗狗,回家养了几天,如果狗狗是原主人抛弃的(allowTaskReparenting = false),这个狗狗就属于你了,如果狗狗是原主人依然需要的( allowTaskReparenting = true),这个狗狗得还别人。
    • 应用A启动应用B的ActivityC,ActivityC的allowTaskReparenting 设置为true,点击Home键返回桌面,点击应用B,会发现启动的并不是应用B的主Activity,而是ActivityC,此时在ActivityC点击返回键,页面会回退到应用B的主Activity。先记住结论,后面会有实验验证

7 实例

  • 7.1 前言

    • 上面的知识点记住了吗?那开始做题吧
    • Standard启动模式受taskAffinity影响吗?
    • SingleTop启动模式受taskAffinity影响吗?
    • SingleTask启动模式与taskAffinity关系是怎样的?
    • SingleInstance与taskAffinity有什么关联?
    • allowTaskReparenting作用是什么?
    • Activity之间的回退栈逻辑是什么?
    • ...
  • 7.2 探究Standard与taskAffinity的关系

  • 7.2.1 代码

open class BaseActivity : AppCompatActivity() {
    val tag: String = "qwq"
    open lateinit var tv:TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tv = findViewById(R.id.tv)
        tv.text = this.localClassName
        Log.i(tag,"${this.localClassName}任务id:$taskId")
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        Log.i(tag, "${this.localClassName}onNewIntent被调用,接收到传递的数据:${intent?.getStringExtra("data")}");
    }
}
class FirstActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        tv.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            intent.putExtra("data", "${this.localClassName}传递的数据")
            startActivity(intent)
        }
    }
}
class SecondActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        tv.setOnClickListener {
            val intent = Intent(this, ThirdActivity::class.java)
            intent.putExtra("data", "${this.localClassName}传递的数据")
            startActivity(intent)
        }
    }
}

     
           
           
     


  • 7.2.2结果

日志打印

2021-12-18 03:56:59.245 7436-7436/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:43
2021-12-18 03:57:02.521 7436-7436/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:43

adb shell dumpsys activity activities 输出

    Running activities (most recent first):
      TaskRecord{97b4e66 #43 A=com.xgz.kwa1 U=0 StackId=1 sz=2}
        Run #1: ActivityRecord{f80978d u0 com.xgz.kwa/.SecondActivity t43}
        Run #0: ActivityRecord{40a33e1 u0 com.xgz.kwa/.FirstActivity t43}
  • 7.2.3 结论

    taskAffinity对Standard启动模式没有影响
  • 7.3 探究SingleTop与taskAffinity的关系

  • 7.3.1 代码

        
            
                

                
            
        
        
  • 7.3.2结果

日志打印

2021-12-18 04:05:33.818 7752-7752/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:45
2021-12-18 04:05:36.684 7752-7752/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:45

adb shell dumpsys activity activities 输出

 TaskRecord{f341410 #45 A=com.xgz.kwa1 U=0 StackId=1 sz=2}
        Run #1: ActivityRecord{968b86a u0 com.xgz.kwa/.SecondActivity t45}
        Run #0: ActivityRecord{aef1362 u0 com.xgz.kwa/.FirstActivity t45}
  • 7.3.3 结论

    taskAffinity对SingleTop启动模式没有影响

  • 7.4 探究SingleTask在taskAffinity不同的情况下表现

  • 7.4.1 代码

        
            
                

                
            
        
        
  • 7.4.2结果

启动SecondActivity日志打印

2021-12-18 04:13:06.458 8071-8071/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:48
2021-12-18 04:13:25.430 8071-8071/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:49

adb shell dumpsys activity activities 输出

     TaskRecord{cc73108 #49 A=com.xgz.kwa2 U=0 StackId=1 sz=1}
        Run #1: ActivityRecord{8efb19f u0 com.xgz.kwa/.SecondActivity t49}
      TaskRecord{c1e02c6 #48 A=com.xgz.kwa1 U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{9c48ae1 u0 com.xgz.kwa/.FirstActivity t48}

系统最近任务栏视图


系统最近任务栏视图

在SecondActivity页面按下Home键返回桌面,重新点击桌面图标进入应用,会进入FirstActivity(主Activity)而不是SecondActivity

  • 7.4.3 结论

    taskAffinity不同的情况下,启动SingleTask模式的Activity会新开一个任务栈,系统最近任务栏会新开一个栈视图,从桌面返回会返回主Activity所在栈栈顶Activity
  • 7.4.4 其他

  • 7.4.4.1 实验1:
    • 条件:如果A是主Activity,B与A的taskAffinity相等,C与A的taskAffinity不相等。A启动模式Standard,B和C启动模式SingleTask
    • 操作:A启动B,B启动C
    • Q1:此时最近任务有几个任务栈
    • Q2:此时C可以通过返回键返回B吗
    • Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
    • A1:两个
    • A2:可以
    • A3:B
  • 7.4.4.2 实验2:
    • 条件:如果A是主Activity,B与A的taskAffinity不相等,C与A的taskAffinity相等。A启动模式Standard,B和C启动模式SingleTask
    • 操作:A启动B,B启动C
    • Q1:此时最近任务有几个任务栈
    • Q2:此时C可以通过返回键返回B吗
    • Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
    • Q4:Q3操作回到应用之后,C回退会回退到哪个Activity
    • Q5:Q4操作之后,A启动B,B会回调onNewIntent还是回调onCreate方法
    • A1:两个
    • A2:可以
    • A3:C
    • A4:A
    • A5:onNewIntent
  • 7.4.4.3 实验3:
    • 条件:A是主Activity,C和A的taskAffinity相等,B和D的taskAffinity相等并且和AC不相等。ABCD启动模式均是SingleTask
    • 操作:A启动B,B启动C,C启动D
    • Q1:此时最近任务有几个任务栈
    • Q2:此时D可以通过返回键返回C吗
    • Q3:此时按下Home键,通过桌面应用图标返回应用,会返回哪个Activity
    • Q4:Q3操作回到应用之后,C回退会回退到哪个Activity
    • Q5:Q4操作之后,A启动B,B会回调onNewIntent还是回调onCreate方法
    • Q6:Q3操作回到应用之后,C启动D,D会回调onNewIntent还是onCreate方法
    • Q7:Q6操作之后,D能返回C吗
    • A1:两个
    • A2:不可以,返回栈是D->B->C->A
    • A3:C
    • A4:A
    • A5:onNewIntent
    • A6:onNewIntent
    • A7:不可以,返回栈是D->B->C->A
  • 7.4.3 总结

    • 在启动模式为SingleTask的情况下,具有相同taskAffinity属性的Activity被分配在同一ActivityStack中
    • 如果这个ActivityStack不存在则新建一个ActivityStack并将Actiivty入栈
    • 每个ActivityStack对应一个最近任务栏视图
    • 一个应用可能存在多个ActivityStack
    • 在没有按下Home键的情况下回退栈按ActivityStack顺序返回,ActivityStack内部按照Activity顺序返回
    • 从桌面返回后,显示Activity为主Activity所在ActivityStack栈顶Activity,回退栈只会根据主Activity所在任务栈进行返回
  • 7.5 探究SingleTask在taskAffinity不同的情况下表现

  • 7.5.1 代码

        
            
                

                
            
        
        
  • 7.4.2结果

启动SecondActivity日志打印

2021-12-18 05:52:11.259 9985-9985/com.xgz.kwa I/qwq: FirstActivity:onCreate:任务id:90
2021-12-18 05:52:13.838 9985-9985/com.xgz.kwa I/qwq: SecondActivity:onCreate:任务id:91

adb shell dumpsys activity activities 输出

      TaskRecord{5a0a68c #91 A=com.xgz.kwa2 U=0 StackId=1 sz=1}
        Run #1: ActivityRecord{fc6b473 u0 com.xgz.kwa/.SecondActivity t91}
      TaskRecord{3889dea #90 A=com.xgz.kwa1 U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{143e0e7 u0 com.xgz.kwa/.FirstActivity t90}

系统最近任务栏视图


系统最近任务栏视图

在SecondActivity页面按下Home键返回桌面,重新点击桌面图标进入应用,会进入FirstActivity(主Activity)而不是SecondActivity

  • 7.5.3 结论

    taskAffinity不同的情况下,启动SingleInstance模式的Activity会新开一个任务栈,系统最近任务栏会新开一个栈视图,从桌面返回会返回主Activity所在栈栈顶Activity
  • 7.5.4 其他

  • 7.5.4.1 实验1:
    • 条件:如果A是主Activity,B,C与A的taskAffinity相等,A,B,C启动模式均为SingleInstance
    • 操作:A启动B,B启动C
    • Q1:此时最近任务有几个任务栈
    • Q2:此时C可以通过返回键返回B吗
    • Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
    • A1:一个
    • A2:可以
    • A3:A
  • 7.5.4.2 实验2:
    • 条件:如果A是主Activity,B与A的taskAffinity相等,A启动模式均为SingleInstance,B启动模式均为SingleTask
    • 操作:A启动B
    • Q1:此时最近任务有几个任务栈
    • Q2:此时B可以通过返回键返回A吗
    • Q3:此时按下Home键,通过桌面应用图标返回应用会返回哪个Activity
    • Q4:Q3操作之后A会回调onNewIntent还是onCreate方法
    • A1:一个
    • A2:可以
    • A3:A
    • A4:onNewIntent
  • 7.5.4.3 实验3:
    • 条件:A是主Activity,AC的taskAffinity相等,BD的taskAffinity相等并且和AC不相等。AC启动模式均是SingleInstance,BD启动模式均是SingleTask
    • 操作:A启动B,B启动C,C启动D
    • Q1:此时最近任务有几个任务栈
    • Q2:此时D可以通过返回键返回C吗
    • Q3:此时按下Home键,通过桌面应用图标返回应用,会返回哪个Activity
    • Q4:Q3操作之后,A会回调onNewIntent还是onCreate方法
    • A1:两个
    • A2:不可以,返回栈是D->B->C->A
    • A3:A
    • A4:onNewIntent
  • 7.5.3 总结

    • 在启动模式为SingleInstance的情况下,一个Activity对应一个ActivityStack
    • 具有相同taskAffinity的Activity在最近任务栏只会显示一个视图
    • 一个最近任务栏可能会对应多个视图
    • 在没有按下Home键的情况下回退栈按ActivityStack顺序返回,ActivityStack内部按照Activity顺序返回
    • 从桌面返回后,显示Activity为主Activity所在ActivityStack栈顶Activity,回退栈只会根据主Activity所在任务栈进行返回
    • 从桌面返回后,如果主Activity启动模式是SingleInstance,会回调onNewIntent方法

8 总结

  • SingleTop和Standard模式不受taskAffinity影响
  • 在SingleTask模式下不同taskAffinity对应不同的ActivityStack,一个ActivityStack对应一个最近任务视图
  • 在SingleTask模式下不同Activity对应不同的ActivityStack,一个具有相同taskAffinity的多个ActivityStack对应一个最近任务视图
  • 任意模式下,在没有按下Home键的情况下,回退栈按ActivityStack顺序返回,ActivityStack内部按照Activity顺序返回
  • 从桌面返回后,显示Activity为主Activity所在ActivityStack栈顶Activity,回退栈只会根据主Activity所在任务栈进行返回
  • 从桌面返回后,如果显示的Activity启动模式是SingleInstance或者SingleTask,会回调onNewIntent方法

你可能感兴趣的:(深入理解Activity启动模式)