Android启动模式详解

Posted 一代小强

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android启动模式详解相关的知识,希望对你有一定的参考价值。

“在整理完启动模式后,我发现大家对启动模式的理解是有误区的“

引言

再谈启动模式,貌似没啥意思。但是你能正确回答下面的问题吗?

  • 问题1:singleTask启动模式,在启动新的Activity的时候,真的会重新创建新的任务栈吗,所谓的任务栈存在指的是什么?
  • 问题2:设置了Intent.FLAG_ACTIVITY_NEW_TASK,每次都会创建新的任务栈吗?
  • 问题3:对于singleInstance,显式指定taskAffinity为应用包名,那在启动的时候,还会创建新的任务栈吗?

以下实验基于android 9.0。如果大家只是想了解每种启动模式的基本概念,可以只看对应的任务栈图,直接忽略掉每一小点。

笔者会用adb的打印,看每一种启动模式下,任务栈的变化。

一、ActivityRecord、TaskRecord、ActivityStack的区别

在回答上面的问题前,我们先整理下ActivityRecord、TaskRecord、ActivityStack的区别。

  • ActivityRecord:一个ActivityRecord对应一个Activity,保存了Activity的所有信息;但一个Activity可能会有多个ActivityRecord,因为Activity可以被多次启动,这个主要取决于其启动模式。

  • TaskRecord:一个TaskRecord由一个或者多个ActivityRecord组成,这就是我们常说的任务栈,具有后进先出的特点。

  • ActivityStack:ActivityStack是用来管理TaskRecord的,包含了多个TaskRecord。

这里简单贴一下代码,让读者对这些概念有一些了解

/**
 * State and management of a single stack of activities.
 */
class ActivityStack extends ConfigurationContainer 

    /**
     * The back history of all previous (and possibly still
     * running) activities.  It contains #TaskRecord objects.
     */
    private final ArrayList<TaskRecord> mTaskHistory = new ArrayList<>();
    //..... 省略代码


class TaskRecord extends ConfigurationContainer 

    /** 
      *List of all activities in the task arranged in history order 
      */
    final ArrayList<ActivityRecord> mActivities;
   //..... 省略代码



/**
 * An entry in the history stack, representing an activity.
 */
final class ActivityRecord extends ConfigurationContainer 
        final ActivityInfo info; // all about me
        int launchMode;         // the launch mode activity attribute.
        final String launchedFromPackage; // always the package who started the activity.
       //..... 省略代码

启动模式的实际值

如果大家看了上面的代码,就会发现launchMode的值是int类型,与我们认知的不太一样,这里贴一下映射关系。

public class ActivityInfo extends ComponentInfo implements Parcelable 
    /** 
       * Constant corresponding to <code>standard</code> in 
       * the @link android.R.attr#launchMode attribute.
       */
    public static final int LAUNCH_MULTIPLE = 0;
    /** 
      * Constant corresponding to <code>singleTop</code> in 
      * the @link android.R.attr#launchMode attribute. 
      */
    public static final int LAUNCH_SINGLE_TOP = 1;
    /** 
      * Constant corresponding to <code>singleTask</code> in 
      * the @link android.R.attr#launchMode attribute. 
      */
    public static final int LAUNCH_SINGLE_TASK = 2;
    /** 
      * Constant corresponding to <code>singleInstance</code> in 
      * the @link android.R.attr#launchMode attribute. 
     */
    public static final int LAUNCH_SINGLE_INSTANCE = 3;
    
    public String taskAffinity;
  

我们注意到ActivityInfo里面有一个taskAffinity字段,想必大家应该知道其作用了——指定任务栈名。不知道也没关系,后面会结合启动模式讲解。

使用dumpsys 命令查看任务栈

我们先来看看在launcher下,任务栈的情况

在cmd下运行 adb shell dumpsys activity a可以得到如下信息

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):

  Stack #0: type=home mode=fullscreen
  isSleeping=false
  mBounds=Rect(0, 0 - 0, 0)
    Task id #1
    mBounds=Rect(0, 0 - 0, 0)
    mMinWidth=-1
    mMinHeight=-1
    mLastNonFullscreenBounds=null
    * TaskRecorda9becdf #1 I=com.miui.home/.launcher.Launcher U=0 StackId=0 sz=1
      userId=0 effectiveUid=u0a23 mCallingUid=1000 mUserSetupComplete=true mCallingPackage=com.android.systemui
      intent=act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800100 cmp=com.miui.home/.launcher.Launcher
      realActivity=com.miui.home/.launcher.Launcher
      autoRemoveRecents=false isPersistable=false numFullscreen=1 activityType=2
      rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
      Activities=[ActivityRecord3705f28 u0 com.miui.home/.launcher.Launcher t1]
      askedCompatMode=false inRecents=true isAvailable=true
      stackId=0
      * Hist #0: ActivityRecord3705f28 u0 com.miui.home/.launcher.Launcher t1
          packageName=com.miui.home processName=com.miui.home
          launchedFromUid=0 launchedFromPackage=null userId=0
          app=ProcessRecord2a0244b 2458:com.miui.home/u0a23
          Intent  act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10800100 cmp=com.miui.home/.launcher.Launcher 
          frontOfTask=true task=TaskRecorda9becdf #1 I=com.miui.home/.launcher.Launcher U=0 StackId=0 sz=1
          taskAffinity=null
          fullscreen=true noDisplay=false immersive=false launchMode=2


  • Stack #0:即home下的ActivityStack id为0。
  • Task id #1:即ActivityStack中有一个id为1的TaskRecord。
  • Activities:用来存放ActivityRecord的,任务栈存放Activity的位置,可以通过这个关键字查看Activity的入栈出栈情况。“com.miui.home/.launcher.Launcher” 就是对应Activity的类路径。
  • taskAffinity:细心的读者应该会注意到这个字段,就是我们在manifest文件中配置的,这里为null表明没有添加taskAffinity。
  • launchMode:即启动模式,对应值为int。

二、Standard模式

默认值。系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。

比如 A启动B,B启动A的任务栈变化情况如下


新建一个项目,MainActivity启动 SecondActivity,都是标准的启动模式。对应的Activity配置如下

        <activity
            android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".StandardActivity"/>

1)intent不设置flag,不配置taskAffinity

新建一个项目,MainActivity启动 SecondActivity,都是标准的启动模式。打印的任务栈如下:

  Stack #9: type=standard mode=fullscreen
  isSleeping=false
  mBounds=Rect(0, 0 - 0, 0)
   Task id #18359
    mBounds=Rect(0, 0 - 0, 0)
    mMinWidth=-1
    mMinHeight=-1
    mLastNonFullscreenBounds=null
    * TaskRecordb751d5b #18359 A=com.example.launchmode U=0 StackId=31 sz=2
      affinity=com.example.launchmode
      ....
    Activities=[ActivityRecord8b23942 u0 com.example.launchmode/.MainActivity t18359, ActivityRecordd0a45e5 u0 com.example.launchmode/.StandardActivity t18359]
      ....

可以看到Activities 里面有两个ActivityRecord,分别对应MainActivity,SecondActivity。这里贴一下对于的生命周期(注意,面试必考题)和TaskId,可以看到Task Id是一样的:

04-18 10:53:37.981 15379 15379 D MainActivity: onCreate:  Task id 18359
04-18 10:53:38.029 15379 15379 D MainActivity: onStart:  Task id 18359
04-18 10:53:38.032 15379 15379 D MainActivity: onResume:  Task id 18359
04-18 10:53:39.611 15379 15379 D MainActivity: ------------- start activity -------------
04-18 10:53:39.622 15379 15379 D MainActivity: onPause:  Task id 18359
04-18 10:53:39.635 15379 15379 D StandardActivity: onCreate:  Task id 18359
04-18 10:53:39.649 15379 15379 D StandardActivity: onStart:  Task id 18359
04-18 10:53:39.651 15379 15379 D StandardActivity: onResume:  Task id 18359
04-18 10:53:40.199 15379 15379 D MainActivity: onStop:  Task id 18359

Activity里面可以通过 getTaskId() 方式获取到TaskId,具体实现就不再讲解,作为一个课后作业。

2)只设置intent为FLAG_ACTIVITY_NEW_TASK

在MainActivity启动StandardActivity的时候,设置flag如下

    public void next(View view) 
        Log.d(TAG, "------------- start activity -------------");
        Intent intent = new Intent(this, StandardActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    

对应的生命周期和TaskId如下,可见并没有启动新的任务栈

04-18 10:57:12.584 15563 15563 D MainActivity: onCreate:  Task id 18361
04-18 10:57:12.631 15563 15563 D MainActivity: onStart:  Task id 18361
04-18 10:57:12.634 15563 15563 D MainActivity: onResume:  Task id 18361
04-18 10:57:14.096 15563 15563 D MainActivity: ------------- start activity -------------
04-18 10:57:14.105 15563 15563 D MainActivity: onPause:  Task id 18361
04-18 10:57:14.117 15563 15563 D StandardActivity: onCreate:  Task id 18361
04-18 10:57:14.132 15563 15563 D StandardActivity: onStart:  Task id 18361
04-18 10:57:14.134 15563 15563 D StandardActivity: onResume:  Task id 18361
04-18 10:57:14.677 15563 15563 D MainActivity: onStop:  Task id 18361

3)只配置taskAffinity

在manifest中配置StandardActivity的taskAffinity为 com.demo.launchmode

        <activity
            android:name=".StandardActivity"
            android:taskAffinity="com.demo.launchmode"/>

直接启动standardActivity,对应的周期和TaskId如下,可见这种方式也不会启动新的任务栈。

04-18 11:02:51.802 16954 16954 D MainActivity: onCreate:  Task id 18363
04-18 11:02:51.849 16954 16954 D MainActivity: onStart:  Task id 18363
04-18 11:02:51.852 16954 16954 D MainActivity: onResume:  Task id 18363
04-18 11:02:53.283 16954 16954 D MainActivity: ------------- start activity -------------
04-18 11:02:53.293 16954 16954 D MainActivity: onPause:  Task id 18363
04-18 11:02:53.306 16954 16954 D StandardActivity: onCreate:  Task id 18363
04-18 11:02:53.321 16954 16954 D StandardActivity: onStart:  Task id 18363
04-18 11:02:53.323 16954 16954 D StandardActivity: onResume:  Task id 18363
04-18 11:02:53.873 16954 16954 D MainActivity: onStop:  Task id 18363

4)intent设置FLAG_ACTIVITY_NEW_TASK 、配置taskAffinity

按照2)、3)方式,同时在intent设置FLAG_ACTIVITY_NEW_TASK 和配置taskAffinity。MainActivity与standardActivity的TaskId就不一样了。对应的栈结构,可以通过上面的dumpsys命令查看,这里就不列出来了。

04-18 11:04:55.107 17185 17185 D MainActivity: onCreate:  Task id 18365
04-18 11:04:55.154 17185 17185 D MainActivity: onStart:  Task id 18365
04-18 11:04:55.156 17185 17185 D MainActivity: onResume:  Task id 18365
04-18 11:04:56.707 17185 17185 D MainActivity: ------------- start activity -------------
04-18 11:04:56.721 17185 17185 D MainActivity: onPause:  Task id 18365
04-18 11:04:56.751 17185 17185 D StandardActivity: onCreate:  Task id 18366
04-18 11:04:56.774 17185 17185 D StandardActivity: onStart:  Task id 18366
04-18 11:04:56.776 17185 17185 D StandardActivity: onResume:  Task id 18366
04-18 11:04:57.560 17185 17185 D MainActivity: onStop:  Task id 18365

画重点:要同时指定taskAffinity和FLAG_ACTIVITY_NEW_TASK才能在新的任务栈启动Standard的Activity

三、SingleTop模式

如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其 onNewIntent() 方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。

如果A以Standard启动B,B再以SingleTop启动B,对应的图为:

如果A以Standard启动B,B再以SingleTop启动A,对应的图为:

对应的Activity配置如下

   <activity
            android:name=".SingleTopActivity"
            android:launchMode="singleTop" />

1)intent不设置flag,不配置taskAffinity

​ 对应的周期是,这种情况是不会创建新的任务栈

04-18 16:40:43.267 24395 24395 D MainActivity: onCreate:  Task id 18428
04-18 16:40:43.315 24395 24395 D MainActivity: onStart:  Task id 18428
04-18 16:40:43.318 24395 24395 D MainActivity: onResume:  Task id 18428
04-18 16:40:59.145 24395 24395 D MainActivity: ------------- start activity -------------
04-18 16:40:59.168 24395 24395 D MainActivity: onPause:  Task id 18428
04-18 16:40:59.180 24395 24395 D SingleTopActivity: onCreate:  Task id 18428
04-18 16:40:59.196 24395 24395 D SingleTopActivity: onStart:  Task id 18428
04-18 16:40:59.198 24395 24395 D SingleTopActivity: onResume:  Task id 18428
04-18 16:40:59.747 24395 24395 D MainActivity: onStop:  Task id 18428

2)只设置intent为FLAG_ACTIVITY_NEW_TASK

对应的周期和TaskId如下,可见这种方式不会启动新的任务栈。

04-18 10:28:48.458 14065 14065 D MainActivity: onCreate:  Task id 18343
04-18 10:28:48.505 14065 14065 D MainActivity: onStart:  Task id 18343
04-18 10:28:48.508 14065 14065 D MainActivity: onResume:  Task id 18343
04-18 10:28:50.358 14065 14065 D MainActivity: ------------- start activity -------------
04-18 10:28:50.367 14065 14065 D MainActivity: onPause:  Task id 18343
04-18 10:28:50.380 14065 14065 D SingleTopActivity: onCreate:  Task id 18343
04-18 10:28:50.394 14065 14065 D SingleTopActivity: onStart:  Task id 18343
04-18 10:28:50.396 14065 14065 D SingleTopActivity: onResume:  Task id 18343
04-18 10:28:50.939 14065 14065 D MainActivity: onStop:  Task id 18343

3)只配置taskAffinity

对应的周期和TaskId如下,可见这种方式也不会启动新的任务栈。

04-18 10:35:29.907 14560 14560 D MainActivity: onCreate:  Task id 18349
04-18 10:35:29.954 14560 14560 D MainActivity: onStart:  Task id 18349
04-18 10:35:29.957 14560 14560 D MainActivity: onResume:  Task id 18349
04-18 10:35:32.096 14560 14560 D MainActivity: ------------- start activity -------------
04-18 10:35:32.107 14560 14560 D MainActivity: onPause:  Task id 18349
04-18 10:35:32.118 14560 14560 D SingleTopActivity: onCreate:  Task id 18349
04-18 10:35:32.132 14560 14560 D SingleTopActivity: onStart:  Task id 18349
04-18 10:35:32.134 14560 14560 D SingleTopActivity: onResume:  Task id 18349
04-18 10:35:32.699 14560 14560 D MainActivity: onStop:  Task id 18349

4)intent设置FLAG_ACTIVITY_NEW_TASK 、配置taskAffinity

周期、taskId如下,可以看到,singleTop的taskid已经变了。即这种方式可以启动新的任务栈。

04-18 10:31:35.382 14357 14357 D MainActivity: onCreate:  Task id 18345
04-18 10:31:35.430 14357 14357 D MainActivity: onStart:  Task id 18345
04-18 10:31:35.432 14357 14357 D MainActivity: onResume:  Task id 18345
04-18 10:31:37.143 14357 14357 D MainActivity: ------------- start activity -------------
04-18 10:31:37.158 14357 14357 D MainActivity: onPause:  Task id 18345
04-18 10:31:37.184 14357 14357 D SingleTopActivity: onCreate:  Task id 18346
04-18 10:31:37.199 14357 14357 D SingleTopActivity: onStart:  Task id 18346
04-18 10:31:37.201 14357 14357 D SingleTopActivity: onResume:  Task id 18346
04-18 10:31:38.004 14357 14357 D MainActivity: onStop:  Task id 18345

5)栈顶情况

在栈顶的时候,singletop的周期如下。可以看到会先调用onPause,然后才调用onNewIntent,onResume也会重新调用。

04-18 10:21:18.128 13663 13663 D SingleTopActivity: onCreate:  Task id 18338
04

以上是关于Android启动模式详解的主要内容,如果未能解决你的问题,请参考以下文章

Activity启动模式(launchMode)详解

Android四大启动模式

Android启动模式详解

Android Activity的4种启动模式详解(示例)

android Activity的启动模式 作用简析+demo详解

Android四种启动模式LanchMode