Intent Flags VS launchMode

  本文试图说明 Activity launchMode 原理,各种常用的 Intent Flag 原理 ,以及launchMode 和 Intent Flag 的区别,相似点等;以及有可能的使用场景(源码基于 7.0 分析)。

SingleTask VS Intent.FLAG_NEW_TASK

  Activity 中有四大启动模式,较常用的一种为SingleTask官方注释如下:

"singleTask" can only begin a task. They are always at the root of 
the activity stack. Moreover, the device can hold only one instance of 
the activity at a time — only one such task.

  大致翻译如下:如果一个Activity设置为SingleTask,那么它必须在当前栈的底部,并且在当前栈中,只能有一个该Activity的实例。
  上述说法其实是错误的,一个被SingleTask标识的Activity是可以不在栈底的。并且在一个栈中,只能有一个该Activity实例。那么 SingleTask 的原理是什么呢。
  不同应用的Activity以及Activity的跳转是由AMS来完成的,当启动一个Activity时,最后都是由 AMS 来完成,最后会调用到 ActivityStarter的startActivityUnchecked()方法,该方法代码如下:

  // Note: This method should only be called from {@link startActivity}.
    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
                                       IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                                       int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
                                       ActivityRecord[] outActivity) {
           //  1.1 解析 LaunchMode , 设置初始化状态
           setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);
           // 1.2 实现 LaunchMode 到 LaunchFlag 转换操作
           computeLaunchingTaskFlags();
           ...
           // 1.3 此处为关键点,寻找可重用的 ActivityRecord实例
           ActivityRecord reusedActivity = getReusableIntentActivity();
           ...
           // 1.4 如果 reusedActivity 不为 null ,走相关逻辑
           if(reusedActivity != null){
                 //后续具体分析..
           }
           ...
     }               

  应用在真正启动一个 Activity 之前,会先走上述1.1~1.3的初始化过程,下面分别来解释一下 1.1 ~1.3具体做了哪些工作。

1.1:setInitialState() 初始化工作

在 setInitialState() 过程中,会首先做一些初始化的操作,如记录该 ActivityRecord 的启动模式,activityResult 校验等等,代码如下:

private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
            boolean doResume, int startFlags, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
        reset();

        mStartActivity = r;
        mIntent = r.intent;
        mOptions = options;
        mCallingUid = r.launchedFromUid;
        mSourceRecord = sourceRecord;
        mVoiceSession = voiceSession;
        mVoiceInteractor = voiceInteractor;

        mLaunchBounds = getOverrideBounds(r, options, inTask);
        // 常见的启动模式的标识,赋值给成员变量
        // 标识将要启动的 Activity 的 LaunchMode
        mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
        mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
        mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
        mLaunchFlags = adjustLaunchFlagsToDocumentMode(
                r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
        mLaunchTaskBehind = r.mLaunchTaskBehind
                && !mLaunchSingleTask && !mLaunchSingleInstance
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
         
        // 此处也是一个重要方法,与 startActivityForResult() 密切相关
        sendNewTaskResultRequestIfNeeded();
        
        ...
    }

  从上述初始化操作可以看出,将目标Activity的Launch Mode 赋值给相关的成员变量,并且在之后有一步 sendNewTaskResultRequestIfNeeded() 的操作,那么这个操作是干什么的呢?该方法代码如下:

private void sendNewTaskResultRequestIfNeeded() {
        if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
                && mStartActivity.resultTo.task.stack != null) {
            // For whatever reason this activity is being launched into a new task...
            // yet the caller has requested a result back.  Well, that is pretty messed up,
            // so instead immediately send back a cancel and let the new task continue launched
            // as normal without a dependency on its originator.
            Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
            mStartActivity.resultTo.task.stack.sendActivityResultLocked(-1, mStartActivity.resultTo,
                    mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null);
            mStartActivity.resultTo = null;
        }
    }

  从该方法注释可以看出,如果以startActivityForResult() 方式启动(RequestCode > 0),并且Intent Flag 有设置 FLAG_ACTIVITY_NEW_TASK 标识,那么当前 Activity 会立即收到 CANCEL 回调。
  上述问题也是一个比较常见的问题,从代码可以看出,如果想要正确的收到 onActivityResult() 回调,那么在启动 Activity 的 Intent 中,一定不能加 FLAG_ACTIVITY_NEW_TASK 标识。
  分析完上述 1.1 之后,接着来看 1.2 computeLaunchingTaskFlags() 具体做了什么事情。

1.2 : computeLaunchingTaskFlags()

代码如下:

private void computeLaunchingTaskFlags() {
        // If the caller is not coming from another activity, but has given us an explicit task into
        // which they would like us to launch the new activity, then let's see about doing that.
        // 此处不在本文的分析范围内,如果用户是通过 Application Context 或者 
        // Service 启动的 目标 Activity ,并且有指定要宿主到哪个 task
        // 该判断条件才会成立。
        if (mSourceRecord == null && mInTask != null && mInTask.stack != null) {
            final Intent baseIntent = mInTask.getBaseIntent();
            final ActivityRecord root = mInTask.getRootActivity();
            if (baseIntent == null) {
                ActivityOptions.abort(mOptions);
                throw new IllegalArgumentException("Launching into task without base intent: "
                        + mInTask);
            }

            // If this task is empty, then we are adding the first activity -- it
            // determines the root, and must be launching as a NEW_TASK.
            if (mLaunchSingleInstance || mLaunchSingleTask) {
                if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
                    ActivityOptions.abort(mOptions);
                    throw new IllegalArgumentException("Trying to launch singleInstance/Task "
                            + mStartActivity + " into different task " + mInTask);
                }
                if (root != null) {
                    ActivityOptions.abort(mOptions);
                    throw new IllegalArgumentException("Caller with mInTask " + mInTask
                            + " has root " + root + " but target is singleInstance/Task");
                }
            }

            // If task is empty, then adopt the interesting intent launch flags in to the
            // activity being started.
            if (root == null) {
                final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK
                        | FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS;
                mLaunchFlags = (mLaunchFlags & ~flagsOfInterest)
                        | (baseIntent.getFlags() & flagsOfInterest);
                mIntent.setFlags(mLaunchFlags);
                mInTask.setIntent(mStartActivity);
                mAddingToTask = true;

                // If the task is not empty and the caller is asking to start it as the root of
                // a new task, then we don't actually want to start this on the task. We will
                // bring the task to the front, and possibly give it a new intent.
            } else if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
                mAddingToTask = false;

            } else {
                mAddingToTask = true;
            }

            mReuseTask = mInTask;
        } else {
            mInTask = null;
            // Launch ResolverActivity in the source task, so that it stays in the task bounds
            // when in freeform workspace.
            // Also put noDisplay activities in the source task. These by itself can be placed
            // in any task/stack, however it could launch other activities like ResolverActivity,
            // and we want those to stay in the original task.
            if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
                    && mSourceRecord.isFreeform())  {
                mAddingToTask = true;
            }
        }

        // 重点分析此处 , 如果启动目标 Activity 为 Activity Context ,那么会为 mSourceRecord 
        // 赋值,如果是从非 Activity 启动,比如 Application Context 等,那么mSourceRecord
        // 为 null 。
        if (mInTask == null) {
            if (mSourceRecord == null) {
                // This activity is not being started from another...  in this
                // case we -always- start a new task.
                // 如果是从非 Activity 入口过来,强制加上 FLAG_NEW_TASK 标识。
                if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
                    Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                            "Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
                    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
                }
            } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
                // The original activity who is starting us is running as a single
                // instance...  this new activity it is starting must go on its
                // own task.
                // 如果 当前 Activity Launch Mode 为 singleInstance,代表不允许目标
                // Activity 与宿主 Activity 栈实例共用一个 task 栈,所以也会强制加上
                // FLAG_ACTIVITY_NEW_TASK 标识。
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            } else if (mLaunchSingleInstance || mLaunchSingleTask) {
                // The activity being started is a single instance...  it always
                // gets launched into its own task.
                // 如果目标 Activity 的 LaunchMode 为 singleTask 或者 singleInstance 
                // 那么也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
        }
    }

  该方法的说明已经在上述代码中注释的比较清楚,总结一些,可以罗列为以下几点:

  • 如果是从非 Activity 入口过来,比如 Application Context 、Service 等 , 强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
  • 如果目标 Activity 启动模式为 SingleTask 或者 SingleInstance ,那么也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
  • 如果源 Activity 启动模式为 SingleInstance , 那么肯定不允许目标 Activity 与源 Activity 公用一个 Task 栈,所以也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
1.3 :寻找可重用的 ActivityRecord 实例

getReusableIntentActivity() 方法代码如下:

/**
     * Decide whether the new activity should be inserted into an existing task. Returns null
     * if not or an ActivityRecord with the task into which the new activity should be added.
     */
    private ActivityRecord getReusableIntentActivity() {
        // We may want to try to place the new activity in to an existing task.  We always
        // do this if the target activity is singleTask or singleInstance; we will also do
        // this if NEW_TASK has been requested, and there is not an additional qualifier telling
        // us to still place it in a new task: multi task, always doc mode, or being asked to
        // launch this as a new task behind the current one.
        boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                || mLaunchSingleInstance || mLaunchSingleTask;
        // If bring to front is requested, and no result is requested and we have not been given
        // an explicit task to launch in to, and we can find a task that was started with this
        // same component, then instead of launching bring that one to the front.
        putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
        ActivityRecord intentActivity = null;
        if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
            final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
            intentActivity = task != null ? task.getTopActivity() : null;
        } else if (putIntoExistingTask) {
            if (mLaunchSingleInstance) {
                // There can be one and only one instance of single instance activity in the
                // history, and it is always in its own unique task, so we do a special search.
               intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, false);
            } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                // For the launch adjacent case we only want to put the activity in an existing
                // task if the activity already exists in the history.
                intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
                        !mLaunchSingleTask);
            } else {
                // Otherwise find the best task to put the activity in.
                intentActivity = mSupervisor.findTaskLocked(mStartActivity);
            }
        }
        return intentActivity;
    }

  从上述代码注释可以看出,该方法主要作用为判断目标 Activity 是否要插入到一个已经存在的栈中,主要判断依据罗列如下:

  • 对于 LaunchMode 为 singleTask 或 singleInstance 的 Activity,强制尝试去寻找是否已经存在一个已经存在的 Task。
  • 对于 含有 FLAG_ACTIVITY_NEW_TASK 标识的 Intent,也强制尝试去寻找。
  • 若目标 Activity LaunchMode 为 singleInstance ,强制去找是否已经存在相同的 ActivityRecord 实例。
  • 若LaunchMode 不为 singleInstance,则强制去寻找已经存在的 Task 实例,特别说明的是,此处寻找是否存在相同的 task 实例是通过判断 taskAffinity 来完成的,具体代码逻辑在 mSupervisor.findTaskLocked(mStartActivity) 方法中,此处不再详细说明。

  通过上述方法可以看出,如果一个 Activity 的 LaunchMode 被标识为 singleInstance , 那么它在 AMS 中永远只存在唯一的一份实例。对于 singleTask 或 FLAG_ACTIVITY_NEW_TASK , 都尝试先根据该 Activity 对应的 taskAffinity 去找是否已经存在相关的栈,如果存在,则将目标 Activity 放入该栈中。
  从上述1.1 ~ 1.3 的分析,发现 launchMode 为 singleTask 与 Intent FLAG_ACTIVITY_NEW_TASK 并没有什么区别,singleTask 在 1.2 computeLaunchingTaskFlags() 方法中也会强制加上 FLAG_ACTIVITY_NEW_TASK 标识,那么两者的区别到底是什么呢?

1.4 : reusedActivity 不为 null 处理

从1.3 处分析可知,对于 launchMode 为 singleTask 或者 Intent Flag 为 FLAG_ACTIVITY_NEW_TASK 的标识,都会首先尝试去找一个已经存在的栈,此处如果可以找到,则将栈中的一个 ActivityRecord 实例赋值给 reusedActivity ,然后继续走接下来的流程,代码如下:

if (reusedActivity != null) {
            ...
            // 重点分析此处方法
            setTaskFromIntentActivity(reusedActivity);
            if (!mAddingToTask && mReuseTask == null) {
                // We didn't do anything...  but it was needed (a.k.a., client don't use that
                // intent!)  And for paranoia, make sure we have correctly resumed the top activity.
                resumeTargetStackIfNeeded();
                if (outActivity != null && outActivity.length > 0) {
                    outActivity[0] = reusedActivity;
                }
                return START_TASK_TO_FRONT;
            }
        }

  重点分析 setTaskFromIntentActivity(reusedActivity) 方法,其中 reusedActivity 代表 1.3 找到的 ActivityRecord 实例, 该方法代码如下:

private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
            // The caller has requested to completely replace any existing task with its new
            // activity. Well that should not be too hard...
            // 如果 Intent flag 同时设置有 FLAG_ACTIVITY_CLEAR_TASK 与 FLAG_ACTIVITY_NEW_TASK
            // 那么会把当前的栈清空,重新开启一个栈
            mReuseTask = intentActivity.task;
            // 清空栈操作
            mReuseTask.performClearTaskLocked();
            mReuseTask.setIntent(mStartActivity);
            // When we clear the task - focus will be adjusted, which will bring another task
            // to top before we launch the activity we need. This will temporary swap their
            // mTaskToReturnTo values and we don't want to overwrite them accidentally.
            mMovedOtherTask = true;
        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                || mLaunchSingleInstance || mLaunchSingleTask) {
            // 如果目标 Activity launchMode 为 singleTask或
            // 者singleInstance , 或者 Intent flag 含有 FLAG_CLEAR_TOP 标识 ,那么首先
            // 去清除 栈里面的元素,直到找到可重用的 ActivityRecord 为止。
           // 此处是一个关键点
            ActivityRecord top = intentActivity.task.performClearTaskLocked(mStartActivity,
                    mLaunchFlags);
            if (top == null) {
                // A special case: we need to start the activity because it is not currently
                // running, and the caller has asked to clear the current task to have this
                // activity at the top.
                mAddingToTask = true;
                // Now pretend like this activity is being started by the top of its task, so it
                // is put in the right place.
                mSourceRecord = intentActivity;
                final TaskRecord task = mSourceRecord.task;
                if (task != null && task.stack == null) {
                    // Target stack got cleared when we all activities were removed above.
                    // Go ahead and reset it.
                    mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
                            null /* bounds */, mLaunchFlags, mOptions);
                    mTargetStack.addTask(task,
                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
                }
            }
        } else if (mStartActivity.realActivity.equals(intentActivity.task.realActivity)) {
            // In this case the top activity on the task is the same as the one being launched,
            // so we take that as a request to bring the task to the foreground. If the top
            // activity in the task is the root activity, deliver this new intent to it if it
            // desires.
            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
                    && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
                ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
                        intentActivity.task);
                if (intentActivity.frontOfTask) {
                    intentActivity.task.setIntent(mStartActivity);
                }
                intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
                        mStartActivity.launchedFromPackage);
            } else if (!intentActivity.task.isSameIntentFilter(mStartActivity)) {
                // In this case we are launching the root activity of the task, but with a
                // different intent. We should start a new instance on top.
                mAddingToTask = true;
                mSourceRecord = intentActivity;
            }
        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
            // In this case an activity is being launched in to an existing task, without
            // resetting that task. This is typically the situation of launching an activity
            // from a notification or shortcut. We want to place the new activity on top of the
            // current task.
            mAddingToTask = true;
            mSourceRecord = intentActivity;
        } else if (!intentActivity.task.rootWasReset) {
            // In this case we are launching into an existing task that has not yet been started
            // from its front door. The current task has been brought to the front. Ideally,
            // we'd probably like to place this new task at the bottom of its stack, but that's
            // a little hard to do with the current organization of the code so for now we'll
            // just drop it.
            intentActivity.task.setIntent(mStartActivity);
        }
    }

从上述代码的注释,可以很清楚的得到以下结论:

  • 如果 Intent flag 同时设置有 FLAG_ACTIVITY_CLEAR_TASK 与 FLAG_ACTIVITY_NEW_TASK 那么会把当前的栈清空。目标Activity 启动的时候,与栈内之前已经存在的 Activity 不是一个实例。
  • 如果目标 Activity launchMode 为 singleTask或者 singleInstance ,那么首先清除栈里面的元素,直到栈顶元素为将要启动的目标 ActivityRecord 实例为止。
  • 若Intent Flag 中包含 FLAG_ACTIVITY_CLEAR_TOP , 则也执行清栈操作,但是会把已经存在的 ActivityRecord 首先 finish掉,然后重新启动目标 ActivityRecord 实例。
  自此可以 FLAG_ACTIVITY_NEW_TASK 和 singleTask 的区别了,singleTask 会执行清除栈的操作,直到栈顶元素为已经存在的目标 ActivityRecord 实例,并不会启动一个新的 Activity 实例。如果launchMode 为标准启动模式,并且仅仅是将IntentFlag 为 FLAG_ACTIVITY_NEW_TASK ,那么仅仅只会复用这个已经存在的栈,并不会执行清除栈的操作,而是仅仅新建一个新的ActivityRecord 实例出来,把它放在栈顶而已。

你可能感兴趣的:(Intent Flags VS launchMode)