深入源码--Activity启动模式问题探究

2,830 阅读11分钟

目录


前言

这也是一个面试过程中经常会被问到的问题,网上的面试题也很多,但是对源码进行分析的文章比较难找,所以我就自己分析了一下,也希望这篇文章能对面试者有所帮助~

一.进入主题 思考问题

分析源码之前呢,咱先思考几个问题,分析完成后,看能不能解决掉~

  • 若两个应用A和B,A中有两个Activity,A1和A2,B中有一个Activity B1,这三个Activity都是SingleTask模式,启动顺序是A1 -> B1 -> A2,然后点击返回按钮,会如何?
  • 接着前面问题,如果A2是Standard模式 + FlAG_ACTIVITY_NEW_TASK呢?
  • FLAG_ACTIVITY_NEW_TASK 有什么用?taskAffinity又是啥?

二.启动模式源码分析

启动模式相关源码都在ActivityStarter.java文件的startActivityUnchecked这个方法, 我们拆解这个函数来分析一下:( Android 9.0 代码为例 )

注意下两个很重要的属性 mLaunchFlags,mLaunchMode,后面的源码分析也主要围绕着两个属性在讨论

mLaunchFlags 包含启动的flag,比如FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_CLEAR_TASK等,作用是规定了如何去启动一个Activity,对栈的选择和处理。

mLaunchMode 表示启动模式,比如LAUNCH_SINGLE_INSTANCE,LAUNCH_SINGLE_TASK等。

2.1 初始化工作

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity) {
    
	// 初始化  mLaunchFlags  mLaunchMode
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);
        // 一.计算 mLaunchFlags
        computeLaunchingTaskFlags();
        //赋值 mSourceTask
        computeSourceStack();
        mIntent.setFlags(mLaunchFlags);
        ... ... ... 

看 computeLaunchingTaskFlags 方法

   private void computeLaunchingTaskFlags() {
	
        ... ... ...
        
	    // mSourceRecod 指的是 启动者,(注意区别于 被启动者 mStartActivity) mSourceRecord为null,表示我们不是从一个Activity来启动的
	    // 可能是从 一个Service 或者 ApplicationContext 来的
            if (mSourceRecord == null) {
                if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
             	    //mInTask mSourceRecord 都为null, 表示 不是从一个Activity 去启动另外一个Activity,所以不管什么	
		    //都加上 FLAG_ACTIVITY_NEW_TASK
                    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
                }
            } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
		// 如果 启动者 自己是 SINGLE_INSTANCE , 那么不管被启动的Activity是什么模式,mLaunchFlags 都加上 FLAG_ACTIVITY_NEW_TASK,
		// 这个新 Activity 需要运行在 自己的 栈内
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
		//如果launchMode是  SINGLE_INSTANCE 或者 SINGLE_TASK;  mLaunchFlags 添加  FLAG_ACTIVITY_NEW_TASK
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
            }
    }

2.1 小结一下

注意图中有3个地方为mLaunchFlags添加了FLAG_ACTIVITY_NEW_TASK

  • 如果启动者mSourceRecord为null,比如在一个Service启动了一个Activity,那么会为mLaunchFlags增加FLAG_ACTIVITY_NEW_TASK
  • 如果启动者mSourceRecord是一个 SingleInstance 类型的Activity,那么被启动者的mLaunchFlags就会加上 FLAG_ACTIVITY_NEW_TASK
  • 如果被启动者mStartActivity是SINGLE_INSTANCE或者SINGLE_TASK 类型的Activity,被启动者的mLaunchFlags都会加上FLAG_ACTIVITY_NEW_TASK

这里可以思考一个问题

一般在Service或者Application启动一个Activity,和在Activity内启动一个Activity有啥区别,要注意什么?(Actvivity重写了父类ContextWrapper的startActivity方法,Service调用的本质上还是ContextImpl的startActivity方法,可以从这个角度去看源码理解)

2.2 getResuableIntentActivity

这一部分还是startActivityUnchecked方法的一个片段,紧接着第一部分的源码

        //还是在 startActivityUnchecked 里面
        ... ... ...
        // 1. 为SINGLE_INSTANCE查找可以复用的Activity  
        // 2. 为 只有FLAG_ACTIVITY_NEW_TASK并且没有MULTI_TASK的
        // 3. SINGLE_TASK 查找可以添加的栈
        ActivityRecord reusedActivity = getReusableIntentActivity();
        ... ... ...

我们顺便也跳出startActivityUnchecked方法,去看一看getResuableIntentActivity方法。

    private ActivityRecord getReusableIntentActivity() {

	//putIntoExistingTask为true的条件
	//(1)当启动模式为SingleInstance;
	//(2)当启动模式为SingleTask;
	//(3)使用了Intent.FLAG_ACTIVITY_NEW_TASK标签,并且没有使用FLAG_ACTIVITY_MULTIPLE_TASK标签
        boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);

        putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
        ActivityRecord intentActivity = null;
		
        if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
        
            ... ... ...
            
        } else if (putIntoExistingTask) { //putIntoExistingTask为true时的策略
            if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {

		// SINGLE_INSTANCE 模式下去寻找,这里目的是findActivityRecord
               intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
                       mStartActivity.isActivityTypeHome());
            } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                ... ...
            } else {
                //这里要区别于singleInstance调用的方法!!!这里目的是findTaskRecord
                // Otherwise find the best task to put the activity in.
                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
            }
        }
        return intentActivity;
    }

2.2 小结一下

下图表示的是三种情况下putIntoExistingTask为true

putIntoExistingTask为true时,继续看代码我们还能得出另外一个结论:

mLaunchMode为SingleInstance时,走mSupervisor.findActivityLocked;其他情况下,比如我们的mStartActivity是一个standard模式的Activity,且只加上了FLAG_ACTIVITY_NEW_TASK的flag,会走mSupervisor.findTaskLocked

这里我们可以隐约根据方法名字猜出来出来,被启动者为SinglgeInstance的情况下,我们是寻找相等的ActivityRecord;而其他情况下,我们找的是一个最合适的栈(从注释也可以看出来)。实际上我们猜的很对,对于SingleInstance的情况,源码上也是遍历查找相同的ActivityRecord。但是对于其他情况呢?我们先思考一个问题,什么叫最合适的栈?它需要满足什么样的条件?

2.3 最合适的可复用栈

从mSupervisor.findTaskLocked进入,我们最后追踪到ActivityStack.java

   void findTaskLocked(ActivityRecord target, FindTaskResult result) {
        ... ... ...
        // 注意看这里是倒序遍历 mTaskHistory
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
        
            ... ... ...

            } else if (!isDocument && !taskIsDocument
                    && result.r == null && task.rootAffinity != null) {
                //检查 是不是 相同的  taskAffinity
                if (task.rootAffinity.equals(target.taskAffinity)) {
                    //当我们找到taskAffinity符合的栈之后,并没有立马break,而是继续去寻找,说明task的index越小,表示更适合
                    result.r = r;
                    result.matchedByRootAffinity = true;
                }
            } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
        }
    }

2.3 小结一下

最合适的栈 满足两个条件

1.Activity的taskAffinity和我们的task的rootAffinity相等

2.不同的task的rootAffinity可能是相等的,倒序遍历找到index最小的,也是最合适的

2.4 reusedActivity的处理

我们接着分析startActivityUnchecked代码

        // 三. 利用可以 复用的Activity 或者 复用栈
        if (reusedActivity != null) {
        
            ... ... ...
			
            // 如果是 SingleInstance 或者 SingleTask 或者  含有 FLAG_ACTIVITY_CLEAR_TOP 标识
            //我们可以判断出来  SingleInstance 或者 SingleTask  含有 FLAG_ACTIVITY_CLEAR_TOP 的效果
            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {

                //拿 reuseActivity 的栈
                final TaskRecord task = reusedActivity.getTask();
			
		// 比如 SingleTask  移除要启动的Activity之前的所有Activity
                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
                        mLaunchFlags);

                if (reusedActivity.getTask() == null) {
                    reusedActivity.setTask(task);
                }

                if (top != null) {
                    if (top.frontOfTask) {
                        // Activity aliases may mean we use different intents for the top activity,
                        // so make sure the task now has the identity of the new intent.
                        top.getTask().setIntent(mStartActivity);
                    }
	    	    //这里是 SingleInstance 或者 SingleTask ,会执行onNewIntent
                    deliverNewIntent(top);
                }
            }
            
            ... ... ...
            
            //启动者和被启动者是同一个
            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!  And for paranoia, make sure we have
                // correctly resumed the top activity.
                resumeTargetStackIfNeeded();
                return START_RETURN_INTENT_TO_CALLER;
            }
            
            if (reusedActivity != null) {
                //这里会去判断几种情况 singleTask singleInstance 和 singleTop
                setTaskFromIntentActivity(reusedActivity);

                if (!mAddingToTask && mReuseTask == null) {
                    
                    //singleInstance singleTask 都会走这里
                    //1.比如要启动的Activity是singleTask,且刚好在reusedActivity的栈内
                    //2.或者一个singleInstance模式的Activity再次被启动
                    resumeTargetStackIfNeeded();
                    if (outActivity != null && outActivity.length > 0) {
                        outActivity[0] = reusedActivity;
                    }
                    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
                }
            }
        }

继续来看一下setTaskFromIntentActivity这个方法

private void setTaskFromIntentActivity(ActivityRecord intentActivity) {

        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
        
            //如果是 FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TASK
            final TaskRecord task = intentActivity.getTask();
            //清空task
            task.performClearTaskLocked();
            mReuseTask = task;
            mReuseTask.setIntent(mStartActivity);
        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {

            //如果是 SingleInstance 或者 SingleTask 走这里,清空栈内要启动的Activity之前的所有Activity们。
            ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
                    mLaunchFlags);
            //如果top == null 继续走,不为null,就结束了这个方法
            if (top == null) {
                ... ... ...
            }
        } else if (mStartActivity.realActivity.equals(intentActivity.getTask().realActivity)) {
            // 判断是否是 SingleTop 模式
            // 这种情况如何复现? SingleTop + FLAG_ACTIVITY_NEW_TASK + taskAffinity。
            // FLAG_ACTIVITY_NEW_TASK + taskAffinity去指定一个特定存在的栈,且栈顶是我们要启动的singleTop模式的activity
            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                        || LAUNCH_SINGLE_TOP == mLaunchMode)
                    && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
                if (intentActivity.frontOfTask) {
                    intentActivity.getTask().setIntent(mStartActivity);
                }
                deliverNewIntent(intentActivity);
            } else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
                ... ... ...
            }
        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
            ... ... ...
        } else if (!intentActivity.getTask().rootWasReset) {
            ... ... ...
        }
    }

2.4 小结一下

前提是reusedActivity不为null,看两种典型情况:

1.如果是SingleTask或者是SingleInstance模式的Activity,则执行performClearTaskLocked方法,把要启动的Activity之前的所有Activity都清除掉。

2.reusedActivity的启动模式恰好是SingleTop,且也是我们要启动的Activity,执行 deliverNewIntent。

上述的两种情况能够满足下面的if判断 !mAddingToTask && mReuseTask == null ,然后return结束。

思考一个问题,情况1和2做例子来浮现一下?

2.5 判断SingleTop模式

继续看 startActivityUnchecked 后面的代码,这一部分是针对SingleTop模式的处理。

注意哦,之前我们也遇到了SingleTop模式的处理,就在上面的setTaskFromIntentActivity方法里。这两个有什么区别呢?

区别在这里的SingleTop模式判断的栈是我们当前展示的栈。而setTaskFromIntentActivity里的判断前提条件是在我们的reusedActivity不为空的情况下,对reusedActivity进行的判断,reusedActivity可能并不是当前的栈。


        ... ... ...
        //下面这段英文解释的很好(SingleTop模式),当我们要启动的Actiivty恰好是当前栈顶的Activity,检查是否只需要被启动一次
        // If the activity being launched is the same as the one currently at the top, then
        // we need to check if it should only be launched once.
        final ActivityStack topStack = mSupervisor.mFocusedStack;
        final ActivityRecord topFocused = topStack.getTopActivity();
        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
        final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.realActivity.equals(mStartActivity.realActivity)
                && top.userId == mStartActivity.userId
                && top.app != null && top.app.thread != null
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));  
        // SINGLE_TOP  SINGLE_TASK,要启动的Activiy恰好在栈顶
        // dontStart为true,表示不会去启动新的Activity,复用栈顶的Activity
        if (dontStart) {
            // For paranoia, make sure we have correctly resumed the top activity.
            topStack.mLastPausedActivity = null;
            if (mDoResume) {
                // resume  Activity
                mSupervisor.resumeFocusedStackTopActivityLocked();
            }
            ActivityOptions.abort(mOptions);
            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;
            }

            deliverNewIntent(top);

            // Don't use mStartActivity.task to show the toast. We're not starting a new activity
            // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
            mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
                    preferredLaunchDisplayId, topStack);

            return START_DELIVERED_TO_TOP;
        }

2.5 小结一下

这部分的代码主要处理SingleTop模式的Activity,要启动的Activity是SingleTop模式,且也恰好在当前栈的顶部,执行deliverNewIntent。

思考一个小问题,启动一个SingleTop模式的Activity,然后再次启动一次它,它的生命周期如何变化呢?

答案是 onCreate -> onStart -> onResume -> onPause -> onNewIntent -> onResume。

2.6 栈的复用和新建

继续分析startActivityUnChecked方法的最后一部分,这部分主要是关于栈是否需要新建。

    ... ... ...
    
        //必要条件是有FLAG_ACTIVITY_NEW_TASK
        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
            //要建立新栈或者使用已经存在的栈,FLAG_ACTIVITY_NEW_TASK是必要条件
            newTask = true;
            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
        } else if (mSourceRecord != null) {
            //把被启动的mStartActivity放在启动者mSourceRecord所在的栈上
            result = setTaskFromSourceRecord();
        }
     ... ... ...
     

setTaskFromReuseOrCreateNewTask 方法

private int setTaskFromReuseOrCreateNewTask(
            TaskRecord taskToAffiliate, ActivityStack topStack) {
            
        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);

        if (mReuseTask == null) {
            //新建一个栈
            final TaskRecord task = mTargetStack.createTaskRecord(
                    mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
                    mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                    mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
                    mOptions);
            addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
            updateBounds(mStartActivity.getTask(), mLaunchParams.mBounds);

        } else {
            //用旧栈
            addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask");
        }
        ... ... ...

2.6 小结一下

我们可以看出,如果需要我们去新建一个栈或者把我们要启动的Activity放在已经存在某个栈中,FLAG_ACTIVITY_NEW_TASK是必要条件。

三. 启动模式流程图

对于第二部分的描述我用一个图来总结一下。

四. 总结

我们先总结一下几个重要的结论:

  1. FLAG_ACTIVITY_NEW_TASK是新建栈或者复用栈的必要条件。SingleTask,SingleInstance会为mLaunchFlags自动添加FLAG_ACTIVITY_NEW_TASK。也就是说他们都有存在不使用当前栈的可能。SingleInstance是很好理解的,SingleTask需要注意下,关于SingleTask我会详细说明,不用担心。

  2. 新建栈或者复用已经存在栈的充要条件是什么?

(1) FLAG_ACTIVITY_NEW_TASK + taskAffinity(taskAffinity必须与当前显示的栈的rootAffinity不相同,taskAffinity默认是包名)。

(2) FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_MULTIPLE_TASK 这是必定会新建一个栈的

  1. SingleTask可以理解成FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP + (taskAffinity == 该应用包名)。解释一下,FLAG_ACTIVITY_CLEAR_TOP的作用是啥?比如我们要启动的SingleTask模式的Activity已经在栈内,且不在栈的头部,会把之前的Activity全部出栈。

  2. SingleInstance比较特殊,首先SingleInstance模式的Activity会被加上FLAG_ACTIVITY_NEW_TASK,这一点和SingleTask一样。特殊的地方其一在于如果启动过了,会去遍历找相等的Activity,查找过程不一样。而不像SingleTask是去找"合适的"栈(2.3最合适的可复用栈),根据taskAffinity来查找。其二在于SingleInstance一个栈只能存放一个Activity,能做到这个的原因是我们在根据taskAffinity找到合适的栈的时候,如果发现是SingleInstance模式Activity的栈,直接忽略。

回答问题

到这里也算是完结了,我们回过头来思考一下最开始提出的几个问题。

  • 若两个应用A和B,A中有两个Activity,A1和A2,B中有一个Activity B1,这三个Activity都是SingleTask模式,启动顺序是A1 -> B1 -> A2,然后点击返回按钮,返回到的界面是A1。因为SingleTask模式本身含有FLAG_ACTIVITY_NEW_TASK,这里由于taskAffinity也和当前展示的栈不相同(taskAffinity默认是包名),所以会去找"合适的"栈(2.3最合适的可复用栈)放入。

  • 接着前面问题,如果A2是Standard模式 + FlAG_ACTIVITY_NEW_TASK呢? 会回到A1,因为A2和A1在一个栈,类似第一种情况。

  • FLAG_ACTIVITY_NEW_TASK 有什么用?taskAffinity又是啥?这个问题可以看上面的小结。

最后

关于启动模式的问题非常多,这里分析了一些常见的情况。

我们知道SingleInstance模式的Activity在一个应用内是不能当做中间Activity,比如A1 、 A2 、A3 这三个Activity中,A2是SingleInstance模式,A1,A2是Standard模式,如果启动顺序是A1 -> A2 -> A3,然后点击返回键,会返回到A1。假如者三个Activity都是Standard模式,想要实现一样的效果如何做?

对比了下同类型的文章,我的这篇文章没有上来就介绍Standard,SingleTask,SingleInstance,SingleTop模式的基本概念和使用介绍,而是更偏向源码性的研究,建议把每种模式在这段代码里面走一遍,就能从本质上看待启动模式之间的区别,对常说的启动模式概念理解更深刻。