美文网首页Android 基础
Activity 启动流程学习总结(附源码)

Activity 启动流程学习总结(附源码)

作者: 0xCAFEBOY | 来源:发表于2019-06-27 17:40 被阅读1次

    前言

    关于 Activity 启动,Android 中场景大致有两个:

    1. 从 launcher 中启动应用,触发该应用默认 Activity 的启动。这种 Activity 都是在新进程和新的任务栈中启动的,所以涉及到新进程和新任务栈的初始化
    2. 应用程序内部启动非默认 Activity, 被启动的 Activity 一般在原来的进程和任务栈中启动

    本文主要介绍第一种场景

    背景知识

    进程与线程

    由于 Activity 的启动流程中涉及了大量的进程间通信,例如:ActivityManagerService 和 ActivityStack 位于同一进程,ApplicationThread 和 ActivityThread 位于同一进程。所以有必要梳理一下进程与线程的区别

    从操作系统的角度看,进程和线程有什么区别?

    1. 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程造成影响。
    2. 线程没有独立的地址空间,线程只是进程所属进程的不同执行路径

    从 JVM 的运行时数据分区的角度,进程和线程有什么关系?

    JVM 运行时数据分区如图所示:

    image

    由图可以看出如下几点:

    1. 一个进程中的多个线程共享堆区和方法区
    2. 每个线程拥有自己的程序计数器,虚拟机栈,本地方法栈

    Activity 启动模式

    启动模式横向对比

    四中启动模式的概念就不详述了,这里只是对关键点做出横向对比,如图所示

    Activity 启动模式比较

    一些鲜为人知的 Intent Flag

    下面是一些 Intent Flag 及介绍,如图所示

    鲜为人知但常用的启动模式 flag

    Activity 启动流程源码分析

    概念

    首先介绍一下 Activity 启动流程涉及到的核心类

    ActivityThread

    ActivityThread 的作用是管理应用程序主线程的相关流程,例如管理与处理 activity manager 发送的请求,这些请求可以来自 Activity 或 BroadCast

    Instrumentation

    它用来监控应用程序与系统之间的交互

    ActivityManagerService (AMS)

    AMS(ActivityManagerService)是贯穿Android系统组件的核心服务,负责了系统中四大组件的启动、切换、调度以及应用进程管理和调度工作

    执行流程

    Step 1. Launcher.startActivitySafely

    /**
     * Launches the intent referred by the clicked shortcut.
     *
     * @param v The view representing the clicked shortcut.
     */
    public void onClick(View v) {
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
            return;
        }
        if (!mWorkspace.isFinishedSwitchingState()) { // Launcher 程序在监听点击事件时会判断页面是否正在滑动,如果在滑动则不响应点击应用程序 icon 的事件
            return;
        }
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) { // 处理点击应用程序 icon 的场景
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
    
            boolean success = startActivitySafely(v, intent, tag); // startActivitySafely 这里主要会判断待启动的 Activity 是否存在,若不存在则会报 ActivityNotFound Exception 并捕获
    
            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {  // 处理点击文件夹的场景
            ···
        } else if (v == mAllAppsButton) {
            ···
        }
    }
    

    Launcher 程序在监听点击事件时会判断页面是否正在滑动,如果在滑动则不响应点击应用程序 icon 的事件

    Step 2. Activity.startActivity()

    由于 Launcher 继承于 Activity, 因此代码执行流程到了 startActivity()

    
    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks {
     
        ......
     
        @Override
        public void startActivity(Intent intent) {
            startActivityForResult(intent, -1);
        }
     
        ......
    

    startActivity 最终是调用 startActivityForResult, 传入的参数为 -1 代表这次启动 Activity 不需要获取结果

    Step 3. Activity.startActivityForResult()

    
    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks {
     
        ......
     
        public void startActivityForResult(Intent intent, int requestCode) {
            if (mParent == null) {
                Instrumentation.ActivityResult ar =
                    mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode);
                ......
            } else {
                ......
            }
     
     
        ......
    

    这里有两个有趣的场景:

    1. startActivityForResult() 不能用于启动 singleTask 为启动模式的 Activity, 否则会从 onActivityForResult() 中收到 cancel 事件
    2. 假设有这样一个场景,Activity A 启动 Activity B, 如果在 A 的 onCreate() / onResume() 中调用 startActivityForResult(), 并且传入的 requestCode >= 1, 那么 A Activity 所挂钩的 Window 将暂时不可见,这种不可见状态直到收到 B Activity 的回调结果。目的是防止 Activity 跳转时 UI 闪烁

    Step 4. ActivityStack.startActivityUncheckedLocked

    final int startActivityUncheckedLocked(ActivityRecord r,
            ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
            int grantedMode, boolean onlyIfNeeded, boolean doResume) {
            final Intent intent = r.intent;
            final int callingUid = r.launchedFromUid;
     
            int launchFlags = intent.getFlags();
     
            // We'll invoke onUserLeaving before onPause only if the launching
            // activity did not explicitly state that this is an automated launch.
            mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
            
            ......
     
            ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
                != 0 ? r : null;
     
            // If the onlyIfNeeded flag is set, then we can do this if the activity
            // being launched is the same as the one making the call...  or, as
            // a special case, if we do not know the caller then we count the
            // current top activity as the caller.
            if (onlyIfNeeded) {
                ......
            }
     
            if (sourceRecord == null) {
                ......
            } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                ......
            } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
                ......
            }
     
            if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
                ......
            }
     
            boolean addingToTask = false;
            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 (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)) {
                        ......
                    }
                }
     
            } else {
                ......
            }
     
            boolean newTask = false;
     
            // Should this be considered a new task?
            if (r.resultTo == null && !addingToTask
                && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
                    // todo: should do better management of integers.
                    mService.mCurTask++;
                    if (mService.mCurTask <= 0) {
                        mService.mCurTask = 1;
                    }
                    r.task = new TaskRecord(mService.mCurTask, r.info, intent,
                        (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
                    ......
                    newTask = true;
                    if (mMainStack) {
                        mService.addRecentTaskLocked(r.task);
                    }
     
            } else if (sourceRecord != null) {
                ......
            } else {
                ......
            }
     
            ......
     
            startActivityLocked(r, newTask, doResume);
            return START_SUCCESS;
        }
    

    由于我们是总 launcher 启动 Activity, 因此 当前的前台任务栈栈顶是 launcher Activity, 因此需要创建一个新的 ActivityStack, 和 ActivityRecord 对象,将 ActivityRecord 放入其中,最终这个新创建的 ActivityStack 被保存到 AMS 中

    Step 5. Activity.resumeTopActivityLocked

       // If the top activity is the resumed one, nothing to do.
        if (mResumedActivity == next && next.state == ActivityState.RESUMED) {
        ......
        }
     
        // If we are sleeping, and there is no resumed activity, and the top
        // activity is paused, well that is the state we want.
        if ((mService.mSleeping || mService.mShuttingDown)
        && mLastPausedActivity == next && next.state == ActivityState.PAUSED) {
        ......
        }
    

    现在我们已经准备好了新的 ActivityStack 和新的 ActivityRecord, 然后我们就需要处理 launcher Activity 的生命周期,将其置为 pause 状态。根据源码,这里首先会检查被启动的 Activity 是否在栈顶,是否就是启动者,如果是的话,就什么都不做,如果都不满足的话,就需要将目前栈顶的 Activity 置为 Pause 状态,在这之前,首先要检查是否有正在 Pausing 的 Activity, 如果有的话launcher Activity 需要被挂起,

    总结

    由于 Activity 启动流程较复杂,剩余流程将不再详述,总结如下图所示:


    image

    相关文章

      网友评论

        本文标题:Activity 启动流程学习总结(附源码)

        本文链接:https://www.haomeiwen.com/subject/rmtdcctx.html