天天看点

android Activity LaunchMode原理解析

1.launchMode介绍

android:launchMode共有四种模式可与 Intent 对象中的 Activity 标记(FLAG_ACTIVITY_* 常量)协同工作,以确定在调用 Activity 处理 Intent 时应执行的操作。这些模式是:

  • standard
  • singleTop
  • singleTask
  • singleInstance

默认模式是“standard”。

这些模式可分为两大类:

  • standard和singleTop 为一类,可多次进行实例化
使用standard或singleTop启动模式的 Activity 可多次进行实例化。实例可归属任何Task,并且可位于 Activity 堆栈中的任何位置。通常,它们会启动到名为 startActivity() 的任务中(除非 Intent 对象包含 FLAG_ACTIVITY_NEW_TASK 指令,在此情况下会选择其他任务 — 请参阅 taskAffinity 属性)。
  • singleTask和singleInstance 为另一类。
singleTask和singleInstance 模式Activity 只能启动Task。它们始终位于Activity 堆栈的根位置。此外,设备一次只能保留一个 Activity 实例,即一次只允许一个此类任务。

具体的差别如下图:

android Activity LaunchMode原理解析

standard和singleTop模式

standard和singleTop模式只有一处不同:在singleTop模式如果目标任务的 Activity 堆栈顶部已有一个 Activity 实例,则该实例会(通过调用 onNewIntent())接收新的 Intent,此时不会创建新实例。

在其他情况下(例如,如果“singleTop”Activity 的某个现有实例虽在目标任务内,但未处于堆栈顶部,或者虽然位于堆栈顶部,但不在目标任务中),系统会创建新实例并将其送入堆栈,和standard一样。

singleTask和singleInstance模式

singleTask和singleInstance模式同样只有一处不同:singleTask模式的Activity 允许其他 Activity 成为其Task的一部分。该 Activity 始终位于其任务的根位置,但其他 Activity(必然是“standard”和“singleTop”Activity)可以启动到该任务中。另一方面,singleInstance模式的Activity 不允许其他 Activity 成为其Task的一部分,它是Task中唯一的 Activity。如果它启动另一个 Activity,则系统会将该 Activity 分配给其他任务,就如同 Intent 中包含 FLAG_ACTIVITY_NEW_TASK 一样。

2.launchMode实现原理分析

在android里面的Activity体系结构(3)_ActivityRecord和Activity状态变化分析说明 有介绍,在

ActivityStarter里面startActivityUnchecked对launchMode进行处理

2.1 startActivityUnchecked

核心逻辑在startActivityUnchecked这个里面处理,分2类:

  • singleTask和singleInstance模式:处理在getReusableIntentActivity,不会重复创建Activity,然后reusedActivity.getTaskRecord()找到可以复用的Task
    • singleInstance模式:使用mRootActivityContainer.findActivity查找,因为在singleInstance模式下,只能存在一个Activity实例
    • singleTask模式:使用mRootActivityContainer.findTask找到最合适的Task,因为在singleTask模式下,可能在多个Task中存在Activity实例
  • standard和singleTop模式:singleTask和singleInstance模式处理完后,然后处理standard和singleTop模式。调用topStack.topRunningNonDelayedActivityLocked(mNotTop)获取当前top Activity,用作standard和singleTop分别处理
    • singleTop模式:如果非空且为mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0,调用deliverNewIntent,直接启动
    • standard模式:非以上3种场景,直接创建一个新的Activity

下面2.2、2.3、 2.4 分别介绍几种模式的处理,建议对着源代码一起看,方法很长,下面贴的代码有裁剪

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity, boolean restrictedBgActivity) {

        // 复用Activity
        ActivityRecord reusedActivity = getReusableIntentActivity();
        // 1.有可复用的Activity
        if (reusedActivity != null) {
           
            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                // 找到可以复用的Task
                final TaskRecord task = reusedActivity.getTaskRecord();

                // In this situation we want to remove all activities from the task up to the one
                // being started. In most cases this means we are resetting the task to its initial
                // state.
                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
                        mLaunchFlags);

                // The above code can remove {@code reusedActivity} from the task, leading to the
                // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
                // task reference is needed in the call below to
                // {@link setTargetStackAndMoveToFrontIfNeeded}.
                if (reusedActivity.getTaskRecord() == null) {
                    reusedActivity.setTask(task);
                }
                //  要启动的Activity位于栈顶,直接调用deliverNewIntent,使用onNewIntent启动
                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.getTaskRecord().setIntent(mStartActivity);
                    }
                    deliverNewIntent(top);
                }
            }

            mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded
                    (false /* forceSend */, reusedActivity);

            reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);

            final ActivityRecord outResult =
                    outActivity != null && outActivity.length > 0 ? outActivity[0] : null;

            // When there is a reused activity and the current result is a trampoline activity,
            // set the reused activity as the result.
            if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
                outActivity[0] = 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) {
                        // The reusedActivity could be finishing, for example of starting an
                        // activity with FLAG_ACTIVITY_CLEAR_TOP flag. In that case, return the
                        // top running activity in the task instead.
                        outActivity[0] = reusedActivity.finishing
                                ? reusedActivity.getTaskRecord().getTopActivity() : reusedActivity;
                    }

                    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
                }
            }
        }
        
        // 2. reusedActivity为空
        // 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 = mRootActivityContainer.getTopDisplayFocusedStack();
        final ActivityRecord topFocused = topStack.getTopActivity();
        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
        final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
                && top.mUserId == mStartActivity.mUserId
                && top.attachedToProcess()
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
                // This allows home activity to automatically launch on secondary display when
                // display added, if home was the top activity on default display, instead of
                // sending new intent to the home activity on default display.
                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
        if (dontStart) {
            // For paranoia, make sure we have correctly resumed the top activity.
            topStack.mLastPausedActivity = null;
            if (mDoResume) {
                mRootActivityContainer.resumeFocusedStacksTopActivities();
            }
            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.getTaskRecord(), preferredWindowingMode,
                    mPreferredDisplayId, topStack);

            return START_DELIVERED_TO_TOP;
        }

       
        boolean newTask = false;
        final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
                ? mSourceRecord.getTaskRecord() : null;

        // Should this be considered a new task?
        int result = START_SUCCESS;
        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
            newTask = true;
            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
        } else if (mSourceRecord != null) {
            result = setTaskFromSourceRecord();
        } else if (mInTask != null) {
            result = setTaskFromInTask();
        } else {
            // This not being started from an existing activity, and not part of a new task...
            // just put it in the top task, though these days this case should never happen.
            result = setTaskToCurrentTopOrCreateNewTask();
        }
        if (result != START_SUCCESS) {
            return result;
        }

        mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
                mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
        mService.getPackageManagerInternalLocked().grantEphemeralAccess(
                mStartActivity.mUserId, mIntent, UserHandle.getAppId(mStartActivity.appInfo.uid),
                UserHandle.getAppId(mCallingUid));
        if (newTask) {
            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId,
                    mStartActivity.getTaskRecord().taskId);
        }
        ActivityStack.logStartActivity(
                EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTaskRecord());
        mTargetStack.mLastPausedActivity = null;

        mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(
                false /* forceSend */, mStartActivity);

        mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
                mOptions);
        if (mDoResume) {
            final ActivityRecord topTaskActivity =
                    mStartActivity.getTaskRecord().topRunningActivityLocked();
            if (!mTargetStack.isFocusable()
                    || (topTaskActivity != null && topTaskActivity.mTaskOverlay
                    && mStartActivity != topTaskActivity)) {
                // If the activity is not focusable, we can't resume it, but still would like to
                // make sure it becomes visible as it starts (this will also trigger entry
                // animation). An example of this are PIP activities.
                // Also, we don't want to resume activities in a task that currently has an overlay
                // as the starting activity just needs to be in the visible paused state until the
                // over is removed.
                mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
                // Go ahead and tell window manager to execute app transition for this activity
                // since the app transition will not be triggered through the resume channel.
                mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
            } else {
                // If the target stack was not previously focusable (previous top running activity
                // on that stack was not visible) then any prior calls to move the stack to the
                // will not update the focused stack.  If starting the new activity now allows the
                // task stack to be focusable, then ensure that we now update the focused stack
                // accordingly.
                if (mTargetStack.isFocusable()
                        && !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
                    mTargetStack.moveToFront("startActivityUnchecked");
                }
                mRootActivityContainer.resumeFocusedStacksTopActivities(
                        mTargetStack, mStartActivity, mOptions);
            }
        } else if (mStartActivity != null) {
            mSupervisor.mRecentTasks.add(mStartActivity.getTaskRecord());
        }
        mRootActivityContainer.updateUserStack(mStartActivity.mUserId, mTargetStack);

        mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTaskRecord(),
                preferredWindowingMode, mPreferredDisplayId, mTargetStack);

        return START_SUCCESS;
    }
           
2.2 getReusableIntentActivity 处理singleTask和singleInstance模式

在此方法里面,使用isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)判断 ,处理singleTask和singleInstance模式

方法里面的注释写的很清楚,不用单独作说明:

  • 针对singleInstance模式,使用mRootActivityContainer.findActivity,因为在此模式下,只会存在一个Activity实例,通过Activity名称直接在ActivityStack里面查找
  • 针对singleTask模式,mRootActivityContainer.findTask,因为在此模式下,可能在多个TaskRecord里面存在,所以需要找到合适的TaskRecord

从实现上来看,也体现了在singleTask和singleInstance模式里面的唯一差别

/**
     * 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)
                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
        // 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 = mRootActivityContainer.anyTaskForId(mOptions.getLaunchTaskId());
            intentActivity = task != null ? task.getTopActivity() : null;
        } else if (putIntoExistingTask) {
            if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
                // 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 = mRootActivityContainer.findActivity(mIntent, mStartActivity.info,
                       mStartActivity.isActivityTypeHome());
            } 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 = mRootActivityContainer.findActivity(mIntent, mStartActivity.info,
                        !(LAUNCH_SINGLE_TASK == mLaunchMode));
            } else {
                // Otherwise find the best task to put the activity in.
                intentActivity =
                        mRootActivityContainer.findTask(mStartActivity, mPreferredDisplayId);
            }
        }

        if (intentActivity != null
                && (mStartActivity.isActivityTypeHome() || intentActivity.isActivityTypeHome())
                && intentActivity.getDisplayId() != mPreferredDisplayId) {
            // Do not reuse home activity on other displays.
            intentActivity = null;
        }

        return intentActivity;
    }
           

在找到可以复用的Activity的时候,找到对应的TaskRecord,清除目标Task里面位于要启动的Activity之上的Activity

// 找到可以复用的Task
final TaskRecord task = reusedActivity.getTaskRecord();
// 清除目标Task里面位于要启动的Activity之上的Activity
final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,mLaunchFlags);
           

进行上面的操作后,目标Activity位于TaskRecord的栈顶,直接调用deliverNewIntent,调用top Activity的onNewIntent

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.getTaskRecord().setIntent(mStartActivity);
    }
    deliverNewIntent(top);
}
           
2.3 topRunningNonDelayedActivityLocked 处理singleTop模式

getReusableIntentActivity方法返回非空时,把singleTask和singleInstance模式处理完。返回空时,先获取当前FocusStack,然后获取top Activity

在topRunningNonDelayedActivityLocked方法里面,遍历ActivityStack里面的所有TaskRecord

ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            final TaskRecord task = mTaskHistory.get(taskNdx);
            final ArrayList<ActivityRecord> activities = task.mActivities;
            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                ActivityRecord r = activities.get(activityNdx);
                if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
                    return r;
                }
            }
        }
        return null;
    }
           

如果当前要启动的Activity和当前Top Activity是一样的,通过dontStart变量来确认是否需要只启动一次

final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
                && top.mUserId == mStartActivity.mUserId
                && top.attachedToProcess()
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
                // This allows home activity to automatically launch on secondary display when
                // display added, if home was the top activity on default display, instead of
                // sending new intent to the home activity on default display.
                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
           

当启动模式为singleTop模式时,mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP,dontStart为true

if (dontStart) {
            // For paranoia, make sure we have correctly resumed the top activity.
            topStack.mLastPausedActivity = null;
            if (mDoResume) {
                mRootActivityContainer.resumeFocusedStacksTopActivities();
            }
            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.getTaskRecord(), preferredWindowingMode,
                    mPreferredDisplayId, topStack);

            return START_DELIVERED_TO_TOP;
        }
           

如果启动模式为singleTop模式,且当前top.mActivityComponent.equals(mStartActivity.mActivityComponent) 即top Activity和要启动的Activity是一致的时候,调用deliverNewIntent,调用onNewIntent方法,不会再创建一个新的Activity

2.4 处理stander模式

前面3种模式处理完后,最后处理stander模式。判断是否需要创建一个newTask,来保存启动的Activity

// Should this be considered a new task?
int result = START_SUCCESS;
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
    && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
    newTask = true;
    result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
}       
           

后面返回START_SUCCESS后,即startActivity返回,后面会再走

android里面的Activity体系结构(3)_ActivityRecord和Activity状态变化分析说明的 4.2 第2阶段:DeskClock应用进入创建并启动

创建一个新的Activity

继续阅读