启动flag、launchmode总结

之前对Activity启动时intent所带的flag的认识比较混乱,首先是因为使用不多,其次是flag本身的数量就很多,再加上一些英文理解起来并不直观,导致一团浆糊的体验。launchmode也类似,由于感觉其和启动flag有很多重合的地方,所以也会产生一定的困扰。

本文的目的并不是把这些flag和launchmode重新再讲述一遍,因为已经有大量的文章有对此进行详细分析了,目的是对这些东西进行归类,再遇到的这些flag和launchmode进行各种组合的时候,分析起来也不至于思路混乱

启动flag归类

直接给出结论,启动flag我主要归为三类

1、启动flag分类例子Activity该放哪个Task?

如FLAG_ACTIVITY_NEW_TASKActivity

2、放入目标Task之后其他Activity该怎么处理?

如FLAG_ACTIVITY_CLEAR_TASK/FLAG_ACTIVITY_CLEAR_TOP

3、给Activity打上某些特殊标记

如FLAG_NEW_DOCUMENT

尽量少的归类才能确保不被弄晕,那种比较全的百科性质的文档只是用作工具查询时使用,而不太能作为记忆使用。

launchmode归类

launchmode也就四种,区别于启动flag,launchmode主要描述的是这个Activity启动的时候如何实例化,也就是能创建几个这样的实例。实例个数也会从侧面对Task中其他Activity带来影响,所以会和flag的功能会有部分重叠。

其实从上面的结论可以比较明白的去思考系统的行为 启动A Activity 我们需要找哪个Task去装它呢?放进这个Task之后其他Activity会受到什么影响?这个Activity能用多个实例还是单个?

从你启动此Activity设置的flag和启动模式如果能回答上面三个问题基本上就能够根据你的应用场景正确的使用这两样东西了。

AMS Activity启动详解

还是要回到代码来验证过这些flag才能算是真的踏实,接下来要分析的是ActivityStarter的startActivityUnchecked

会尽量罗列代码做到清晰,代码部分仅关注跟flag和launchmode相关的

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
        //step1:初始化Activity启动状态,获取launchmode flag 同时解决一些falg和launchmode的冲突
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);
        //step2:根据是否存在制定的目标task来设定启动flag        
        computeLaunchingTaskFlags();
        
        ......
        
        //step3:计算寻找可以重用的Task
        mReusedActivity = getReusableIntentActivity();

        ......
        //如果存在可重用的Activity
        if (mReusedActivity != null) {

            ......

            if (mStartActivity.task == null) {
                mStartActivity.task = mReusedActivity.task;
            }
            
            if (mReusedActivity.task.intent == null) {
                // This task was started because of movement of the activity based on affinity...
                // Now that we are actually launching it, we can assign the base intent.
                mReusedActivity.task.setIntent(mStartActivity);
            }
            
            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                    || mLaunchSingleInstance || mLaunchSingleTask) {
                ......
            }
            
            ......
            //step4:设置目标stack
            mReusedActivity = setTargetStackAndMoveToFrontIfNeeded(mReusedActivity);
            
            ......
            //step5:设置目标Task,根据某些flag来进行清理操作
            setTaskFromIntentActivity(mReusedActivity);
            
            ......
        }
        ......
    }

step1 跳转前的初始化

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

        ......
        //读取launchmode
        mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
        mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
        mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
        //根据启动模式来调整flag到NEW_DOCUMENT
        //具体规则是
        //1、如果此Activity是由singleInstance或者singleTask的话且flag带了NEW_DOCUMENT,则需要去掉
        //NEW_DOCUMENT和MULTIPLE_TASK的flag
        //2、如果不属于第一种情况则读取ActivityInfo中的documentLaunchMode来对flag赋值
        mLaunchFlags = adjustLaunchFlagsToDocumentMode(
                r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
        mLaunchTaskBehind = r.mLaunchTaskBehind
                && !mLaunchSingleTask && !mLaunchSingleInstance
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;

        ......
        
        //如果设置了NEW_DOCUMENT标志同时此Activity不是其他Activity启动的
        //则在加上NEW_TASK的标志
        if ((mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
            mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
        }

        ......

        mInTask = inTask;
        // In some flows in to this function, we retrieve the task record and hold on to it
        // without a lock before calling back in to here...  so the task at this point may
        // not actually be in recents.  Check for that, and if it isn't in recents just
        // consider it invalid.
        if (inTask != null && !inTask.inRecents) {
            Slog.w(TAG, "Starting activity in task not in recents: " + inTask);
            mInTask = null;
        }

        ......

        mNoAnimation = (mLaunchFlags & FLAG_ACTIVITY_NO_ANIMATION) != 0;
    }

这个step中根据singleInstance和singleTask去调整flag中的NEW_DOCUMENT,主要因为singleInstance的Activity会独自存在于一个Task中,没有必要去设置NEW_DOCUMENT。singleTask的功能跟NEW_DOCUMENT一样,所以也没必要多设置这样一个flag。

step2 计算启动task flag,主要是给某些场景设置FLAG_ACTIVITY_NEW_TASK

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.
        //如果不是由其他Activity调起且指定了目标task放置该Activity的话
        //这种情况感觉遇到的可能性比较小
        if (mSourceRecord == null && mInTask != null && mInTask.stack != null) {
            final Intent baseIntent = mInTask.getBaseIntent();
            final ActivityRecord root = mInTask.getRootActivity();

            ......

            // 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) {
                //如果启动模式是singleTask或者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;
            }
        }
        //mAddingToTask这个变量表示已经找到某个task来放置Activity,有可能是启动时指定的task还有可能是启动的sourceTask,反正就是不用再去遍历寻找task

        if (mInTask == null) {
            if (mSourceRecord == null) {//未指定Task且没有sourceRecord,系统会去增加NEW_TASK的flag
                // This activity is not being started from another...  in this
                // case we -always- start a 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.
                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.
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
        }
    }

这个函数主要的是根据sourceRecord,mInTask以及相关的launchmode来决定是否存在一个确定Task,如果不存在就给launchflag填上一个NEW_TASK的flag,让其在后面的启动流程中根据规则去寻找合适的task。 很多情况下NEW_TASK并非我们的app显式地设置的

step3 寻找可以防止目标Activity的Task,返回这个Task顶部的Activity

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) {
        //要进入这里需要NEW_TASK为true但是MULTIPLE_TASK为false
            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.
                //这里是最常见的形式,mStartActivity是需要启动的Activity,intentActivity是找到的可用的Task中顶部的Activity
                intentActivity = mSupervisor.findTaskLocked(mStartActivity);
            }
        }
        return intentActivity;
    }

step4 设置目标stack(带有调整stack中的task的顺序)

//参数intentActivity:是目标task中的顶部Activity
private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
        mTargetStack = intentActivity.task.stack;
        mTargetStack.mLastPausedActivity = null;
        // If the target task is not in the front, then we need to bring it to the front...
        // except...  well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
        // the same behavior as if a new instance was being started, which means not bringing it
        // to the front if the caller is not itself in the front.
        //得到当前有焦点的stack和焦点stack中顶部Activity
        final ActivityStack focusStack = mSupervisor.getFocusedStack();
        ActivityRecord curTop = (focusStack == null)
                ? null : focusStack.topRunningNonDelayedActivityLocked(mNotTop);

        if (curTop != null
                && (curTop.task != intentActivity.task || curTop.task != focusStack.topTask())
                && !mAvoidMoveToFront) {
            //需要将目标task移动到stack的顶端,添加此flag
            mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
            
            if (mSourceRecord == null || (mSourceStack.topActivity() != null &&
                    mSourceStack.topActivity().task == mSourceRecord.task)) {
            //能掉进这个条件中||后面部分的可能性很大
            //sourcestack顶部Activity非空 且其task和sourceRecord的task相同
                // We really do want to push this one into the user's face, right now.
                if (mLaunchTaskBehind && mSourceRecord != null) {
                    intentActivity.setTaskToAffiliateWith(mSourceRecord.task);
                }
                mMovedOtherTask = true;

                // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
                // will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
                // So no point resuming any of the activities here, it just wastes one extra
                // resuming, plus enter AND exit transitions.
                // Here we only want to bring the target stack forward. Transition will be applied
                // to the new activity that's started after the old ones are gone.
                final boolean willClearTask =
                        (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
                if (!willClearTask) {//没有设置clearTask的话
                    final ActivityStack launchStack = getLaunchStack(
                            mStartActivity, mLaunchFlags, mStartActivity.task, mOptions);
                    if (launchStack == null || launchStack == mTargetStack) {
                        // We only want to move to the front, if we aren't going to launch on a
                        // different stack. If we launch on a different stack, we will put the
                        // task on top there.
                        //需要把task在目标stack中移动到前台
                        mTargetStack.moveTaskToFrontLocked(
                                intentActivity.task, mNoAnimation, mOptions,
                                mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
                        mMovedToFront = true;
                    } else if (launchStack.mStackId == DOCKED_STACK_ID
                            || launchStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
                        ......
                    }
                    mOptions = null;
                }//if(!willClearTask)
                updateTaskReturnToType(intentActivity.task, mLaunchFlags, focusStack);
            }
        }

        ......

        //这个就是如果设置了NEW_DOCMENT,且启动该task中的Activity带有此flag就需要做清理操作
        if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
            return mTargetStack.resetTaskIfNeededLocked(intentActivity, mStartActivity);
        }
        return intentActivity;
    }

这个函数主要做的就是将目标task挪动到stack的顶部,以及针对FLAG_ACTIVITY_RESET_TASK_IF_NEEDED去做清理

step5 根据要启动的Activity的flag去做Task中的清理操作

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...
            mReuseTask = intentActivity.task;
            //clear_task清理掉task中所有的Activity
            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) {
            //clear_top清理掉task中这个Activity之上的
            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");
                }
            }
            //要启动的Activity的组建名同目标task中的rootActivity相同的情况下
        } 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.
            //目标task中的顶部的Activity和驱动Activity相同
            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);
                }
                //重新发送一个intent来避免重新建一个Activity
                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;
            }
        }
        ......
    }

这里主要是针对CLEAR_TASK和CLEAR_TOP两个flag做清理,NEW_DOCUMENT的清理在上一步以及完成

实例分析

在基本理清了启动过程中的关键函数点,写到这里我觉得可以把针对这几个flag和launchmode的文字描述和实际效果带入代码中去

singleTop:可以有多个实例,但是不允许多个相同Activity叠加 如果当前栈顶不是一个singleTop的Activity,那么要启动一个singleTop的Activity跟其他模式并没有差别 但是如果当前栈顶是一个singleTop的Activity,那我们回到代码看看是怎么处理的 ActivityStarter的setTaskFormInTask

// Check whether we should actually launch the new activity in to the task,
        // or just reuse the current activity on top.
        ActivityRecord top = mInTask.getTopActivity();
        if (top != null && top.realActivity.equals(mStartActivity.realActivity) && top.userId == mStartActivity.userId) {
            if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                    || mLaunchSingleTop || mLaunchSingleTask) {
                ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
                if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                    // We don't need to start a new activity, and the client said not to do
                    // anything if that is the case, so this is it!
                    return START_RETURN_INTENT_TO_CALLER;
                }
                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
                return START_DELIVERED_TO_TOP;
            }
        }

singleTask:启动的目标Activity实例如果已经存在task容器栈中,不管当前实例处于栈的任何位置,是栈顶也好,栈底也好,还是处于栈中间,只要目标Activity实例处于task容器栈中,都可以重用该Activity实例对象,然后,把处于该Activity实例对象上面全部Activity实例清除掉,并且,task容器栈中永远只有唯一实例对象,不会存在两个相同的实例对象。 其实在保证实例数量的同时,也就会顺带操作结束其他Activity的生命。因为当目标Activity不在栈顶的话,且不想多创建一个实例的话,android做的并不是其挪动到栈顶,因为这样做会导致整个栈内部的逻辑乱掉,索性直接清掉其上面的所有Activity。 在step5中

if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                || mLaunchSingleInstance || mLaunchSingleTask) {
            //在找到的目标Task上执行清理函数,清理mStartActivity之上的Activity
            ActivityRecord top = intentActivity.task.performClearTaskLocked(mStartActivity,
                    mLaunchFlags);
            ......
        }

singleInstance:只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。step3中遍历寻找目标Activity的实例

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);

其他的FLAG在此流程中的处理跟踪step1~step5遇到问题可以自行分析

    原文作者:Kelvin wu
    原文地址: https://zhuanlan.zhihu.com/p/27731961
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞