Activity生命周期之冷启动

前言

写 Activity 系列的文章,是一件非常具有挑战性的事情,因为 Activity 与窗口、动画,紧密关联。

本文从最基础的方面开始分析,也就是生命周期,但是读者也不用担心自己不懂窗口、动画,等等。因为我会在分析的时候,帮你避开这些,让你能快速了解生命周期是怎么运转的。然后,当你掌握了窗口、动画,等等知识,不妨再回头研究本文,或许你会有更深的理解。

那么选择什么 Android 版本来分析代码呢?现在市面上最新款的手机,使用的是 Android 14 ,例如 Xiaomi14。那么,我也选择 Android 14,也就是 Android U,来分析 Activity 的生命周期,窗口显示,窗口动画,等等。后面的文章就不再赘述分析的是哪个 Android 版本了。

选择分析案例

为了完整的分析 Activity 生命周期流程,我选择以桌面冷启动 App 为例,来分析 Activity 的启动,因为这个过程中会为 App 创建进程。另外,也涉及桌面的远程动画,也是为以后分析动画做准备。

桌面冷启动 App

我粗略地看过 Launcher 启动 App 的源码,其实就是调用 Context#startActivity() 来启动 Activity,最终实现的函数如下

java 复制代码
// ContextImpl.java

    public void startActivity(Intent intent, Bundle options) {
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
java 复制代码
// Instrumentation.java

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        
        // ...
        
        try {
            // ...
            
            // 通过 ATMS 通知服务端启动
            int result = ActivityTaskManager.getService().startActivity(whoThread,
                    who.getOpPackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                    target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
            
            // ...
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

很简单,最终是通过 ActivityTaskManagerService(简称 ATMS) 来让服务端启动 Activity。至于 Launcher 传入了什么参数,在后面的分析中,如果有需要,我会指出来,因此这里就不详细描述传入了哪些数据。

由于在服务端,启动 Activity 并不是一次性完成,我分为了三个启动阶段来分析,旨在让读者更清晰地掌握 Activity 的启动过程。

第一阶段启动

第一阶段启动,由 App 端发起,并且由服务端 ATMS 处理。

ATMS

java 复制代码
// ActivityTaskManagerService.java

    public final int startActivity(IApplicationThread caller, String callingPackage,
            String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
            Bundle bOptions) {
        // 增加了最后一个参数 userId
        return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }
    
    public int startActivityAsUser(IApplicationThread caller, String callingPackage,
            String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
            Bundle bOptions, int userId) {
        // 增加了最后一个参数 validateIncomingUser,值为 true
        return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, userId,
                true /*validateIncomingUser*/);
    }
    
    private int startActivityAsUser(IApplicationThread caller, String callingPackage,
            @Nullable String callingFeatureId, Intent intent, String resolvedType,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {

        // app 端传过来的参数是 Bundle 类型,并且是序列化的,因此无法打印
        // 在服务端,需要把它转换为 ActivityOptions 才能打印 app 端传入的数据
        // 这里更进一步,把 ActivityOptions 包装成 SafeActivityOptions,为的是检测调用方的权限
        final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);

        // ...
        
        // 把启动参数包装成 ActivityStart#mRequest ,并通过 ActivityStarter 来启动 activity
        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller) // 指向 launcher
                .setCallingPackage(callingPackage) // com.android.launcher
                .setCallingFeatureId(callingFeatureId) // null
                .setResolvedType(resolvedType) // 这是 mime type,目前是 null
                .setResultTo(resultTo) // 指向 launcher
                .setResultWho(resultWho) // null 
                // 注意,这个请求码是 -1,也就是说 launcher 不需要知道启动结果
                .setRequestCode(requestCode) 
                .setStartFlags(startFlags) // 0
                .setProfilerInfo(profilerInfo) // null
                .setActivityOptions(opts) // 这个是启动 Activity 传入的数据,不为 null
                .setUserId(userId)
                .execute();

    }    

ATMS 把参数保存到 ActivityStart#mRequest 中,并通过 ActivityStarter 来执行启动流程。

ActivityStarter

java 复制代码
// ActivityStarter.java

    /**
     * Resolve necessary information according the request parameters provided earlier, and execute
     * the request which begin the journey of starting an activity.
     * @return The starter result.
     */
    int execute() {
        try {
            // ...
            
            // 1.解析 Activity 信息,并保存到 mRequest.activityInfo
            if (mRequest.activityInfo == null) {
                mRequest.resolveActivity(mSupervisor);
            }

            // ...

            int res;
            synchronized (mService.mGlobalLock) {
                // ...

                try {
                    // 2.执行activity启动请求
                    res = executeRequest(mRequest);
                } finally {
                    mRequest.logMessage.append(" result code=").append(res);
                    // 3. log 中输出启动的数据,以及启动结果
                    // 这个log 就是著名的 START u0 XXX
                    Slog.i(TAG, mRequest.logMessage.toString());
                    mRequest.logMessage.setLength(0);
                }

                // ...

                return getExternalResult(res);
            }
        } finally {
            onExecutionComplete();
        }
    }

看注释,这一步主要就是解析一些必要信息,例如 activity 信息,然后执行请求,最后输出一条 START u0 的log。

executeRequest()

java 复制代码
// ActivityStarter.java

/**
 * Executing activity start request and starts the journey of starting an activity. Here
 * begins with performing several preliminary checks. The normally activity launch flow will
 * go through {@link #startActivityUnchecked} to {@link #startActivityInner}.
 */
private int executeRequest(Request request) {

    // 1. 做一些启动前检查,以及解析一些参数

    // ...

    // 2.构造 START u0 的log
    if (err == ActivityManager.START_SUCCESS) {
        request.logMessage.append("START u").append(userId).append(" {")
                .append(intent.toShortString(true, true, true, false))
                .append("} with ").append(launchModeToString(launchMode))
                .append(" from uid ").append(callingUid);
        if (callingUid != realCallingUid
                && realCallingUid != Request.DEFAULT_REAL_CALLING_UID) {
            request.logMessage.append(" (realCallingUid=").append(realCallingUid).append(")");
        }
    }   

    ActivityRecord resultRecord = null;
    if (resultTo != null) {
        sourceRecord = ActivityRecord.isInAnyTask(resultTo);
        if (sourceRecord != null) {
            // requestCode 此时值为 -1,因此 resultRecord 最终值为 null
            // 从这里可以看出,actvity 要想接收启动另外一个的结果,requestCode 必须大于0,并且自己没有正在 finish
            if (requestCode >= 0 && !sourceRecord.finishing) {
                resultRecord = sourceRecord;
            }
        }
    }    

    // ...


    // 3. 创建 ActivityRecord
    final ActivityRecord r = new ActivityRecord.Builder(mService)
            .setCaller(callerApp) // 指向 launcher 进程
            .setLaunchedFromPid(callingPid)
            .setLaunchedFromUid(callingUid)
            .setLaunchedFromPackage(callingPackage) // com.android.launcher
            .setLaunchedFromFeature(callingFeatureId) // null
            .setIntent(intent)
            .setResolvedType(resolvedType) // null
            .setActivityInfo(aInfo)
            .setConfiguration(mService.getGlobalConfiguration()) // 当前配置
            .setResultTo(resultRecord) // null
            .setResultWho(resultWho) // null
            .setRequestCode(requestCode) // -1
            .setComponentSpecified(request.componentSpecified) // false
            .setRootVoiceInteraction(voiceSession != null) // false
            .setActivityOptions(checkedOptions) // 不为空
            .setSourceRecord(sourceRecord) // launcher
            .build();

    // mLastStartActivityRecord 保存最近一次启动的activity
    mLastStartActivityRecord = r;

    // ...

    // 4. 以 ActivityRecord 为参数,继续执行启动
    mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
            request.voiceInteractor, startFlags, checkedOptions,
            inTask, inTaskFragment, balCode, intentGrants, realCallingUid);

    // ...

    return mLastStartActivityResult;
}

正如注释所说,这一步做了启动前的检查,我省略了这段代码,因为对于本文来说,并不重要。一旦检查通过,就创建 ActivityRecord,然后利用 ActivityRecord 来执行下一步的启动。

在这一步中,我还展示了 START u0 的 log 是如何构造的,这里展示下从桌面冷启动 App 的 log

ini 复制代码
ActivityTaskManager: START u0 
{act=com.android.intent.MAIN, 
cat=[com.android.category.LAUNCHER], 
flg=0x10200000, cmp=com.example.helloworld/.MainActivity ....} ... 
result code = 0

大括号内是 Intent 的信息,flg 的值中包含 FLAG_ACTIVITY_NEW_TASK,所以从桌面冷启动 App,是需要新建一个 Task 的。

startActivityUnchecked()

java 复制代码
// ActivityStarter.java

/**
 * Start an activity while most of preliminary checks has been done and caller has been
 * confirmed that holds necessary permissions to do so.
 * Here also ensures that the starting activity is removed if the start wasn't successful.
 */
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        int startFlags, ActivityOptions options, Task inTask,
        TaskFragment inTaskFragment, @BalCode int balCode,
        NeededUriGrants intentGrants, int realCallingUid) {
    int result = START_CANCELED;
    final Task startedActivityRootTask;

    final TransitionController transitionController = r.mTransitionController;
    // 1.创建 OPEN transition,并使 transition 进入收集状态
    Transition newTransition = transitionController.isShellTransitionsEnabled()
            ? transitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
    
    // 从 launcher 启动 app 是,会携带一个 RemoteTranstion 数据,与动画相关
    RemoteTransition remoteTransition = r.takeRemoteTransition();
    
    try {
        mService.deferWindowLayout();
        
        // 1.1 transition 收集正在启动的 ActivityRecord
        transitionController.collect(r);
        
        try {
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
            
            // 2. 继续启动 activity
            result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, options, inTask, inTaskFragment, balCode,
                    intentGrants, realCallingUid);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
            
            // 3.处理启动结果
            // 主要做两件事,一是失败后的清理工作,二是成功后的请求启动transition
            startedActivityRootTask = handleStartResult(r, options, result, newTransition,
                    remoteTransition);
        }
    } finally {
        mService.continueWindowLayout();
    }

    // 注意这段代码中关于 START_TASK_TO_FRONT 和  START_DELIVERED_TO_TOP 结果的处理,与PIP等功能有关
    postStartActivityProcessing(r, result, startedActivityRootTask);
    
    return result;
}
  1. 创建一个 OPEN Transition ,然后收集 target ActivityRecord。
  2. 执行下一步启动。
  3. 处理启动的结果。如果启动失败,会做一些清理工作。如果启动成功时,会请求启动 transition 。

第1步和第3步的重点在于动画,本文可以不用关心,我这里列举出来,是为了方便以后分析动画。

约定: target Activity 或者 target ActivityRecord 都代表要启动的 Activity。

startActivityInner()

java 复制代码
// ActivityStarter.java

/**
 * Start an activity and determine if the activity should be adding to the top of an existing
 * task or delivered new intent to an existing activity. Also manipulating the activity task
 * onto requested or valid root-task/display.
 *
 * Note: This method should only be called from {@link #startActivityUnchecked}.
 */
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        int startFlags, ActivityOptions options, Task inTask,
        TaskFragment inTaskFragment, @BalCode int balCode,
        NeededUriGrants intentGrants, int realCallingUid) {
    // 保存一些初始状态,然后解析一些参数,例如 mDoResume 解析为 true
    setInitialState(r, options, inTask, inTaskFragment, startFlags, sourceRecord,
            voiceSession, voiceInteractor, balCode, realCallingUid);
    
    // 调整 mLaunchFlags ,它来自于 Intent#mFlags
    computeLaunchingTaskFlags();
    // mIntent 保存调整后的 mLaunchFlags
    mIntent.setFlags(mLaunchFlags);
    
    // ...
    
    // Get top task at beginning because the order may be changed when reusing existing task.
    final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
    final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;
    
    // 找到一个可重用的Task,目前返回的是 null
    final Task reusedTask = getReusableTask();
    
    // ...
    
    // Compute if there is an existing task that should be used for.
    // 获取 activity 应该加入的 task,目前获取为 null
    final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
    
    // 获取不到 activity 应该加入的 task,因此 newTask 为 true
    final boolean newTask = targetTask == null;
    
    // mTargetTask 保存 activity 应该加入的 task
    // 由于目前没有获取到,因此 mTargetTask 为 null
    mTargetTask = targetTask;
    
    // 调整 mLaunchParams,然后从 mLauchParams 中更新 mPreferredTaskDisplayArea,mPreferredWindowingMode
    // mPreferredTaskDisplayArea 值为 DefaultTaskDisplayArea
    // mPreferredWindowingMode 的值为 WINDOWING_MODE_UNDEFINED
    computeLaunchParams(r, sourceRecord, targetTask);
    
    // ...
    
    // null
    final ActivityRecord targetTaskTop = newTask
            ? null : targetTask.getTopNonFinishingActivity();
            
    if (targetTaskTop != null) {
        // ...
    } else {
        // 表示正在把 activity 加入 task
        mAddingToTask = true;
    }
    
    // 如果当前焦点 root task 的栈顶的 activity 就是要启动的 activity,那么检测是否调用 onNewIntent
    final Task topRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
    if (topRootTask != null) {
        startResult = deliverToCurrentTopIfNeeded(topRootTask, intentGrants);
        if (startResult != START_SUCCESS) {
            return startResult;
        }
    }
    
    if (mTargetRootTask == null) {
        // 1. 创建 root task ,并且作为 top child 保存到 TDA 下
        mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
                mOptions);
    }
    
    if (newTask) {
        // taskToAffiliate 为 null
        final Task taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
                ? mSourceRecord.getTask() : null;
        // 2. root task 作为 leaf task 保存 ActivityRecord,并且是以 top child 的方式保存
        setNewTask(taskToAffiliate);
    } else if (mAddingToTask) {
        // ...
    }
    
    if (!mAvoidMoveToFront && mDoResume) {
        // 在创建 root task 过程中,已经把 root task 作为 top child 保存到 TDA 下
        mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
        if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
                && !dreamStopping) {
            // ...
        }
    }
    
    // ...
    final Task startedTask = mStartActivity.getTask();
    if (newTask) {
        EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId,
                startedTask.getRootTaskId(), startedTask.getDisplayId());
    }
    
    // event log 会输出 wm_create_activity [XXX],括号里展示了启动 activity 的 Intent 的各种信息,
    // 例如 ComponentName , action, flags 等等。
    mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask);
    
    mStartActivity.getTaskFragment().clearLastPausedActivity();
    mRootWindowContainer.startPowerModeLaunchIfNeeded(
            false /* forceSend */, mStartActivity);
    
    // isTaskSwitch 为 true
    final boolean isTaskSwitch = startedTask != prevTopTask;
    // 3. 创建启动窗口
    mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
            mOptions, sourceRecord);
    
    if (mDoResume) {
        final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked();
        if (!mTargetRootTask.isTopActivityFocusable()
                || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
                && mStartActivity != topTaskActivity)) {
            // ...
        } else {
            // mTargetRootTask 目前已经是 top focused task
            if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
                    && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
                // ... 
            }
            // 4. ActivityRecord 的窗口层级已经构建成功,现在由 RWC 来执行下一步启动
            // mTransientLaunch 来自于 mOptions 的解析某一个数据,这里值为 false
            mRootWindowContainer.resumeFocusedTasksTopActivities(
                    mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
        }
    }
    
    mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);
    // Update the recent tasks list immediately when the activity starts
    // 保存到 RecentTasks#mTasks 中
    mSupervisor.mRecentTasks.add(startedTask);
    
    // ...
    return START_SUCCESS;
}

这一步,涉及到窗口层级树的知识,这里需要先简单介绍下

窗口层级树大致如下

graph TD RootWindowContainer --> DisplayContent DisplayContent --> DislayArea1 DisplayContent --> DislayArea DisplayContent --> DislayArea2 DislayArea --> TaskDisplayArea TaskDisplayArea --> Task Task --> ActivityRecor ActivityRecord --> WindowState

其中,RootWindowContainer 是层级树的根,DisplayContent 代表一个屏幕,DisplayArea 管理着屏幕上某一部分窗口,TaskDisplayArea 是用来管理 Task,Task 用来保存代表 Activity 的 ActivityRecord,ActivityRecord 保存显示的窗口 WindowState。

有了这个窗口层级树的概念后,再来看这一步的代码。这一步中,有很多琐碎的逻辑,我不好一一分析,这里简单说明下大致的过程

  1. 创建 root Task,并以 top child 保存到 TaskDisplayArea 下。什么是 root Task ? 保存在 TaskDisplayArea 下的第一个 Task 就是 root Task。
  2. root Task 作为 leaf Task(叶子结点 Task),把 target ActivityRecord 作为 top child 进行保存。什么是 leaf Task ? 其实 Task 下面也可以保存子 Task,子 Task 下面如果没有 Task,那么子 Task 就是 leaf Task。
  3. 创建启动窗口。就是 App 开发经常说的 splash screen。
  4. 窗口层级构建完成后,通过层级树的根 RootWindowContainer 来执行下一步的 Activity 启动。

经过这一步之后,窗口层级树如下

graph TD RootWindowContainer --> DisplayContent DisplayContent --> DislayArea1 DisplayContent --> DislayArea DisplayContent --> DislayArea2 DislayArea --> TaskDisplayArea TaskDisplayArea --> Task Task --> ActivityRecord ActivityRecord --> WindowState TaskDisplayArea --> targetTask targetTask --> taretActivityRecord

其中,targetTask 和 targetActvityRecord 的意思,就不言而喻了吧。这里读者肯定很好奇,targetActvityRecord 为何没有 WindowState 呢?因为现在还没有给 ActivityRecord 添加实际显示的窗口。ActivityRecord 其实不是代表 Activity 的窗口,它是管理 Activity 的所有窗口的容器。

1.我这里只是简单介绍窗口层级树,对窗口有兴趣的读者,可以自行 Google 相关的文章进行学习。

2.可以通过 adb shell dumpsys activity a 查看窗口层级树,你会有一个比较全面的认识。

小结

到此,ActivityStarter 的工作已经完成,回顾下它的作用是什么呢?其实就是构建窗口层级,然后交给 RootWindowContainer 去启动 Activity。

RootWindowContainer

java 复制代码
// RootWindowContainer.java

boolean resumeFocusedTasksTopActivities(
        Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
        boolean deferPause) {
    if (!mTaskSupervisor.readyToResume()) {
        return false;
    }
    boolean result = false;
    
    // targetRootTask 是要 target activity 的 root task
    // targetRootTask 目前已经是 top root task
    if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
            || getTopDisplayFocusedRootTask() == targetRootTask)) {
        // 有 toot task 启动它的 top activity
        // 参数 deferPause 为 false
        result = targetRootTask.resumeTopActivityUncheckedLocked(target, targetOptions,
                deferPause);
    }

    // 处理 activity 启动失败的情况
    // 例如,从桌面启动 activity 失败,至少系统得再把桌面拉起来
    for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
        // ...
    }
    return result;
}

RootWindowContainer 交给 target activity 的 root task 来启动它的 top activity,这个 top activity 就是 target activity.

root Task#resumeTopActivityUncheckedLocked()

java 复制代码
// Task.java


// prev 就是 target actvity
// options 不为 null
// deferPause 为 false
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options,
        boolean deferPause) {

    if (mInResumeTopActivity) {
        // Don't even start recursing.
        return false;
    }

    boolean someActivityResumed = false;
    try {
        // Protect against recursion.
        mInResumeTopActivity = true;

        if (isLeafTask()) { // leaf Task
            if (isFocusableAndVisible()) { // top activity 可以获焦,并且 task 可见
                // 由 leaf Task 来启动它的 top activity
                // 参数 prev 此时是 target activity, 参数deferPause为false
                someActivityResumed = resumeTopActivityInnerLocked(prev, options, deferPause);
            }
        } else {
            // ... 这里处理不是 leaf task 的情况,不外乎就是遍历 children,然后递归调用当前方法
        }

        final ActivityRecord next = topRunningActivity(true /* focusableOnly */);
        if (next == null || !next.canTurnScreenOn()) {
            checkReadyForSleep();
        }
    } finally {
        mInResumeTopActivity = false;
    }
    return someActivityResumed;
}

root Task 把启动任务交给 leaf Task,根据前面的分析可知,target activity 的 root Task 就是 leaf Task。

leaf Task#resumeTopActivityInnerLocked()

java 复制代码
// Task.java

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options,
        boolean deferPause) {
    if (!mAtmService.isBooting() && !mAtmService.isBooted()) {
        // Not ready yet!
        return false;
    }
    
    //获取 Task 的 top-focusable-non-finishing-activty
    final ActivityRecord topActivity = topRunningActivity(true /* focusableOnly */);

    if (topActivity == null) {
        // ...
    }

    final boolean[] resumed = new boolean[1];

    final TaskFragment topFragment = topActivity.getTaskFragment();
    // 由 top activity 的 TaskFragment 来启动 top activity
    resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause);
    
    forAllLeafTaskFragments(f -> {
        // ...
    }, true);
    return resumed[0];
}

这里又有一个 TaskFragment 概念,其实 Task 是继承自 TaskFragment,Task 下面可以保存 TaskFragment,当然也可以保存 Task(因为 Task 是 TaskFragment 子类)。

对于目前分析的情况,Task 下面是不存在 TaskFragment 的。因此,这里 leaf Task 把启动任务交给 top activity 的 TaskFragment,这个 TaskFragment 其实就是 leaf Task,并且也是 root Task。

TaskFragment#resumeTopActivity()

java 复制代码
// TaskFragment.java

// prev 此时就是 target activity
// deferPause 为 false
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
        boolean deferPause) {
    // 获取 top-focusable-activity,其实就是 target activity
    ActivityRecord next = topRunningActivity(true /* focusableOnly */);
    
    if (next == null || !next.canResumeByCompat()) {
        return false;
    }

    next.delayedResume = false;

    // 如果当前有 Actvity 正在暂停,那么什么也不做
    // If we are currently pausing an activity, then don't do anything until that is done.
    final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
    if (!allPausedComplete) {
        ProtoLog.v(WM_DEBUG_STATES,
                "resumeTopActivity: Skip resume: some activity pausing.");
        return false;
    }

    final TaskDisplayArea taskDisplayArea = getDisplayArea();

    // Task 才刚刚创建,mResumedActivity 和 mLastPausedActivity 都为 null
    if (mResumedActivity == next && next.isState(RESUMED)
            && taskDisplayArea.allResumedActivitiesComplete()) {
        // ....
        return false;
    }
    if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
        // ...
        return false;
    }

    // ...

    // 1.暂停后台 Task
    // 如果有后台 Task 的 resumed activity 正在暂停,那么返回 false
    // 此时 deferPause 为 false
    boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);

    if (mResumedActivity != null) {
        // ...
    }

    if (pausing) {
        ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to"
                + " start pausing");
                
        if (next.attachedToProcess()) {
            // ...
        } else if (!next.isProcessRunning()) {
            // Since the start-process is asynchronous, if we already know the process of next
            // activity isn't running, we can start the process earlier to save the time to wait
            // for the current activity to be paused.
            final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
            // 2. 如果要启动的 acivity 的进程还没有启动,那么先创建进程
            mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
                    isTop ? HostingRecord.HOSTING_TYPE_NEXT_TOP_ACTIVITY
                            : HostingRecord.HOSTING_TYPE_NEXT_ACTIVITY);
        }
        
        // 注意,这里直接返回了
        // 此时的情况是 activity 的进程已经启动
        return true;
    } else if (mResumedActivity == next && next.isState(RESUMED)
            && taskDisplayArea.allResumedActivitiesComplete()) {
        // ...
    }
    // ...
    return true;
}

target activity 的 TaskFragment ,先暂停后台 Task ,然后由于 target activity 的进程还没有启动,因此需要先创建进程。

暂停后台Task

根据前面所了解的窗口层级树可知,TaskDisplayArea 管理所有的 Task,因此暂停后台 Task 的任务肯定是交给它。

java 复制代码
// TaskDisplayArea.java

    // 参数 resuming 此时就是 target activity
    boolean pauseBackTasks(ActivityRecord resuming) {
        final int[] someActivityPaused = {0};
        // 从上到下,遍历 TaskDisplayArea 下的所有 leaf Task
        forAllLeafTasks(leafTask -> {
            // 目前我们分析的情况,leaf Task 是不包含 TaskFragment
            if (!leafTask.isLeafTaskFragment()) {
                // ...
            }

            // 从上到下,遍历 leaf Task 下的 leaf TaskFragment
            // 注意,如果 Task 下没有 TaskFragment,那么 Task 会作为 leaf TaskFragment 来回调
            leafTask.forAllLeafTaskFragments((taskFrag) -> {
                final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
                
                // 后台 Task 必须要有 resumed activity,并且不能启动 target activity
                if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
                    if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
                        someActivityPaused[0]++;
                    }
                }
            }, true /* traverseTopToBottom */);
        }, true /* traverseTopToBottom */);
        return someActivityPaused[0] > 0;
    }

TaskDisplayArea 遍历所有 leaf Task,然后 leaf Task 遍历所有 leaf TaskFragment,最后又 leaf TaskFragment 来暂停 resumed activity。

根据前面的分析可知,leaf Task 下是不存在 TaskFragment 的,因此这里其实就是有 leaf Task 暂停 resumed activity。此时的需要暂停的后台 Task 就是桌面的 Task,因为只有它有 resumed activity。

java 复制代码
// TaskFragment.java

final boolean startPausing(boolean uiSleeping, ActivityRecord resuming, String reason) {
    return startPausing(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
}

// 参数 resuming 此时是 target activity
boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
        String reason) {
    if (!hasDirectChildActivities()) {
        return false;
    }

    ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
            mResumedActivity);

    if (mPausingActivity != null) {
        // ..
    }

    // mResumedActivity 就是桌面正在显示的 Activity
    ActivityRecord prev = mResumedActivity;
    
    if (prev == null) {
        // ...
        return false;
    }
    if (prev == resuming) {
        Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
        return false;
    }

    ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);

    // 保存正在暂停的 Activity
    mPausingActivity = prev;
    mLastPausedActivity = prev;

    // TODO: 这个 no history 有什么用
    if (!prev.finishing && prev.isNoHistory()
            && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
        mTaskSupervisor.mNoHistoryActivities.add(prev);
    }

    // 1. 正在暂停的 Activity,状态切换到 PAUSING
    // 并且还会通知 TaskFragment 状态改变,TaskFragment 会把它的 mResumedActivity 更新为 null
    prev.setState(PAUSING, "startPausingLocked");

    prev.getTask().touchActiveTime();
    mAtmService.updateCpuStats();

    // pauseImmediately 表示在通知 app 端去暂停 activity 后,服务端是否立即完成暂停任务
    // 如果 pauseImmediately 为 true,那么要启动的 Activity 不会等待后台 activity 暂停完成
    // 就可以直接启动,这个对于热启动或者温启动非常有用,因为可以加快 Activity 启动速度
    // 但是,很显然是有弊端的,后台 Activity 还没有调用 onPaused() 就启动新 Activity,
    // 可能会导致数据丢失等等的异常情况
    boolean pauseImmediately = false;
    boolean shouldAutoPip = false;
    if (resuming != null) {
        final boolean resumingOccludesParent = resuming.occludesParent();
        // false
        final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
                "shouldAutoPipWhilePausing", userLeaving);
        if (userLeaving && resumingOccludesParent && lastResumedCanPip
                && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
            // ...
        } else if (!lastResumedCanPip) {
            // 这是 AndroidManifest 中 Activity 的属性 resumeWhilePausing
            pauseImmediately = (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
        } else {
            // ...
        }
    }


    if (prev.attachedToProcess()) {
        if (shouldAutoPip) {
            // ...
        } else {
            // 2.通知app端去暂停activity
            // 参数 pauseImmediately 一般为 false
            schedulePauseActivity(prev, userLeaving, pauseImmediately,
                    false /* autoEnteringPip */, reason);
        }
    } else {
        // ...
    }

    // ...

    if (mPausingActivity != null) {
        if (!uiSleeping) {
            prev.pauseKeyDispatchingLocked();
        } else {
            ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
        }
        
        // pauseImmediately 一般为 false
        if (pauseImmediately) {
            // ...
        } else {
            // 发送一个暂停超时消息
            prev.schedulePauseTimeout();
            
            // All activities will be stopped when sleeping, don't need to wait for pause.
            if (!uiSleeping) {
                // 3. 更新 Transition 的 ready 状态为 false,因为要等待后台 activity 暂停完成。
                // Unset readiness since we now need to wait until this pause is complete.
                mTransitionController.setReady(this, false /* ready */);
            }
            return true;
        }
    } else {
        // ...
    }
}

来看下 leaf TaskFragment 是如何完成暂停后台 Task 的任务

  1. 用 mPausingActivity 保存正在暂停的 Activity,并且把状态切换到 PAUSING,这里还会清理掉 mResumingActivity。
  2. 通知 app 端去暂停 Activity。
  3. 更新 Transition 的 ready 状态为 false,这属于动画部分,可不用关心。

下一篇文章,我会详细分析服务端如何通知 app 端去操作 Activity 生命周期,以及 app 端如何通知服务端生命周期处理完成。

小结

现在总结下 RootWindowContainer 在启动过程的作用

  1. 暂停后台 Task,也就是通知后台 Task 的 app 端去暂停 Activity,并且 app 端也会通知服务端 activity 暂停完成。而服务端处理 activity 暂停完成时,也会尝试去启动 target activity,我称之为第二阶段启动。
  2. 创建 target activity 的进程。当进程创建完成后,会运行 app,然后通知服务端去 attach app,服务端处理 attach app 时,也会去启动 target activity,我称之为第三个启动阶段。

要注意,以上所说的两个启动阶段,都是异步执行的。那么谁先执行,导致代码的执行流程也不同。但是,通常创建进程是非常缓慢的,慢到什么程度呢?慢到第二阶段结束时,第三阶段还没有开始。

第二阶段启动

如前面所说,第二阶段启动,开始于 app 端通知服务端 activity 暂停完成,服务端会调用如下函数

java 复制代码
// ActivityClientController.java

    public void activityPaused(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized (mGlobalLock) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityPaused");
            // 获取服务端对应的 ActivityRecord
            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
            if (r != null) {
                // 通知 ActvityRecord 暂停完成
                r.activityPaused(false);
            }
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
        Binder.restoreCallingIdentity(origId);
    }
java 复制代码
// ActivityRecord.java

    void activityPaused(boolean timeout) {
        ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", token,
                timeout);

        final TaskFragment taskFragment = getTaskFragment();
        if (taskFragment != null) {
            removePauseTimeout();

            final ActivityRecord pausingActivity = taskFragment.getPausingActivity();
            if (pausingActivity == this) {
                ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s %s", this,
                        (timeout ? "(due to timeout)" : " (pause complete)"));
                        
                mAtmService.deferWindowLayout();
                try {
                    // 1. activity 的 TaskFragment 完成 activity 暂停
                    // 第一个参数 resumeNext 为 true,表示需要 resume 下一个 activity
                    taskFragment.completePause(true /* resumeNext */, null /* resumingActivity */);
                } finally {
                    mAtmService.continueWindowLayout();
                }
                
                // 暂停完成,直接退出
                return;
            } else {
                // ...
            }
        }

        // ...
    }

可以看到,服务端是由已暂停 ActivityRecord 的 TaskFragment 来处理暂停完成的工作

java 复制代码
// TaskFragment.java

    void completePause(boolean resumeNext, ActivityRecord resuming) {
        ActivityRecord prev = mPausingActivity;
        ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);

        if (prev != null) {
            prev.setWillCloseOrEnterPip(false);
            
            // 当前状态是 PAUSING,所以这里为 false
            final boolean wasStopping = prev.isState(STOPPING);
            
            // 1. 更新 AcivityRecrod#mState 为 PAUSED
            prev.setState(PAUSED, "completePausedLocked");
            
            if (prev.finishing) {
                // ...
            } else if (prev.attachedToProcess()) {
                ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                                + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
                        prev.isVisibleRequested());
                        
                if (prev.deferRelaunchUntilPaused) {
                    // ...
                } else if (wasStopping) {
                    // ...
                } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
                    // ... 目前 prev.mVisibleRequested 还是 true
                }
            } else {
                // ...
            }
            
            if (prev != null) {
                prev.stopFreezingScreenLocked(true /*force*/);
            }
            
            // 现在已经进入 PAUSED 状态,因此清理 mPausingActivity
            mPausingActivity = null;
        }

        // 此时 resumeNext 为 true
        if (resumeNext) {
            final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
            if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
                // 2. resume top activity of top focused root task
                mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev,
                        null /* targetOptions */);
            } else {
                // ...
            }
        }

        if (prev != null) {
            prev.resumeKeyDispatchingLocked();
        }

        // 3.更新 activity 可见性
        mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);

        if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
                || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
            mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
            mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
        }
    }

TaskFragment 处理 activity 暂停完成

  1. 把正在暂停的 ActivityRecord 的 mState 更新为 PAUSED,并且清理 mPausingActivity。
  2. 通知 RootWindowContainer 再次去启动 top root task 的 top activity,其实就是启动 target activity。
  3. 更新 ActivityRecord 的可见性,这一步其实涉及到已暂停 Activity 的生命周期流程,已暂停 Activity 会在这里进入 stopping 流程。

RootWindowContainer 启动 target actiity

RootWindowContainer#resumeTopActivity() 这个函数,在前面已经分析过,最终是由 TaskFragment 来启动 target activity

java 复制代码
// TaskFragment.java

    // 参数 prev 此时代表已经完成暂停的 Activity
    // options 为 null
    // deferPause 为 false
    final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
        // 获取 TaskFragment 的 top-focusable-activity
        ActivityRecord next = topRunningActivity(true /* focusableOnly */);
        
        if (next == null || !next.canResumeByCompat()) {
            return false;
        }

        next.delayedResume = false;

        // 如果有 activity 正在暂停,那么什么也不做
        final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
        if (!allPausedComplete) {
            ProtoLog.v(WM_DEBUG_STATES,
                    "resumeTopActivity: Skip resume: some activity pausing.");
            return false;
        }

        final TaskDisplayArea taskDisplayArea = getDisplayArea();
        
        // 此时 mResumedActivity 为 null
        if (mResumedActivity == next && next.isState(RESUMED)
                && taskDisplayArea.allResumedActivitiesComplete()) {
            // ....
            return false;
        }

        // 此时 mLastPausedActivity 为 null
        if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
            // ...
            return false;
        }

        // ...

        // 暂停后台 Task
        // 此时已经没有需要暂停的后台 Task 了,因此这里返回 false
        boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
        
        // mResumedActivity 为 null
        if (mResumedActivity != null) {
            // ...
        }
        
       
        if (pausing) {
            // ...
        } 
        // mResumedActivity 为 null
        else if (mResumedActivity == next && next.isState(RESUMED)
                && taskDisplayArea.allResumedActivitiesComplete()) {
            //  ....
            return true;
        }

        // ...

        // next 代表 target activity
        // next.nowVisible 代表是 Acivity 的窗口是否可见,现在是不可见的
        if (prev != null && prev != next && next.nowVisible) {
            // ...
        }

        // ...

        boolean anim = true;
        final DisplayContent dc = taskDisplayArea.mDisplayContent;
        if (prev != null) {
            if (prev.finishing) {
                // ...
            } else {
                if (DEBUG_TRANSITION) {
                    Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
                }
                
                // shell transition 打开的情况下,下面的 AppTransition 功能无效
                if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
                    anim = false;
                    dc.prepareAppTransition(TRANSIT_NONE);
                } else {
                    dc.prepareAppTransition(TRANSIT_OPEN,
                            next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
                }
            }
        } else {
            // ...
        }

        if (anim) {
            // 这里虽然是针对 app transition 动画,但是有设置状态栏动画延迟
            next.applyOptionsAnimation();
        } else {
            next.abortAndClearOptionsAnimation();
        }

        mTaskSupervisor.mNoAnimActivities.clear();

        // 检测即将启动的 ActivityRecord 是否绑定到进程
        // 进程还在创建中呢!
        if (next.attachedToProcess()) {
            // ...
        } else {
            // Whoops, need to restart this activity!
            if (!next.hasBeenLaunched) {
                next.hasBeenLaunched = true;
            } else {
                if (SHOW_APP_STARTING_PREVIEW) {
                    next.showStartingWindow(false /* taskSwich */);
                }
                if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
            }
            
            ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);
            
            // 根据进程状态启动 activity
            mTaskSupervisor.startSpecificActivity(next, true, true);
        }

        return true;
    }

现在,后台 Task 已经暂停完成了,因此,一般来说,target ActivityRecord 此时还没有绑定进程,那么只能通过 AcivityTaskSupervisor 根据进程状态来启动 target activity

java 复制代码
// ActivityTaskSupervisor.java

    void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, r.info.applicationInfo.uid);

        boolean knownToBeDead = false;

        if (wpc != null && wpc.hasThread()) {
            try {
                // 如果已经完成了 attach app ,那么直接通知 app 端启动 target activity
                realStartActivityLocked(r, wpc, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }

            // ...
        } else if (r.intent.isSandboxActivity(mService.mContext)) {
            // ...
        }

        r.notifyUnknownVisibilityLaunchedForKeyguardTransition();

        final boolean isTop = andResume && r.isTopRunningActivity();
        // 走到这里,表示连 attach app 过程都没有完成
        // 那么直接走启动进程的流程
        mService.startProcessAsync(r, knownToBeDead, isTop,
                isTop ? HostingRecord.HOSTING_TYPE_TOP_ACTIVITY
                        : HostingRecord.HOSTING_TYPE_ACTIVITY);
    }

其实,到现在为止,target activity 的进程还在创建中,更不可能完成 attach app ,因此,这里最后还是直接走了创建进程的流程。 因为 target activity 的进程,此时已经在创建中,那么这里其实什么也没做。

更新 ActivityRecord 可见性

java 复制代码
// RootWindowContainer.java

// starting 为 null
// configChanges 为 0
// preserveWindows 为 false
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
        boolean preserveWindows) {
    ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
}

这里会从 RootWindowContainer 开始,从上到下遍历,找到 leaf Task ,然后由 EnsureActivitiesVisibleHelper 来处理 leaf Task 下的 ActivityRecord 可见性,如下

java 复制代码
// EnsureActivitiesVisibleHelper.java

    // starting 为 null, configChanges 为0,preserveWindows为false,notifyClients为true
    void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
            boolean notifyClients) {
        // ...

        ArrayList<TaskFragment> adjacentTaskFragments = null;
        for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) {
            final WindowContainer child = mTaskFragment.mChildren.get(i);
            final TaskFragment childTaskFragment = child.asTaskFragment();
            if (childTaskFragment != null
                    && childTaskFragment.getTopNonFinishingActivity() != null) {
                // ...
            } else if (child.asActivityRecord() != null) {
                 // 设置 ActivityRecord 可见性
                setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
            }
        }
    }

EnsureActivitiesVisibleHelper 也是从上到下遍历 leaf Task 的 children,由于我们分析的情况是 Task 下只存在 ActivityRecord,因此最终调用 setActivityVisibilityState() 来更新可见性

java 复制代码
// EnsureActivitiesVisibleHelper.java

    private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
            final boolean resumeTopActivity) {
        // ...

        if (reallyVisible) {
            // ...

            if (!r.attachedToProcess()) {
                // 1.更新可见
                makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges,
                        resumeTopActivity && isTop, r);
            } else if (r.isVisibleRequested()) {
                // ...
            } else {
                // ...
            }
            
            mConfigChanges |= r.configChangeFlags;
        } else {
            if (DEBUG_VISIBILITY) {
                Slog.v(TAG_VISIBILITY, "Make invisible? " + r
                        + " finishing=" + r.finishing + " state=" + r.getState()
                        + " containerShouldBeVisible=" + mContainerShouldBeVisible
                        + " behindFullyOccludedContainer=" + mBehindFullyOccludedContainer
                        + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
            }
            // 2.更新不可见
            r.makeInvisible();
        }

        // ...
    }

这里涉及到一些琐碎的窗口可见性计算,本文就不展示了。对于目前的情况来说,top Task 就是 target activity 的 Task,它的 top activity ,也就是 target activity,由于进程还在创建中,因此它会更新可见。而对于桌面的 Task,由于它现在是后台 Task,因此它的 top activity 要更新不可见。

我们这里只关心不可见性更新,只为它直接与生命周期相关

java 复制代码
// ActivityRecord.java

    void makeInvisible() {
        // 从这里可以看出,更新可见性其实就是更新 mVisibleRequested
        if (!mVisibleRequested) {
            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this);
            return;
        }
        
        if (DEBUG_VISIBILITY) {
            Slog.v(TAG_VISIBILITY, "Making invisible: " + this + ", state=" + getState());
        }
        
        try {
            // ...
            
            // 1.更新可见性为 false,其实就是更新 mVisibleRequested
            setVisibility(false);

            switch (getState()) {
                // ...
              
                case PAUSED:
                    // 2.已暂停的 activity 进入 stopping 流程
                    addToStopping(true /* scheduleIdle */,
                            canEnterPictureInPicture /* idleDelayed */, "makeInvisible");
                    break;

            }
        } 
    }

第一步是更新可见性,也就是更新 ActivityRecord#mVisibleRequested,这里就不分析。来看下第二步,已暂停的 Activity 进入 stopping 流程

java 复制代码
// ActivityRecord.java

    void addToStopping(boolean scheduleIdle, boolean idleDelayed, String reason) {

        // 1. 加入到 mTaskSupervisor.mStoppingActivities 中
        if (!mTaskSupervisor.mStoppingActivities.contains(this)) {
            EventLogTags.writeWmAddToStopping(mUserId, System.identityHashCode(this),
                    shortComponentName, reason);
            mTaskSupervisor.mStoppingActivities.add(this);
        }

        final Task rootTask = getRootTask();
        boolean forceIdle = mTaskSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE
                || (isRootOfTask() && rootTask.getChildCount() <= 1);
        
        // scheduleIdle 此时为 true
        if (scheduleIdle || forceIdle) {
            ProtoLog.v(WM_DEBUG_STATES,
                    "Scheduling idle now: forceIdle=%b immediate=%b", forceIdle, !idleDelayed);
            
            // idleDelayed 此时为 false
            if (!idleDelayed) {
                // 2.强制调用 idle 处理流程
                mTaskSupervisor.scheduleIdle();
            } else {
                // ...
            }
        } else {
            // ...
        }
    }

已暂停的 Activity 在真正进入到 stopping 流程前,首先需要加入到 stopping list 中,也就是 mTaskSupervisor.mStoppingActivities,然后强制调度一次 idle 流程。

idle 流程才决定了是否真正进入到 stopping 流程,但是有条件限制的,来看下实现,最终会调用到如下函数

java 复制代码
// ActivityRecord.java

    void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,
            boolean processPausingActivities, String reason) {
        boolean displaySwapping = false;
        ArrayList<ActivityRecord> readyToStopActivities = null;
        
        for (int i = 0; i < mStoppingActivities.size(); i++) {
            final ActivityRecord s = mStoppingActivities.get(i);
            
            // 判断 ActivityRecord 是否处于动画中
            // 目前,确实处于动画中
            final boolean animating = s.isInTransition()
                    && s.getTask() != null && !s.getTask().isForceHidden();
                    
            displaySwapping |= s.isDisplaySleepingAndSwapping();
            
            ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
                    + "finishing=%s", s, s.nowVisible, animating, s.finishing);
            
            // 如果 ActivityRecord 处于动画中,那就保存到 readyToStopActivities
            if ((!animating && !displaySwapping) || mService.mShuttingDown) {
                if (!processPausingActivities && s.isState(PAUSING)) {
                    // Defer processing pausing activities in this iteration and reschedule
                    // a delayed idle to reprocess it again
                    removeIdleTimeoutForActivity(launchedActivity);
                    scheduleIdleTimeout(launchedActivity);
                    continue;
                }

                ProtoLog.v(WM_DEBUG_STATES, "Ready to stop: %s", s);
                if (readyToStopActivities == null) {
                    readyToStopActivities = new ArrayList<>();
                }
                readyToStopActivities.add(s);

                mStoppingActivities.remove(i);
                i--;
            }
        }

        // ...

        final int numReadyStops = readyToStopActivities == null ? 0 : readyToStopActivities.size();
        for (int i = 0; i < numReadyStops; i++) {
            final ActivityRecord r = readyToStopActivities.get(i);
            if (r.isInHistory()) {
                if (r.finishing) {
                    // ...
                } else {
                    // 通知 app 端去 stop
                    r.stopIfPossible();
                }
            }
        }

        // ...
    }

看到了吧,只有已暂停的 ActivityRecord 不处于动画中,才能真正的进入 stopping 流程。但是,很可惜,在 ActivityRecord 更新可见性时,就被收集到 Transition 中,也就是处于动画中了。所以,已暂停的 Activity 此时不能进入 stopping 流程,也就无法通知 app 端去 stop activity。

小结

第二阶段启动,是由服务端收到 actvity paused 开始,主要就是把已暂停的 ActivityRecord 状态更新到 PAUSED,然后加入到 stopping list 中。

第三阶段启动

第三阶段的启动,是由创建 target activity 进程开始,但是进程创建的过程,本文就不分析了。当进程创建完毕后,会执行 App 的入口函数,也就是 ActivityThread#main()

java 复制代码
// ActivityThread.java

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    // ...
    Looper.prepareMainLooper();
    // ...
    // 创建 ActivityThread,并调用它的 attach() 
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    // ...
    
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

private void attach(boolean system, long startSeq) {
    // ..

    if (!system) {
        final IActivityManager mgr = ActivityManager.getService();
        try {
            // 1. attach app
            // mAppThread 是一个 binder 回调,在服务端代表 app 进程
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }

        // ...
    } else {
        // ...
    }

    // ...
}    

ActivityThreaad#main() 最终就是向服务端注册一个 Binder 回调,这就是所谓的 attach app。

attach app

现在来看下服务端的 attach app 流程

java 复制代码
// ActivityManagerService.java

public final void attachApplication(IApplicationThread thread, long startSeq) {
    if (thread == null) {
        throw new SecurityException("Invalid application interface");
    }
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);
        Binder.restoreCallingIdentity(origId);
    }
}

private void attachApplicationLocked(@NonNull IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    // ...

    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            // 进程创建的时候,会保存到 mPidsSelfLocked
            app = mPidsSelfLocked.get(pid);
        }
        if (app != null && (app.getStartUid() != callingUid || app.getStartSeq() != startSeq)) {
            // ...
        }
    } else {
        // ...
    }

    // ...

    EventLogTags.writeAmProcBound(app.userId, pid, app.processName);

    // ...

    try {
        // ...

        if (app.getIsolatedEntryPoint() != null) {
            // ....
        } else if (instr2 != null) {
            // ...
        } else {
            // 1. 通知 app 进行 bind application
            thread.bindApplication(processName, appInfo,
                    app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
                    providerList, null, profilerInfo, null, null, null, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.isPersistent(),
                    new Configuration(app.getWindowProcessController().getConfiguration()),
                    app.getCompat(), getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, autofillOptions, contentCaptureOptions,
                    app.getDisabledCompatChanges(), serializedSystemFontMap,
                    app.getStartElapsedTime(), app.getStartUptime());
        }
        
        // ...

        synchronized (mProcLock) {
            // 2. 完成 attach app
            // 其实就是进程实例 ProcessRecord 保存 app 端传入的回调 IApplicationThread 对象
            app.makeActive(thread, mProcessStats);
            checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
        }
        
        // ... 

        // mConstants.mEnableWaitForFinishAttachApplication 默认为 false
        // 如果为 true,表示需要等待 app 完成 bind application 之后,才执行 finish attach application 操作
        if (!mConstants.mEnableWaitForFinishAttachApplication) {
            // 3. 收尾 attach app
            // 四大组件中如果有等待进程启动的,此时就可以启动组件
            finishAttachApplicationInner(startSeq, callingUid, pid);
        } else {
            // ...
        }
    } catch (Exception e) {
        // ...
    }
}

服务端处理 attach app

  1. 通知 app 端 bind application。其实主要就是创建 Application 。
  2. 进程实例保存 app 端出入的回调,这就是真正的 attach app,也可以叫做 attach app to process。
  3. attach app 收尾。主要是针对四大组件的启动,例如,如果启动 activity 时,activity 连进程都没有创建,那么等进程创建完毕,在 attach app 时,再启动 activity。

finish attach app

java 复制代码
// ActivityManagerService.java

private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
    final ProcessRecord app;
    synchronized (mPidsSelfLocked) {
        // 获取进程实例 ProcessRecord
        app = mPidsSelfLocked.get(pid);
    }
    // ...
    synchronized (this) {
        // ...

        // See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
            } catch (Exception e) {
                // ...
            }
        }
        // ...
    }
}

从注释可以看出,检测是否 top visible acitivity 等待进程启动,如果有,这个时候就可以启动这个 top visible activity

java 复制代码
// ActivityTaskManagerService.java

public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
    synchronized (mGlobalLockWithoutBoost) {
        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
        }
        try {
            return mRootWindowContainer.attachApplication(wpc);
        } finally {
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
}
java 复制代码
// RootWindowContainer.java

boolean attachApplication(WindowProcessController app) throws RemoteException {
    try {
        return mAttachApplicationHelper.process(app);
    } finally {
        mAttachApplicationHelper.reset();
    }
}  

boolean process(WindowProcessController app) throws RemoteException {
    mApp = app;

    // RootWindowContainer 从上到下,遍历所有的 children,也就是 DisplayContent
    for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
        // DisplayContent 遍历所有的 root task
        getChildAt(displayNdx).forAllRootTasks(this);
        if (mRemoteException != null) {
            throw mRemoteException;
        }
    }

    // 此时有 aciivty 启动,mHasActivityStar为 true
    if (!mHasActivityStarted) {
        // ...
    }
    return mHasActivityStarted;
}

public void accept(Task rootTask) {
    if (mRemoteException != null) {
        return;
    }

    // root task 不可见,不能启动 activity
    // top root task 一般都是可见的
    if (rootTask.getVisibility(null /* starting */)
            == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
        return;
    }

    // 保存 root task 的 top activity
    mTop = rootTask.topRunningActivity();

    // root task 从上到下遍历 activity
    rootTask.forAllActivities(this);
}

public boolean test(ActivityRecord r) {
    // 注意这里 r.app != null 条件,如果 r.app 不为 null,那么表示 activity 已经启动
    if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard
            || r.app != null || mApp.mUid != r.info.applicationInfo.uid
            || !mApp.mName.equals(r.processName)) {
        return false;
    }
    try {
        // 真正启动 acivity 地方
        if (mTaskSupervisor.realStartActivityLocked(r, mApp,
                mTop == r && r.getTask().canBeResumed(r) /* andResume */,
                true /* checkConfig */)) {
            // 表示有activity启动了
            mHasActivityStarted = true;
        }
    } catch (RemoteException e) {
        // ...
    }
    return false;
}

有了窗口层级树的知识,这里是不是非常简单,就是从根 RootWindowContainer 开始,从上到下遍历,然后启动那些还没有启动的 activity,也就是启动我们的 target actvity

OK,现在由 ActivityTaskSupervisor 真正的来启动 target activity

java 复制代码
// ActivityTaskSupervisor.java

boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
        boolean andResume, boolean checkConfig) throws RemoteException {
    // 有 Activity 正在暂停,不能启动 activity
    if (!mRootWindowContainer.allPausedActivitiesComplete()) {
        ProtoLog.v(WM_DEBUG_STATES,
                "realStartActivityLocked: Skipping start of r=%s some activities pausing...",
                r);
        return false;
    }
    
    // ...

    try {
        // ...

        // 1.ActivityRecord 绑定进程,这就是 attach ActivityRecord to process
        // 下面马上就要通知 app 端启动 activity
        r.setProcess(proc);

        // ...

        // checkConfig 此时为 true
        if (checkConfig) {
            // 更新可见性,然后检测系统方向是否要更新,如果要更新,那么还会更新系统配置
            mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
                    false /* markFrozenIfConfigChanged */, true /* deferResume */);
        }
        
        // ...

        // 这是一个 binder ,它会被发送给客户端,客户端使用它来回调 activity 状态
        final IActivityClientController activityClientController =
                proc.hasEverLaunchedActivity() ? null : mService.mActivityClientController;

        // ...

        try {
            if (!proc.hasThread()) {
                throw new RemoteException();
            }

            // ...

            // event log : wm_restart_activity [userId, hashCode, taskId, componentName]
            EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r),
                    task.mTaskId, r.shortComponentName);

            // ...

            // 计算发送客户端的配置
            final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity();
            final MergedConfiguration mergedConfiguration = new MergedConfiguration(
                    procConfig, r.getMergedOverrideConfiguration());
            // 保存上一次发送的配置
            r.setLastReportedConfiguration(mergedConfiguration);

            // ...

            // 创建一个事务,这个事务会发送给客户端
            final ClientTransaction clientTransaction = ClientTransaction.obtain(
                    proc.getThread(), r.token);
            final boolean isTransitionForward = r.isTransitionForward();
            final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
            final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
            // 给这个事务,添加一个回调,这个用于创建 Activity,并且调用 Activity#onCreate()
            clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                    System.identityHashCode(r), r.info,
                    // TODO: Have this take the merged configuration instead of separate global
                    // and override configs.
                    mergedConfiguration.getGlobalConfiguration(),
                    mergedConfiguration.getOverrideConfiguration(), deviceId,
                    r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
                    proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
                    results, newIntents, r.takeOptions(), isTransitionForward,
                    proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
                    r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));

            // Set desired final state.
            final ActivityLifecycleItem lifecycleItem;
            if (andResume) {
                // 创建一个 Resume 的生命周期请求
                // 代表 Activity 声明周期的最终状态
                lifecycleItem = ResumeActivityItem.obtain(isTransitionForward,
                        r.shouldSendCompatFakeFocus());
            } else {
                // ...
            }

            // 设置最终的生命周期状态
            clientTransaction.setLifecycleStateRequest(lifecycleItem);
            
            // 2. 通知 app 端创建 Activity,并将生命周期执行到 onResumed()
            mService.getLifecycleManager().scheduleTransaction(clientTransaction);
            // ...
        } catch (RemoteException e) {
            // ...
        }
    } finally {
        endDeferResume();
        proc.resumeConfigurationDispatch();
    }
    r.launchFailed = false;
    if (andResume && readyToResume()) {
        // 3.完成最小部分 activity resumed 的工作
        // 例如将 activity 状态设置为 RESUMED
        // Activity执行完 onResumed() 后,会通知服务端的,服务端然后完成剩余的 resumed activity 工作
        rootTask.minimalResumeActivityLocked(r);
    } else {
        // ...
    }
    // ...
    return true;
}

ActivityTaskSupervisor#realStartActivityLocked() 正如函数名一样,真正用来启动 Activity

  1. 因为即将要通知 app 端启动 target activity,所以,这个时候 ActivityRecord 就要与进程绑定起来,这就是所谓的 attach ActivityRecord to process。
  2. 通知 app 端创建 Activity,并将生命周期执行到 onResumed(),至此,activity 的启动,终于完成。
  3. 完成最小部分 activity resumed 的工作,主要就是把 ActivityRecord 的状态切换为 RESUMED。剩下的一部分工作将由 app 端通知服务端 activity resumed 后完成,服务端收到 activity resumed 后,处理了一些杂事,本文不分析。

stop activity

前面 target activity 已经执行了 onResumed(),那么已暂停的 Activity 什么时候执行 onStoped() 呢?

前面分析过,一定得等到动画结束,才能被允许进入 stopping 流程,动画结束后会调用如下函数

java 复制代码
// ActivityTaskSupervisor.java

    void scheduleProcessStoppingAndFinishingActivitiesIfNeeded() {
    
        // 可以看到,这里不仅处理 stopping 流程,还处理 finishing 流程
        if (mStoppingActivities.isEmpty() && mFinishingActivities.isEmpty()) {
            return;
        }

        // 1.所有 resumed activity 进入 idle 状态,调度一次 idle 流程,处理 stopping 流程
        if (mRootWindowContainer.allResumedActivitiesIdle()) {
            scheduleIdle();
            return;
        }

        // 2.activity 真窗绘制完成,也可以执行 stopping 流程
        if (!mHandler.hasMessages(PROCESS_STOPPING_AND_FINISHING_MSG)
                && mRootWindowContainer.allResumedActivitiesVisible()) {
            mHandler.sendEmptyMessage(PROCESS_STOPPING_AND_FINISHING_MSG);
        }
    }

可以看到,有两种方式进入 stopping 流程

  1. 所有 resumed activity 上报了 idle 状态,可以处理 stopping 流程。
  2. Activity 真窗绘制完成,也可以进入 stopping 流程。

什么是 idle 状态,大家查询下 IdleHandler 就知道了。

进入 stopping 流程,最终调用人下函数

java 复制代码
// ActivityTaskSupervisor.java

    void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,
            boolean processPausingActivities, String reason) {
        boolean displaySwapping = false;
        ArrayList<ActivityRecord> readyToStopActivities = null;
        
        for (int i = 0; i < mStoppingActivities.size(); i++) {
            final ActivityRecord s = mStoppingActivities.get(i);
            
            // 判断 ActivityRecord 是否处于动画中
            // 目前,确实处于动画中
            final boolean animating = s.isInTransition()
                    && s.getTask() != null && !s.getTask().isForceHidden();
                    
            displaySwapping |= s.isDisplaySleepingAndSwapping();
            
            ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
                    + "finishing=%s", s, s.nowVisible, animating, s.finishing);
            
            // 如果 ActivityRecord 处于动画中,那就保存到 readyToStopActivities
            if ((!animating && !displaySwapping) || mService.mShuttingDown) {
                if (!processPausingActivities && s.isState(PAUSING)) {
                    // Defer processing pausing activities in this iteration and reschedule
                    // a delayed idle to reprocess it again
                    removeIdleTimeoutForActivity(launchedActivity);
                    scheduleIdleTimeout(launchedActivity);
                    continue;
                }

                ProtoLog.v(WM_DEBUG_STATES, "Ready to stop: %s", s);
                if (readyToStopActivities == null) {
                    readyToStopActivities = new ArrayList<>();
                }
                readyToStopActivities.add(s);

                mStoppingActivities.remove(i);
                i--;
            }
        }

        // ...

        final int numReadyStops = readyToStopActivities == null ? 0 : readyToStopActivities.size();
        for (int i = 0; i < numReadyStops; i++) {
            final ActivityRecord r = readyToStopActivities.get(i);
            if (r.isInHistory()) {
                if (r.finishing) {
                    // ...
                } else {
                    // 通知 app 端去 stop
                    r.stopIfPossible();
                }
            }
        }

        // ...
    }

现在,动画已经结束了,已暂停的 ActivityRecord 正式进入 stop 流程,如下

java 复制代码
// ActivityRecord.java

    void stopIfPossible() {
        if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);
        final Task rootTask = getRootTask();
        
        if (isNoHistory()) {
            if (!finishing) {
                if (!rootTask.shouldSleepActivities()) {
                    ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s", this);
                    // 注意,nohistory 的 Activity,直接进入 finish 流程
                    if (finishIfPossible("stop-no-history", false /* oomAdj */)
                            != FINISH_RESULT_CANCELLED) {
                        resumeKeyDispatchingLocked();
                        return;
                    }
                } else {
                    ProtoLog.d(WM_DEBUG_STATES, "Not finishing noHistory %s on stop "
                            + "because we're just sleeping", this);
                }
            }
        }

        if (!attachedToProcess()) {
            return;
        }
        resumeKeyDispatchingLocked();
        try {
            ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);

            // 1.状态切换到 STOPPING
            setState(STOPPING, "stopIfPossible");
            
            if (DEBUG_VISIBILITY) {
                Slog.v(TAG_VISIBILITY, "Stopping:" + this);
            }
            
            EventLogTags.writeWmStopActivity(
                    mUserId, System.identityHashCode(this), shortComponentName);
            
            //2. 通知 app 端去 stop activity
            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
                    StopActivityItem.obtain(configChangeFlags));

            mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
        } catch (Exception e) {
            // ...
        }
    }

可以看到,stop 流程中,服务端通知 app 端去 stop activity,最终生命周期走到 onStopped()。

结束

限于篇幅的原因,本身省略了很多细节,想深入研究的读者,可以在本文的基础上继续研究。

虽然省略了很多细节,但是 Activity 启动的生命周期流程,仍然是是非复杂的。犹如练乾坤大挪移一般,每练深一层,需要更高的内功。

可能,你现在的基础不够牢固,但是没有关系,Activity 生命周期方面,我已经带你入门了。你可以点赞收藏加关注之后,慢慢打好基础,再基于本文,进行融会贯通。

相关推荐
深海呐3 小时前
Android 最新的AndroidStudio引入依赖失败如何解决?如:Failed to resolve:xxxx
android·failed to res·failed to·failed to resol·failed to reso
解压专家6663 小时前
安卓解压软件推荐:高效处理压缩文件的实用工具
android·智能手机·winrar·7-zip
Rverdoser3 小时前
Android 老项目适配 Compose 混合开发
android
️ 邪神5 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏
android·flutter·ios·鸿蒙·reatnative
努力遇见美好的生活6 小时前
Mysql每日一题(行程与用户,困难※)
android·数据库·mysql
图王大胜7 小时前
Android Framework AMS(17)APP 异常Crash处理流程解读
android·app·异常处理·ams·crash·binderdied·讣告
ch_s_t8 小时前
电子商务网站之首页设计
android
豆 腐10 小时前
MySQL【四】
android·数据库·笔记·mysql
想取一个与众不同的名字好难12 小时前
android studio导入OpenCv并改造成.kts版本
android·ide·android studio
Jewel10513 小时前
Flutter代码混淆
android·flutter·ios