• Activity的launchMode详细分析


    在研究了Activity的启动过程后,我觉得很有必要对Activity的launchMode进行分析一下,因为到目前为止,我发现网上对launchMode的讲解都是通过实例讲解,看完了总是似懂非懂的感觉,并没有根本上理解launchMode的原理。这里我会从源码的角度讲解launchMode。相信大家会和我一样,看了源码之后就会有一种豁然开朗的感觉。

    Activity的启动模式一种有四种,分别如下:

    1、standard

    2、singleTop

    3、singleTask

    4、singleInstance

    这里我们分两种情况讨论上述四种启动模式:app内和app之间

    首先讨论app内

    standard:不论当前任务栈中是否存在该Activity,都会新建一个Activity,如 任务栈为A B,要启动B 那么任务栈为 A B B

    singleTop:如果当前要创建的Activity就在任务栈的顶端,那么不会创建新的Activity,仅仅调用Activity的onNewIntent,如果不在栈顶(或者栈中没有该Activity),那么还是会创建新的Activity,如任务栈为A B 启动B 任务栈变为 A B 如果 启动A 那么任务栈为 A B A

    singleTask:如果当前任务中存在要启动的Activity,那么就不会创建新的Activity,如果不存在就会创建新的Activity,如任务栈为 A B C,启动B ,那么任务栈就会变为A B

    singleInstance:将一个Activity的launchMode设置为该值时,表明这个Activity独自占用一个任务队列,这个队列中不让在加入其他的Activity

    不同的app之间:

    既然在不同的app之间,那么就说明是两个任务栈,并且有一个处理前台,一个运行在后台

    standard:当前台的Activity启动后台任务的Activity,不管这个Activity在后台任务栈中是否已经存在,都会创建一个新的Activity,并将它加入前台的任务栈中

    singleTop:如果前台的Activity启动后台任务的Activity,并且这个Activity已经在后台任务的栈顶,和app内不同的是,这里还不能确定是否会创建新的Activity,这里还需要看启动Activity的Intent里面是否有Intent.FLAG_ACTIVITY_NEW_TASK,如果此标记,那么就会将后台的任务队列移动到前端,如 前端任务栈 A B 后端任务栈 E F C,现在前端需要启动一个C,如果有Intent.FLAG_ACTIVITY_NEW_TASK,那么后端任务栈就会移到前端(并调用C的onNewIntent),前端栈退居后端。如果没有这个标记,那么仅仅就是在前端任务栈中创建一个新的Activity C。

    singleTask:如果前台的Activity启动后台任务的Activity,不管这个Activity是否在栈顶,都会将后台的任务栈移到前台,前台任务栈移至后台。这里不需要考虑标记问题,因为被启动的Activity如果是singleTask,那么自动在启动Activity的intent中加入上述标记。

    singleInstance:如果前台Activity启动后台任务的Activity,如果后台任务栈中已经有该Activity,那么就会调用该Activity的onNewIntent,并且后台任务还是在后台。如果后台任务栈中没有该Activity,那么会重新创建一个Acitivyt,并单独放入一个任务栈,其实在启动该Acitivity的Intent中也会加入上述标记

    其实还有一种情况比较特殊,不过很少使用,就是在一个app之间,我们给某一个Activity配置了taskAffinity属性,这个属性会影响singleTask等属性,这个可以大家自己去分析。

    下面我们就来看看和launchMode处理有关的代码吧

    对launchMode处理的逻辑主要是放在了ActivityStack的startActivityUncheckedLocked方法中,这份方法的逻辑有些复杂,我们来一部分一部分的分析:

    第一部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    if (sourceRecord == null) {
               // This activity is not being started from another...  in this
               // case we -always- start a new task.
               if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
                   Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: "
                         + intent);
                   launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
               }
           } else if (sourceRecord.launchMode == ActivityInfo.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.
               launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
           } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
                   || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
               // The activity being started is a single instance...  it always
               // gets launched into its own task.
               launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
           }



    首先判断sourceRecord是否为Null(桌面启动一个Activity或者通过Context启动一个Activity时sourceRecord为null),如果在启动的Intent中没有FLAG_ACTIVITY_NEW_TASK那么就会在该Intent中添加该标记。

    如果sourceRecord的launchMode设置的是singleinstance,那么就会在Intent添加FLAG_ACTIVITY_NEW_TASK,因为对于singleinstance的Activity,是不会和别人共享一个队列的。

    如果被启动的Activity的launchMode是singleinstance或者singletask,那么也会在Intent中添加FLAG_ACTIVITY_NEW_TASK标记(上面我们已经说过)。

    在看第二部分代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
        // 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.");
        sendActivityResultLocked(-1,
                r.resultTo, r.resultWho, r.requestCode,
            Activity.RESULT_CANCELED, null);
        r.resultTo = null;
    }



    通过这段代码可以当Intent中包含了Intent.FLAG_ACTIVITY_NEW_TASK标记时,是不能使用startActivityforResult方法启动Activity的,也就是说如果一个Activity是sinleTask或者singleInstance时,都不能通过startActivityForResult方法调起(可以调起,但是无法传回值)

    第三部分代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    boolean addingToTask = false;
            boolean movedHome = false;
            TaskRecord reuseTask = null;
            if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
                    (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                    || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
                    || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                // If bring to front is requested, and no result is requested, and
                // we can find a task that was started with this same
                // component, then instead of launching bring that one to the front.
                if (r.resultTo == null) {
                    // See if there is a task to bring to the front.  If this is
                    // a SINGLE_INSTANCE activity, there can be one and only one
                    // instance of it in the history, and it is always in its own
                    // unique task, so we do a special search.
                    ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
                            ? findTaskLocked(intent, r.info)
                            : findActivityLocked(intent, r.info);
                    if (taskTop != null) {
                        if (taskTop.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.
                            taskTop.task.setIntent(intent, r.info);
                        }
                        // 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.
                        ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop);
                        if (curTop != null && curTop.task != taskTop.task) {
                            r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
                            boolean callerAtFront = sourceRecord == null
                                    || curTop.task == sourceRecord.task;
                            if (callerAtFront) {
                                // We really do want to push this one into the
                                // user's face, right now.
                                movedHome = true;
                                moveHomeToFrontFromLaunchLocked(launchFlags);
                                moveTaskToFrontLocked(taskTop.task, r, options);
                                options = null;
                            }
                        }
                        // If the caller has requested that the target task be
                        // reset, then do so.
                        if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                            taskTop = resetTaskIfNeededLocked(taskTop, r);
                        }
                        if ((startFlags&ActivityManager.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.
                            if (doResume) {
                                resumeTopActivityLocked(null, options);
                            } else {
                                ActivityOptions.abort(options);
                            }
                            return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                        }
                        if ((launchFlags &
                                (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
                                == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.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...
                            reuseTask = taskTop.task;
                            performClearTaskLocked(taskTop.task.taskId);
                            reuseTask.setIntent(r.intent, r.info);
                        } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                            // 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.
                            ActivityRecord top = performClearTaskLocked(
                                    taskTop.task.taskId, r, launchFlags);
                            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.task.setIntent(r.intent, r.info);
                                }
                                logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
                                top.deliverNewIntentLocked(callingUid, r.intent);
                            } else {
                                // 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.
                                addingToTask = true;
                                // Now pretend like this activity is being started
                                // by the top of its task, so it is put in the
                                // right place.
                                sourceRecord = taskTop;
                            }
                        } else if (r.realActivity.equals(taskTop.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 ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
                                    && taskTop.realActivity.equals(r.realActivity)) {
                                logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task);
                                if (taskTop.frontOfTask) {
                                    taskTop.task.setIntent(r.intent, r.info);
                                }
                                taskTop.deliverNewIntentLocked(callingUid, r.intent);
                            } else if (!r.intent.filterEquals(taskTop.task.intent)) {
                                // 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.
                                addingToTask = true;
                                sourceRecord = taskTop;
                            }
                        } else if ((launchFlags&Intent.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.
                            addingToTask = true;
                            sourceRecord = taskTop;
                        } else if (!taskTop.task.rootWasReset) {
                            // In this case we are launching in to 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.
                            taskTop.task.setIntent(r.intent, r.info);
                        }
                        if (!addingToTask && reuseTask == 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.
                            if (doResume) {
                                resumeTopActivityLocked(null, options);
                            } else {
                                ActivityOptions.abort(options);
                            }
                            return ActivityManager.START_TASK_TO_FRONT;
                        }
                    }
                }
            }

    这段代码的逻辑有些复杂,但是功能很简单,就是为要启动的Activity寻找或者创建一个任务栈,现在我使用singleTask为例,跟踪一下上述代码流程。

    假设后台任务栈为 A B C,前台任务中要启动一个Activity B,我们就使用这个需求来跟踪上面的代码逻辑。

    首先注意默认的addingToTask是false,reuseTask为null,由于是singleTask的,所以((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)是成立的,即使这里不成立,后面的逻辑也是成立,所以必然进入该if语句

    继续跟进代码,会发现调用了findTaskLocked方法返回一个ActivityRecord,你可以查看findTaskLocked的逻辑,你会知道这个ActiivtyRecord 就是对应的是Activity C,继续往下走,就会调用 moveTaskToFrontLocked将后台任务移动到前台,由于我们考虑的是launchMode是singleTask,所以将会进入

    1
    2
    3
    else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE)


    进入上面的if语句之后,就会调用performClearTaskLocked返回一个ActivityRecord,你可以去研究一下performClearTaskLocked的逻辑,这个方法传入了一个ActivityRecord r,就是将任务栈中r之上的AcrivityRecord 清除掉,并返回ActivityRecord r,这里返回的ActivityRecord 对应的就是Activity B。接着调用Activity B的onNewIntent方法。执行了这个else if,其他else if是都不会执行的,所以就直接执行到了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if (!addingToTask && reuseTask == 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.
                       if (doResume) {
                           resumeTopActivityLocked(null, options);
                       } else {
                           ActivityOptions.abort(options);
                       }
                       return ActivityManager.START_TASK_TO_FRONT;
                   }


    这里直接调用了resumeTopActivityLocked方法。到这里singleTask就分析完了,其他的启动模式大家可以按照这种思路进行分析。

    第四部分代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    if (r.packageName != null) {
                // 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.
                ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
                if (top != null && r.resultTo == null) {
                    if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
                        if (top.app != null && top.app.thread != null) {
                            if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
                                logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
                                // For paranoia, make sure we have correctly
                                // resumed the top activity.
                                if (doResume) {
                                    resumeTopActivityLocked(null);
                                }
                                ActivityOptions.abort(options);
                                if ((startFlags&ActivityManager.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 ActivityManager.START_RETURN_INTENT_TO_CALLER;
                                }
                                top.deliverNewIntentLocked(callingUid, r.intent);
                                return ActivityManager.START_DELIVERED_TO_TOP;
                            }
                        }
                    }
                }
     
            } else {
                if (r.resultTo != null) {
                    sendActivityResultLocked(-1,
                            r.resultTo, r.resultWho, r.requestCode,
                        Activity.RESULT_CANCELED, null);
                }
                ActivityOptions.abort(options);
                return ActivityManager.START_CLASS_NOT_FOUND;
            }
     
            boolean newTask = false;
            boolean keepCurTransition = false;
     
            // Should this be considered a new task?
            if (r.resultTo == null && !addingToTask
                    && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
                if (reuseTask == null) {
                    // todo: should do better management of integers.
                    mService.mCurTask++;
                    if (mService.mCurTask <= 0) {
                        mService.mCurTask = 1;
                    }
                    r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true);
                    if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                            + " in new task " + r.task);
                } else {
                    r.setTask(reuseTask, reuseTask, true);
                }
                newTask = true;
                if (!movedHome) {
                    moveHomeToFrontFromLaunchLocked(launchFlags);
                }
                 
            } else if (sourceRecord != null) {
                if (!addingToTask &&
                        (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
                    // In this case, we are adding the activity to an existing
                    // task, but the caller has asked to clear that task if the
                    // activity is already running.
                    ActivityRecord top = performClearTaskLocked(
                            sourceRecord.task.taskId, r, launchFlags);
                    keepCurTransition = true;
                    if (top != null) {
                        logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
                        top.deliverNewIntentLocked(callingUid, r.intent);
                        // For paranoia, make sure we have correctly
                        // resumed the top activity.
                        if (doResume) {
                            resumeTopActivityLocked(null);
                        }
                        ActivityOptions.abort(options);
                        return ActivityManager.START_DELIVERED_TO_TOP;
                    }
                } else if (!addingToTask &&
                        (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
                    // In this case, we are launching an activity in our own task
                    // that may already be running somewhere in the history, and
                    // we want to shuffle it to the front of the stack if so.
                    int where = findActivityInHistoryLocked(r, sourceRecord.task.taskId);
                    if (where >= 0) {
                        ActivityRecord top = moveActivityToFrontLocked(where);
                        logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
                        top.updateOptionsLocked(options);
                        top.deliverNewIntentLocked(callingUid, r.intent);
                        if (doResume) {
                            resumeTopActivityLocked(null);
                        }
                        return ActivityManager.START_DELIVERED_TO_TOP;
                    }
                }
                // An existing activity is starting this new activity, so we want
                // to keep the new one in the same task as the one that is starting
                // it.
                r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false);
                if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                        + " in existing task " + r.task);
     
            } 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.
                final int N = mHistory.size();
                ActivityRecord prev =
                    N > 0 ? mHistory.get(N-1) : null;
                r.setTask(prev != null
                        ? prev.task
                        : new TaskRecord(mService.mCurTask, r.info, intent), null, true);
                if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                        + " in new guessed " + r.task);
            }
     
            mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
                    intent, r.getUriPermissionsLocked());
     
            if (newTask) {
                EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId);
            }
            logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
            startActivityLocked(r, newTask, doResume, keepCurTransition, options);
            return ActivityManager.START_SUCCESS;



    我刚刚使用singleTask模式分析时,并没有走到这里,大家可以另一种情况来分析,就可以走到这里。

    后台任务栈 A B,前台任务栈 E F,在前台任务栈中要启动后台任务的一个Activity C,也就是说后台任务栈中不存在C的情况,或者在一个app内通过配置taskAffinity属性也可以走到这里。

  • 相关阅读:
    图片原理解说(综合版:JPEG,PNG,BMP,GIF)
    PHP ImageMagick
    [android] 练习使用ListView(三)
    [android] 练习使用ListView(二)
    [android] 练习使用ListView(一)
    [android] 练习viewpagerindicator的使用(二)
    [android] 练习viewpagerindicator的使用(一)
    [android] 练习PopupWindow实现对话框
    [android] 练习样式主题自定义activity切换动画
    [android] 安卓自定义样式和主题
  • 原文地址:https://www.cnblogs.com/jimmyfang/p/4787021.html
Copyright © 2020-2023  润新知