Android14 - AMS之Activity启动过程(2)

Android14 - AMS之Activity启动过程(1)-CSDN博客
Android14 - AMS之Activity启动过程(3)-CSDN博客
上篇梳理到:

TaskDisplayArea和Task的复用与创建

TaskDisplayArea

executeRequest后,随后调用startActivityUnchecked,进而调用startActivityInner。

platform/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java

复制代码
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)` `{`
    `...`
`    result =` `startActivityInner(r, sourceRecord, voiceSession, voiceInteractor, startFlags, options, inTask, inTaskFragment, balCode, intentGrants, realCallingUid);`
`}`

`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)` `{`
        `// 位置1:初始化启动参数`
        `setInitialState(r, options, inTask, inTaskFragment, startFlags, sourceRecord,`
`                voiceSession, voiceInteractor, balCode, realCallingUid);`
        `computeLaunchingTaskFlags();`
`        mIntent.setFlags(mLaunchFlags);`
        
        `...`
        `// 位置2:mPreferredTaskDisplayArea为RootWindowContainer.getDefaultTaskDisplayArea()`
`        final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();`
`        final Task prevTopTask = prevTopRootTask !=` `null` `? prevTopRootTask.getTopLeafTask()` `:` `null;`
`        final Task reusedTask =` `getReusableTask();`
        `...`
`        final Task targetTask = reusedTask !=` `null` `? reusedTask :` `computeTargetTask();`
`        final boolean newTask = targetTask ==` `null;`
`        mTargetTask = targetTask;`    
        
        `// 位置3:计算WindowingMode`
        `computeLaunchParams(r, sourceRecord, targetTask);`
        
        `int startResult =` `isAllowedToStart(r, newTask, targetTask);`        
        
        `...`
`        final ActivityRecord targetTaskTop = newTask`
                `?` `null` `: targetTask.getTopNonFinishingActivity();`
        `if` `(targetTaskTop !=` `null)` `{`
            `// Removes the existing singleInstance activity in another task (if any) while`
            `// launching a singleInstance activity on sourceRecord's task.`
            `if` `(LAUNCH_SINGLE_INSTANCE == mLaunchMode && mSourceRecord !=` `null`
                    `&& targetTask == mSourceRecord.getTask())` `{`
`                final ActivityRecord activity = mRootWindowContainer.findActivity(mIntent,`
`                        mStartActivity.info,` `false);`
                `if` `(activity !=` `null` `&& activity.getTask()` `!= targetTask)` `{`
`                    activity.destroyIfPossible("Removes redundant singleInstance");`
                `}`
            `}`
            `recordTransientLaunchIfNeeded(targetTaskTop);`
            `// Recycle the target task for this launch.`
`            startResult =` `recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);`
            `if` `(startResult != START_SUCCESS)` `{`
                `return startResult;`
            `}`
        `}` `else` `{`
`            mAddingToTask =` `true;`
        `}`

        `if` `(mTargetRootTask ==` `null)` `{`
`            mTargetRootTask =` `getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,`
`                    mOptions);`
        `}`
        `if` `(newTask)` `{`
`            final Task taskToAffiliate =` `(mLaunchTaskBehind && mSourceRecord !=` `null)`
                    `? mSourceRecord.getTask()` `:` `null;`
            `setNewTask(taskToAffiliate);`
        `}` `else` `if` `(mAddingToTask)` `{`
            `addOrReparentStartingActivity(targetTask,` `"adding to task");`
        `}`
        `...`
`        mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,`
`                mOptions, sourceRecord);`
    `// **** 位置4`
    `if` `(mDoResume)` `{`
`		final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked();`
		`if` `(!mTargetRootTask.isTopActivityFocusable()`
				`||` `(topTaskActivity !=` `null` `&& topTaskActivity.isTaskOverlay()`
				`&& mStartActivity != topTaskActivity))` `{`
			`// If the activity is not focusable, we can't resume it, but still would like to`
			`// make sure it becomes visible as it starts (this will also trigger entry`
			`// animation). An example of this are PIP activities.`
			`// Also, we don't want to resume activities in a task that currently has an overlay`
			`// as the starting activity just needs to be in the visible paused state until the`
			`// over is removed.`
			`// Passing {@code null} as the start parameter ensures all activities are made`
			`// visible.`
`			mTargetRootTask.ensureActivitiesVisible(null` `/* starting */,`
					`0` `/* configChanges */,` `!PRESERVE_WINDOWS);`
			`// Go ahead and tell window manager to execute app transition for this activity`
			`// since the app transition will not be triggered through the resume channel.`
`			mTargetRootTask.mDisplayContent.executeAppTransition();`
		`}` `else` `{`
			`// If the target root-task was not previously focusable (previous top running`
			`// activity on that root-task was not visible) then any prior calls to move the`
			`// root-task to the will not update the focused root-task.  If starting the new`
			`// activity now allows the task root-task to be focusable, then ensure that we`
			`// now update the focused root-task accordingly.`
			`if` `(!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()`
					`&&` `!mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask))` `{`
`				mTargetRootTask.moveToFront("startActivityInner");`
			`}`
`			mRootWindowContainer.resumeFocusedTasksTopActivities(`
`					mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);`
		`}`
	`}`
`	mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);`

	`// Update the recent tasks list immediately when the activity starts`
`	mSupervisor.mRecentTasks.add(startedTask);`
`	mSupervisor.handleNonResizableTaskIfNeeded(startedTask,`
`			mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask);`

	`// If Activity's launching into PiP, move the mStartActivity immediately to pinned mode.`
	`// Note that mStartActivity and source should be in the same Task at this point.`
	`if` `(mOptions !=` `null` `&& mOptions.isLaunchIntoPip()`
			`&& sourceRecord !=` `null` `&& sourceRecord.getTask()` `== mStartActivity.getTask()`
			`&& balCode != BAL_BLOCK)` `{`
`		mRootWindowContainer.moveActivityToPinnedRootTask(mStartActivity,`
`				sourceRecord,` `"launch-into-pip");`
	`}`

	`return START_SUCCESS;`
`}

mPreferredTaskDisplayArea的赋值来自setInitialState,

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java`

`private void setInitialState(ActivityRecord r, ActivityOptions options, Task inTask,`
`            TaskFragment inTaskFragment, int startFlags,`
`            ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession,`
`            IVoiceInteractor voiceInteractor, @BalCode int balCode, int realCallingUid) {`
            
    `mStartActivity = r;`
`	mIntent = r.intent;`
`	mOptions = options;`
`	mCallingUid = r.launchedFromUid;`
`	mRealCallingUid = realCallingUid;`
`	mSourceRecord = sourceRecord;`
`	mSourceRootTask = mSourceRecord != null ? mSourceRecord.getRootTask() : null;`
`	mVoiceSession = voiceSession;`
`	mVoiceInteractor = voiceInteractor;`
`	mBalCode = balCode;`

`	mLaunchParams.reset();`

`    mSupervisor.getLaunchParamsController().calculate(inTask, r.info.windowLayout, r, sourceRecord, options, mRequest, PHASE_DISPLAY, mLaunchParams);`
`    mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea()`
`                ? mLaunchParams.mPreferredTaskDisplayArea`
`                : mRootWindowContainer.getDefaultTaskDisplayArea();`
`    mPreferredWindowingMode = mLaunchParams.mWindowingMode;`

`    mLaunchMode = r.launchMode;`

`    mLaunchFlags = adjustLaunchFlagsToDocumentMode(`
`                r, LAUNCH_SINGLE_INSTANCE == mLaunchMode,`
`                LAUNCH_SINGLE_TASK == mLaunchMode, mIntent.getFlags());

首先注意mStartActivity = r;的赋值。mStartActivity就是新new的ActivityRecord后面会用到。

mSupervisor.getLaunchParamsController()来自ActivityTaskSupervisor的初始化,调用其calculate方法。注意,这里传入的PHASE_DISPLAY:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java`

`public void initialize() {`
`    mLaunchParamsController = new LaunchParamsController(mService, mLaunchParamsPersister);`
`    mLaunchParamsController.registerDefaultModifiers(this);`
`}`

`void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {`
`        // {@link TaskLaunchParamsModifier} handles window layout preferences.`
`        registerModifier(new TaskLaunchParamsModifier(supervisor));`
`        if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) {`
`            // {@link DesktopModeLaunchParamsModifier} handles default task size changes`
`            registerModifier(new DesktopModeLaunchParamsModifier());`
`        }`
`    }

我们看到,new了一个LaunchParamsController后,注册了Modifiers为new TaskLaunchParamsModifier。

LaunchParamsController的calculate代码:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/LaunchParamsController.java`

`void` `calculate(Task task,` `WindowLayout layout,` `ActivityRecord activity,` `ActivityRecord source,`
            `ActivityOptions options, @Nullable Request request,` `int phase,` `LaunchParams result)` `{`
     `if` `(task !=` `null` `|| activity !=` `null)` `{`
`		mPersister.getLaunchParams(task, activity, result);`
	`}`

	`// We start at the last registered {@link LaunchParamsModifier} as this represents`
	`// The modifier closest to the product level. Moving back through the list moves closer to`
	`// the platform logic.`
	`for` `(int i = mModifiers.size()` `-` `1; i >=` `0;` `--i)` `{`
`		mTmpCurrent.set(result);`
`		mTmpResult.reset();`
`		final LaunchParamsModifier modifier = mModifiers.get(i);`

		`switch(modifier.onCalculate(task, layout, activity, source, options, request, phase,`
`				mTmpCurrent, mTmpResult))` `{`
			`case RESULT_SKIP:`
				`// Do not apply any results when we are told to skip`
				`continue;`
			`case RESULT_DONE:`
				`// Set result and return immediately.`
`				result.set(mTmpResult);`
				`return;`
			`case RESULT_CONTINUE:`
				`// Set result and continue`
`				result.set(mTmpResult);`
				`break;`
		`}`
	`}`

	`if` `(activity !=` `null` `&& activity.requestedVrComponent !=` `null)` `{`
		`// Check if the Activity is a VR activity. If so, it should be launched in main display.`
`		result.mPreferredTaskDisplayArea = mService.mRootWindowContainer`
				`.getDefaultTaskDisplayArea();`
	`}` `else` `if` `(mService.mVr2dDisplayId != INVALID_DISPLAY)` `{`
		`// Get the virtual display ID from ActivityTaskManagerService. If that's set we`
		`// should always use that.`
`		result.mPreferredTaskDisplayArea = mService.mRootWindowContainer`
				`.getDisplayContent(mService.mVr2dDisplayId).getDefaultTaskDisplayArea();`
	`}`           
`}

mModifiers目前只有一个成员,就是TaskLaunchParamsModifier,这里调用其onCalculate:

复制代码
@Override`
    `public` `int` `onCalculate(@Nullable` `Task task,` `@Nullable` `ActivityInfo.WindowLayout layout,`
            `@Nullable` `ActivityRecord activity,` `@Nullable` `ActivityRecord source,`
            `@Nullable` `ActivityOptions options,` `@Nullable` `Request request,` `int phase,`
            `LaunchParams currentParams,` `LaunchParams outParams)` `{`
        `initLogBuilder(task, activity);`
        `final` `int result =` `calculate(task, layout, activity, source, options, request, phase,`
`                currentParams, outParams);`
        `outputLog();`
        `return result;`
    `}`

    `private` `int` `calculate(@Nullable` `Task task,` `@Nullable` `ActivityInfo.WindowLayout layout,`
            `@Nullable` `ActivityRecord activity,` `@Nullable` `ActivityRecord source,`
            `@Nullable` `ActivityOptions options,` `@Nullable` `Request request,` `int phase,`
            `LaunchParams currentParams,` `LaunchParams outParams)` `{`
        `final` `ActivityRecord root;`
        `if` `(task !=` `null)` `{`
`            root = task.getRootActivity()` `==` `null` `? activity : task.getRootActivity();`
        `}` `else` `{`
`            root = activity;`
        `}`

        `if` `(root ==` `null)` `{`
            `// There is a case that can lead us here. The caller is moving the top activity that is`
            `// in a task that has multiple activities to PIP mode. For that the caller is creating a`
            `// new task to host the activity so that we only move the top activity to PIP mode and`
            `// keep other activities in the previous task. There is no point to apply the launch`
            `// logic in this case.`
            `return RESULT_SKIP;`
        `}`

        `// STEP 1: Determine the suggested display area to launch the activity/task.`
        `final` `TaskDisplayArea suggestedDisplayArea =` `getPreferredLaunchTaskDisplayArea(task,`
`                options, source, currentParams, activity, request);`
`        outParams.mPreferredTaskDisplayArea = suggestedDisplayArea;`
        `final` `DisplayContent display = suggestedDisplayArea.mDisplayContent;`
        `if` `(DEBUG)` `{`
            `appendLog("display-id="` `+ display.getDisplayId()`
                    `+` `" task-display-area-windowing-mode="` `+ suggestedDisplayArea.getWindowingMode()`
                    `+` `" suggested-display-area="` `+ suggestedDisplayArea);`
        `}`

        `if` `(phase == PHASE_DISPLAY)` `{`
            `return RESULT_CONTINUE;`
        `}`
        `...`
`}

由于我们一开始的场景是从Context启动一个Activity,source、option、inTask等参数都是空,currentParams(从ActivityStarter.setInitialState构建而来)的mPreferredTaskDisplayArea也是空,所以getPreferredLaunchTaskDisplayArea方法最终会从getFallbackDisplayAreaForActivity方法获取mPreferredTaskDisplayArea:

复制代码
/**`
` * Calculates the default {@link TaskDisplayArea} for a task. We attempt to put the activity`
` * within the same display area if possible. The strategy is to find the display in the`
` * following order:`
` *`
` * <ol>`
` *     <li>The display area of the top activity from the launching process will be used</li>`
` *     <li>The display area of the top activity from the real launching process will be used`
` *     </li>`
` *     <li>Default display area from the associated root window container.</li>`
` * </ol>`
` * @param activityRecord the activity being started`
` * @param request optional {@link Request} made to start the activity record`
` * @return {@link TaskDisplayArea} to house the task`
` */`
`private` `TaskDisplayArea` `getFallbackDisplayAreaForActivity(`
		`@NonNull` `ActivityRecord activityRecord,` `@Nullable` `Request request)` `{`

	`WindowProcessController controllerFromLaunchingRecord = mSupervisor.mService`
			`.getProcessController(activityRecord.launchedFromPid,`
`					activityRecord.launchedFromUid);`
	`final` `TaskDisplayArea displayAreaForLaunchingRecord = controllerFromLaunchingRecord ==` `null`
			`?` `null` `: controllerFromLaunchingRecord.getTopActivityDisplayArea();`
	`if` `(displayAreaForLaunchingRecord !=` `null)` `{`
		`return displayAreaForLaunchingRecord;`
	`}`

	`WindowProcessController controllerFromProcess = mSupervisor.mService.getProcessController(`
`			activityRecord.getProcessName(), activityRecord.getUid());`
	`final` `TaskDisplayArea displayAreaForRecord = controllerFromProcess ==` `null` `?` `null`
			`: controllerFromProcess.getTopActivityDisplayArea();`
	`if` `(displayAreaForRecord !=` `null)` `{`
		`return displayAreaForRecord;`
	`}`

	`WindowProcessController controllerFromRequest = request ==` `null` `?` `null` `: mSupervisor`
			`.mService.getProcessController(request.realCallingPid, request.realCallingUid);`
	`final` `TaskDisplayArea displayAreaFromSourceProcess = controllerFromRequest ==` `null` `?` `null`
			`: controllerFromRequest.getTopActivityDisplayArea();`
	`if` `(displayAreaFromSourceProcess !=` `null)` `{`
		`return displayAreaFromSourceProcess;`
	`}`

	`return mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();`
`}

首先会根据activityRecord.launchedFromPid,也就是CallingPid(在ActivityStarter.executeReauest()时设置),或activityRecord.getProcessName,或者realCallingPid,去获取其所在的TopActivity所属的DisplayArea。比如如果是从Laucnher启动的一个新的Activity,那么此时会使用Launcher顶部Activity所属的DisplayArea。如果以上都没有找到, 则使用mRootWindowContainer的默认DefaultTaskDisplayArea。通常这个DefaultTaskDisplayArea是对应一个屏幕的DisplayContent。

回到calculate(方法。现在我们得到了suggestedDisplayArea,赋值给outParams.mPreferredTaskDisplayArea。由于上面我们传入的phase是PHASE_DISPLAY,所以下面直接返回return RESULT_CONTINUE;

再继续返回LaunchParamsController的calculate(..), RESULT_CONTINUE对应的逻辑是result.set(mTmpResult);,就是将刚才outParams的值设置到in-out型参数result,返回给上层。而后面的activity.requestedVrComponent 等判断逻辑,都是跟VR场景有关的,不探讨。

回到ActivityStarter的startActivityInner, 假如我们现在只有一个物理屏幕,那么final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();返回的是默认DisplayContent的getFocusedRootTask:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java`

`@Nullable`
`Task getFocusedRootTask() {`
`    return getItemFromTaskDisplayAreas(TaskDisplayArea::getFocusedRootTask);`
`}
复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java`

`/**`
` * Finds the first non {@code null} return value from calling the callback on all`
` * {@link TaskDisplayArea} at or below this container.`
` * @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it`
` *                 returns non {@code null}.`
` * @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in`
` *                            terms of z-order, else from bottom-to-top.`
` * @return the first returned object that is not {@code null}. Returns {@code null} if not`
` *         found.`
` */`
`@Nullable`
`<R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback,`
`		boolean traverseTopToBottom) {`
`	int childCount = mChildren.size();`
`	int i = traverseTopToBottom ? childCount - 1 : 0;`
`	while (i >= 0 && i < childCount) {`
`		R result = (R) mChildren.get(i)`
`				.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);`
`		if (result != null) {`
`			return result;`
`		}`
`		i += traverseTopToBottom ? -1 : 1;`
`	}`
`	return null;`
`}
复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayArea.java`

`@Nullable`
`@Override`
`<R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback,`
`		boolean traverseTopToBottom) {`
`	// Only DisplayArea of Type.ANY may contain TaskDisplayArea as children.`
`	if (mType != DisplayArea.Type.ANY) {`
`		return null;`
`	}`

`	int childCount = mChildren.size();`
`	int i = traverseTopToBottom ? childCount - 1 : 0;`
`	while (i >= 0 && i < childCount) {`
`		T child = mChildren.get(i);`
`		// Only traverse if the child is a DisplayArea.`
`		if (child.asDisplayArea() != null) {`
`			R result = (R) child.asDisplayArea()`
`					.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);`
`			if (result != null) {`
`				return result;`
`			}`
`		}`
`		i += traverseTopToBottom ? -1 : 1;`
`	}`
`	return null;`
`}
复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java`

`@Nullable`
`@Override`
`<R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback,`
`		boolean traverseTopToBottom) {`
`	if (traverseTopToBottom) {`
`		final R item = super.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);`
`		return item != null ? item : callback.apply(this);`
`	} else {`
`		final R item = callback.apply(this);`
`		return item != null`
`				? item`
`				: super.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);`
`	}`
`}

getItemFromTaskDisplayAreas是一个递归查询某TaskDisplayArea的首个非空child的方法。其参数callback是一个Function类型的参数,用于将TaskDisplayArea转换为R类型。

首先,getItemFromTaskDisplayAreas是在WindowContainer声明的,DisplayArea、TaskDisplayArea重载了getItemFromTaskDisplayAreas。所有的窗口类型(WindowToken、DisplayContent、DisplayArea、TaskDisplayArea等)都是WindowContainer的子类,而DisplayContent继承于DisplayArea,DisplayArea继承WindowContainer。所以这里首先执行的是DisplayArea的getItemFromTaskDisplayAreas。而TaskDisplayArea是DisplayContent的child,所以接下来会调用TaskDisplayArea的getItemFromTaskDisplayAreas,此时在递归深度优先获取到首个非空child后,会调用callbak。

由于getFocusedRootTask()传入的callback是TaskDisplayArea::getFocusedRootTask,这里是一个lamda语法,看下这个方法:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java`

`Task getFocusedRootTask() {`
`	if (mPreferredTopFocusableRootTask != null) {`
`		return mPreferredTopFocusableRootTask;`
`	}`

`	for (int i = mChildren.size() - 1; i >= 0; --i) {`
`		final WindowContainer child = mChildren.get(i);`
`		if (child.asTaskDisplayArea() != null) {`
`			final Task rootTask = child.asTaskDisplayArea().getFocusedRootTask();`
`			if (rootTask != null) {`
`				return rootTask;`
`			}`
`			continue;`
`		}`

`		final Task rootTask = mChildren.get(i).asTask();`
`		if (rootTask.isFocusableAndVisible()) {`
`			return rootTask;`
`		}`
`	}`

`	return null;`
`}

如果该TaskDisplayArea还有子TaskDisplayArea,那么会找子TaskDisplayArea的Task,否则就遍历当前TaskDisplayArea的子Task,找到isFocusableAndVisible的,返回。

这里的逻辑有点绕,关于窗口体系,请见Android12 - WMS之WindowContainer树(DisplayArea)-CSDN博客

复用Task

Task有嵌套关系。RootTask包含LeafTask。

回到startActivityInner,拿到prevTopRootTask后,Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/Task.java`

`/** Return the top-most leaf-task under this one, or this task if it is a leaf. */`
`public Task getTopLeafTask() {`
`	for (int i = mChildren.size() - 1; i >= 0; --i) {`
`		final Task child = mChildren.get(i).asTask();`
`		if (child == null) continue;`
`		return child.getTopLeafTask();`
`	}`
`	return this;`
`}

如果该prevTopRootTask有Child Task,则返回最顶部(最后加入的)Task,否则就返回自身。那么prevTopTask就是当前最顶部task。

final Task reusedTask = getReusableTask();获取可重复使用的Task。

复制代码
`
`/**`
` * Decide whether the new activity should be inserted into an existing task. Returns null`
` * if not or an ActivityRecord with the task into which the new activity should be added.`
` */`
`private Task getReusableTask() {`
`	// If a target task is specified, try to reuse that one`
`	if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) {`
`		Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId());`
`		if (launchTask != null) {`
`			return launchTask;`
`		}`
`		return null;`
`	}`

`	// We may want to try to place the new activity in to an existing task.  We always`
`	// do this if the target activity is singleTask or singleInstance; we will also do`
`	// this if NEW_TASK has been requested, and there is not an additional qualifier telling`
`	// us to still place it in a new task: multi task, always doc mode, or being asked to`
`	// launch this as a new task behind the current one.`
`	boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&`
`			(mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)`
`			|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);`
`	// If bring to front is requested, and no result is requested and we have not been given`
`	// an explicit task to launch in to, and we can find a task that was started with this`
`	// same component, then instead of launching bring that one to the front.`
`	putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;`
`	ActivityRecord intentActivity = null;`
`	if (putIntoExistingTask) {`
`		if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {`
`			// There can be one and only one instance of single instance activity in the`
`			// history, and it is always in its own unique task, so we do a special search.`
`			intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,`
`				   mStartActivity.isActivityTypeHome());`
`		} else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {`
`			// For the launch adjacent case we only want to put the activity in an existing`
`			// task if the activity already exists in the history.`
`			intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,`
`					!(LAUNCH_SINGLE_TASK == mLaunchMode));`
`		} else {`
`			// Otherwise find the best task to put the activity in.`
`			intentActivity =`
`					mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea);`
`		}`
`	}`

`	if (intentActivity != null && mLaunchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK`
`			&& !intentActivity.getTask().getRootActivity().mActivityComponent.equals(`
`			mStartActivity.mActivityComponent)) {`
`		// The task could be selected due to same task affinity. Do not reuse the task while`
`		// starting the singleInstancePerTask activity if it is not the task root activity.`
`		intentActivity = null;`
`	}`

`	if (intentActivity != null`
`			&& (mStartActivity.isActivityTypeHome() || intentActivity.isActivityTypeHome())`
`			&& intentActivity.getDisplayArea() != mPreferredTaskDisplayArea) {`
`		// Do not reuse home activity on other display areas.`
`		intentActivity = null;`
`	}`

`	return intentActivity != null ? intentActivity.getTask() : null;`
`}

如果mOptions.getLaunchTaskId指定了复用的TaskId(通过在startActivity时指定ActivityOption),则直接返回。否则根据Intent flag判断是否需要使用已存在的task。

如果getReusableTask返回空,则继续使用computeTargetTask查看是否有可复用的task

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java`

`/** Returns the leaf task where the target activity may be placed. */`
`private Task computeTargetTask() {`
`	if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask`
`			&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {`
`		// A new task should be created instead of using existing one.`
`		return null;`
`	} else if (mSourceRecord != null) {`
`		return mSourceRecord.getTask();`
`	} else if (mInTask != null) {`
`		// The task is specified from AppTaskImpl, so it may not be attached yet.`
`		if (!mInTask.isAttached()) {`
`			// Attach the task to display area. Ignore the returned root task (though usually`
`			// they are the same) because "target task" should be leaf task.`
`			getOrCreateRootTask(mStartActivity, mLaunchFlags, mInTask, mOptions);`
`		}`
`		return mInTask;`
`	} else {`
`		final Task rootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, null /* task */,`
`				mOptions);`
`		final ActivityRecord top = rootTask.getTopNonFinishingActivity();`
`		if (top != null) {`
`			return top.getTask();`
`		} else {`
`			// Remove the root task if no activity in the root task.`
`			rootTask.removeIfPossible("computeTargetTask");`
`		}`
`	}`
`	return null;`
`}

从之前的参数整理部分,我们在我们启动的这个场景,resultTo和mInTask是空,此时mAddingToTask是false。如果用的是FLAG_ACTIVITY_NEW_TASK标签,则直接就放回null了。本方法的其他的条件中,也是想复用task。所以这个方法本意还是想复用。如果没有复用的话,mTargetRootTask还是空。

更新WindowingMode和mBounds

回到startActivityInner继续往下,在【位置3】处,调用了如下代码:

computeLaunchParams(r, sourceRecord, targetTask);

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java`

`private void computeLaunchParams(ActivityRecord r, ActivityRecord sourceRecord,`
`		Task targetTask) {`
`	mSupervisor.getLaunchParamsController().calculate(targetTask, r.info.windowLayout, r,`
`			sourceRecord, mOptions, mRequest, PHASE_BOUNDS, mLaunchParams);`
`	mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea()`
`			? mLaunchParams.mPreferredTaskDisplayArea`
`			: mRootWindowContainer.getDefaultTaskDisplayArea();`
`	mPreferredWindowingMode = mLaunchParams.mWindowingMode;`
`}

这里看到,会再次调用mSupervisor.getLaunchParamsController().calculate, 二此时传入的phase是PHASE_BOUNDS。那么我们回到TaskLaunchParamsModifier.calculate方法。上次我们走到PHASE_DISPLAY后return了。这次继续往下走:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java`

`private int calculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,`
`		@Nullable ActivityRecord activity, @Nullable ActivityRecord source,`
`		@Nullable ActivityOptions options, @Nullable Request request, int phase,`
`		LaunchParams currentParams, LaunchParams outParams) {`
`	final ActivityRecord root;`
`	if (task != null) {`
`		root = task.getRootActivity() == null ? activity : task.getRootActivity();`
`	} else {`
`		root = activity;`
`	}`

`	if (root == null) {`
`		// There is a case that can lead us here. The caller is moving the top activity that is`
`		// in a task that has multiple activities to PIP mode. For that the caller is creating a`
`		// new task to host the activity so that we only move the top activity to PIP mode and`
`		// keep other activities in the previous task. There is no point to apply the launch`
`		// logic in this case.`
`		return RESULT_SKIP;`
`	}`

`	// STEP 1: Determine the suggested display area to launch the activity/task.`
`	final TaskDisplayArea suggestedDisplayArea = getPreferredLaunchTaskDisplayArea(task,`
`			options, source, currentParams, activity, request);`
`	outParams.mPreferredTaskDisplayArea = suggestedDisplayArea;`
`	final DisplayContent display = suggestedDisplayArea.mDisplayContent;`
`	if (DEBUG) {`
`		appendLog("display-id=" + display.getDisplayId()`
`				+ " task-display-area-windowing-mode=" + suggestedDisplayArea.getWindowingMode()`
`				+ " suggested-display-area=" + suggestedDisplayArea);`
`	}`

`	if (phase == PHASE_DISPLAY) {`
`		return RESULT_CONTINUE;`
`	}`
`   // *** 本次我们继续往下执行`
`	// STEP 2: Resolve launch windowing mode.`
`	// STEP 2.1: Determine if any parameter can specify initial bounds/windowing mode. That`
`	// might be the launch bounds from activity options, or size/gravity passed in layout. It`
`	// also treats the launch windowing mode in options and source activity windowing mode in`
`	// some cases as a suggestion for future resolution.`
`	int launchMode = options != null ? options.getLaunchWindowingMode()`
`			: WINDOWING_MODE_UNDEFINED;`
`	// In some cases we want to use the source's windowing mode as the default value, e.g. when`
`	// source is a freeform window in a fullscreen display launching an activity on the same`
`	// display.`
`	if (launchMode == WINDOWING_MODE_UNDEFINED`
`			&& canInheritWindowingModeFromSource(display, suggestedDisplayArea, source)) {`
`		// The source's windowing mode may be different from its task, e.g. activity is set`
`		// to fullscreen and its task is pinned windowing mode when the activity is entering`
`		// pip.`
`		launchMode = source.getTask().getWindowingMode();`
`		if (DEBUG) {`
`			appendLog("inherit-from-source="`
`					+ WindowConfiguration.windowingModeToString(launchMode));`
`		}`
`	}`
`	// If the launch windowing mode is still undefined, inherit from the target task if the`
`	// task is already on the right display area (otherwise, the task may be on a different`
`	// display area that has incompatible windowing mode or the task organizer request to`
`	// disassociate the leaf task if relaunched and reparented it to TDA as root task).`
`	if (launchMode == WINDOWING_MODE_UNDEFINED`
`			&& task != null && task.getTaskDisplayArea() == suggestedDisplayArea`
`			&& !task.getRootTask().mReparentLeafTaskIfRelaunch) {`
`		launchMode = task.getWindowingMode();`
`		if (DEBUG) {`
`			appendLog("inherit-from-task="`
`					+ WindowConfiguration.windowingModeToString(launchMode));`
`		}`
`	}`
`	// hasInitialBounds is set if either activity options or layout has specified bounds. If`
`	// that's set we'll skip some adjustments later to avoid overriding the initial bounds.`
`	boolean hasInitialBounds = false;`
`	// hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow is set if the outParams.mBounds`
`	// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is`
`	// different, we should recalculating the bounds.`
`	boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false;`
`	// Note that initial bounds needs to be set to fullscreen tasks too as it's used as restore`
`	// bounds.`
`	final boolean canCalculateBoundsForFullscreenTask =`
`			canCalculateBoundsForFullscreenTask(suggestedDisplayArea, launchMode);`
`	final boolean canApplyFreeformWindowPolicy =`
`			canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode);`
`	final boolean canApplyWindowLayout = layout != null`
`			&& (canApplyFreeformWindowPolicy || canCalculateBoundsForFullscreenTask);`
`	final boolean canApplyBoundsFromActivityOptions =`
`			mSupervisor.canUseActivityOptionsLaunchBounds(options)`
`					&& (canApplyFreeformWindowPolicy`
`					|| canApplyPipWindowPolicy(launchMode)`
`					|| canCalculateBoundsForFullscreenTask);`

`	if (canApplyBoundsFromActivityOptions) {`
`		hasInitialBounds = true;`
`		// |launchMode| at this point can be fullscreen, PIP, MultiWindow, etc. Only set`
`		// freeform windowing mode if appropriate by checking |canApplyFreeformWindowPolicy|.`
`		launchMode = launchMode == WINDOWING_MODE_UNDEFINED && canApplyFreeformWindowPolicy`
`				? WINDOWING_MODE_FREEFORM`
`				: launchMode;`
`		outParams.mBounds.set(options.getLaunchBounds());`
`		if (DEBUG) appendLog("activity-options-bounds=" + outParams.mBounds);`
`	} else if (canApplyWindowLayout) {`
`		mTmpBounds.set(currentParams.mBounds);`
`		getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds);`
`		if (!mTmpBounds.isEmpty()) {`
`			launchMode = canApplyFreeformWindowPolicy ? WINDOWING_MODE_FREEFORM : launchMode;`
`			outParams.mBounds.set(mTmpBounds);`
`			hasInitialBounds = true;`
`			hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true;`
`			if (DEBUG) appendLog("bounds-from-layout=" + outParams.mBounds);`
`		} else {`
`			if (DEBUG) appendLog("empty-window-layout");`
`		}`
`	} else if (launchMode == WINDOWING_MODE_MULTI_WINDOW`
`			&& options != null && options.getLaunchBounds() != null) {`
`		// TODO: Investigate whether we can migrate this clause to the`
`		//  |canApplyBoundsFromActivityOptions| case above.`
`		outParams.mBounds.set(options.getLaunchBounds());`
`		hasInitialBounds = true;`
`		if (DEBUG) appendLog("multiwindow-activity-options-bounds=" + outParams.mBounds);`
`	}`

`	// STEP 2.2: Check if previous modifier or the controller (referred as "callers" below) has`
`	// some opinions on launch mode and launch bounds. If they have opinions and there is no`
`	// initial bounds set in parameters. Note the check on display ID is also input param`
`	// related because we always defer to callers' suggestion if there is no specific display ID`
`	// in options or from source activity.`
`	//`
`	// If opinions from callers don't need any further resolution, we try to honor that as is as`
`	// much as possible later.`

`	// Flag to indicate if current param needs no further resolution. It's true it current`
`	// param isn't freeform mode, or it already has launch bounds.`
`	boolean fullyResolvedCurrentParam = false;`
`	// We inherit launch params from previous modifiers or LaunchParamsController if options,`
`	// layout and display conditions are not contradictory to their suggestions. It's important`
`	// to carry over their values because LaunchParamsController doesn't automatically do that.`
`	// We only check if display matches because display area can be changed later.`
`	if (!currentParams.isEmpty() && !hasInitialBounds`
`			&& (currentParams.mPreferredTaskDisplayArea == null`
`				|| currentParams.mPreferredTaskDisplayArea.getDisplayId()`
`					== display.getDisplayId())) {`
`		// Only set windowing mode if display is in freeform. If the display is in fullscreen`
`		// mode we should only launch a task in fullscreen mode.`
`		if (currentParams.hasWindowingMode()`
`				&& suggestedDisplayArea.inFreeformWindowingMode()) {`
`			launchMode = currentParams.mWindowingMode;`
`			fullyResolvedCurrentParam = launchMode != WINDOWING_MODE_FREEFORM;`
`			if (DEBUG) {`
`				appendLog("inherit-" + WindowConfiguration.windowingModeToString(launchMode));`
`			}`
`		}`

`		if (!currentParams.mBounds.isEmpty()) {`
`			// Carry over bounds from callers regardless of launch mode because bounds is still`
`			// used to restore last non-fullscreen bounds when launch mode is not freeform.`
`			outParams.mBounds.set(currentParams.mBounds);`
`			fullyResolvedCurrentParam = true;`
`			if (launchMode == WINDOWING_MODE_FREEFORM) {`
`				if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);`
`			}`
`		}`
`	}`

`	// STEP 2.3: Adjust launch parameters as needed for freeform display. We enforce the`
`	// policies related to unresizable apps here. If an app is unresizable and the freeform`
`	// size-compat mode is enabled, it can be launched in freeform depending on other properties`
`	// such as orientation. Otherwise, the app is forcefully launched in maximized. The rest of`
`	// this step is to define the default policy when there is no initial bounds or a fully`
`	// resolved current params from callers.`

`	// hasInitialBoundsForSuggestedDisplayAreaInFreeformMode is set if the outParams.mBounds`
`	// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is`
`	// different, we should recalcuating the bounds.`
`	boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = false;`
`	if (suggestedDisplayArea.inFreeformWindowingMode()) {`
`		if (launchMode == WINDOWING_MODE_PINNED) {`
`			if (DEBUG) appendLog("picture-in-picture");`
`		} else if (!root.isResizeable()) {`
`			if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) {`
`				launchMode = WINDOWING_MODE_FREEFORM;`
`				if (outParams.mBounds.isEmpty()) {`
`					getTaskBounds(root, suggestedDisplayArea, layout, launchMode,`
`							hasInitialBounds, outParams.mBounds);`
`					hasInitialBoundsForSuggestedDisplayAreaInFreeformMode = true;`
`				}`
`				if (DEBUG) appendLog("unresizable-freeform");`
`			} else {`
`				launchMode = WINDOWING_MODE_FULLSCREEN;`
`				outParams.mBounds.setEmpty();`
`				if (DEBUG) appendLog("unresizable-forced-maximize");`
`			}`
`		}`
`	} else {`
`		if (DEBUG) appendLog("non-freeform-task-display-area");`
`	}`

    `// 更新WindoingMode,集成TaskDisplayArea的WindowMode`
`	// If launch mode matches display windowing mode, let it inherit from display.`
`	outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode()`
`			? WINDOWING_MODE_UNDEFINED : launchMode;`

`	if (phase == PHASE_WINDOWING_MODE) {`
`		return RESULT_CONTINUE;`
`	}`

`	// STEP 3: Finalize the display area. Here we allow WM shell route all launches that match`
`	// certain criteria to specific task display areas.`
`	final int resolvedMode = (launchMode != WINDOWING_MODE_UNDEFINED) ? launchMode`
`			: suggestedDisplayArea.getWindowingMode();`
`	TaskDisplayArea taskDisplayArea = suggestedDisplayArea;`
`	// If launch task display area is set in options we should just use it. We assume the`
`	// suggestedDisplayArea has the right one in this case.`
`	if (options == null || (options.getLaunchTaskDisplayArea() == null`
`			&& options.getLaunchTaskDisplayAreaFeatureId() == FEATURE_UNDEFINED)) {`
`		final int activityType =`
`				mSupervisor.mRootWindowContainer.resolveActivityType(root, options, task);`
`		display.forAllTaskDisplayAreas(displayArea -> {`
`			final Task launchRoot = displayArea.getLaunchRootTask(`
`					resolvedMode, activityType, null /* ActivityOptions */,`
`					null /* sourceTask*/, 0 /* launchFlags */);`
`			if (launchRoot == null) {`
`				return false;`
`			}`
`			mTmpDisplayArea = displayArea;`
`			return true;`
`		});`
`		// We may need to recalculate the bounds and the windowing mode if the new`
`		// TaskDisplayArea is different from the suggested one we used to calculate the two`
`		// configurations.`
`		if (mTmpDisplayArea != null && mTmpDisplayArea != suggestedDisplayArea) {`
`			outParams.mWindowingMode = (launchMode == mTmpDisplayArea.getWindowingMode())`
`					? WINDOWING_MODE_UNDEFINED : launchMode;`
`			if (hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow) {`
`				outParams.mBounds.setEmpty();`
`				getLayoutBounds(mTmpDisplayArea, root, layout, outParams.mBounds);`
`				hasInitialBounds = !outParams.mBounds.isEmpty();`
`			} else if (hasInitialBoundsForSuggestedDisplayAreaInFreeformMode) {`
`				outParams.mBounds.setEmpty();`
`				getTaskBounds(root, mTmpDisplayArea, layout, launchMode,`
`						hasInitialBounds, outParams.mBounds);`
`			}`
`		}`

`		if (mTmpDisplayArea != null) {`
`			taskDisplayArea = mTmpDisplayArea;`
`			mTmpDisplayArea = null;`
`			appendLog("overridden-display-area=["`
`					+ WindowConfiguration.activityTypeToString(activityType) + ", "`
`					+ WindowConfiguration.windowingModeToString(resolvedMode) + ", "`
`					+ taskDisplayArea + "]");`
`		}`
`	}`
`	appendLog("display-area=" + taskDisplayArea);`
`	outParams.mPreferredTaskDisplayArea = taskDisplayArea;`

`	if (phase == PHASE_DISPLAY_AREA) {`
`		return RESULT_CONTINUE;`
`	}`

`	// STEP 4: Determine final launch bounds based on resolved windowing mode and activity`
`	// requested orientation. We set bounds to empty for fullscreen mode and keep bounds as is`
`	// for all other windowing modes that's not freeform mode. One can read comments in`
`	// relevant methods to further understand this step.`
`	//`
`	// We skip making adjustments if the params are fully resolved from previous results.`
`	if (fullyResolvedCurrentParam) {`
`		if (resolvedMode == WINDOWING_MODE_FREEFORM) {`
`			// Make sure bounds are in the displayArea.`
`			if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) {`
`				adjustBoundsToFitInDisplayArea(taskDisplayArea, layout, outParams.mBounds);`
`			}`
`			// Even though we want to keep original bounds, we still don't want it to stomp on`
`			// an existing task.`
`			adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds);`
`		}`
`	} else {`
`		if (source != null && source.inFreeformWindowingMode()`
`				&& resolvedMode == WINDOWING_MODE_FREEFORM`
`				&& outParams.mBounds.isEmpty()`
`				&& source.getDisplayArea() == taskDisplayArea) {`
`			// Set bounds to be not very far from source activity.`
`			cascadeBounds(source.getConfiguration().windowConfiguration.getBounds(),`
`					taskDisplayArea, outParams.mBounds);`
`		}`
`		getTaskBounds(root, taskDisplayArea, layout, resolvedMode, hasInitialBounds,`
`				outParams.mBounds);`
`	}`
`	return RESULT_CONTINUE;`
`}

首先,对outParams.mBounds进行了设置

outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode()

? WINDOWING_MODE_UNDEFINED : launchMode;

则对mWindowingMode进行了设置。这里沿用的是上层TaskDisplayArea的launchMode。

关于TaskDisplayArea的launchMode, 默认值来自DisplayConent的构造方法。在创建DisplayConent时,会调用setWindowingMode(WINDOWING_MODE_FULLSCREEN);

TaskDisplayArea 创建Root Task

到目前,如果没有复用的话,mTargetRootTask还是空。所以会通过下面代码来创建新Task

复制代码
if (mTargetRootTask == null) {`
`	mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,`
`			mOptions);`
`}

getOrCreateRootTask会调用mRootWindowContainer.getOrCreateRootTask(r, aOptions, task, sourceTask, onTop, mLaunchParams, launchFlags);来创建Task:

复制代码
Task getOrCreateRootTask(@Nullable ActivityRecord r,`
`	@Nullable ActivityOptions options, @Nullable Task candidateTask,`
`	@Nullable Task sourceTask, boolean onTop,`
`	@Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags) {`
`// First preference goes to the launch root task set in the activity options.`
`if (options != null) {`
`	final Task candidateRoot = Task.fromWindowContainerToken(options.getLaunchRootTask());`
`	if (candidateRoot != null && canLaunchOnDisplay(r, candidateRoot)) {`
`		return candidateRoot;`
`	}`
`}`
`// *** 位置1`
`// Next preference goes to the task id set in the activity options.`
`if (options != null) {`
`	final int candidateTaskId = options.getLaunchTaskId();`
`	if (candidateTaskId != INVALID_TASK_ID) {`
`		// Temporarily set the task id to invalid in case in re-entry.`
`		options.setLaunchTaskId(INVALID_TASK_ID);`
`		final Task task = anyTaskForId(candidateTaskId,`
`				MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, options, onTop);`
`		options.setLaunchTaskId(candidateTaskId);`
`		if (canLaunchOnDisplay(r, task)) {`
`			return task.getRootTask();`
`		}`
`	}`
`}`
`// *** 位置2`
`// Next preference goes to the TaskDisplayArea candidate from launchParams`
`// or activity options.`
`TaskDisplayArea taskDisplayArea = null;`
`if (launchParams != null && launchParams.mPreferredTaskDisplayArea != null) {`
`	taskDisplayArea = launchParams.mPreferredTaskDisplayArea;`
`} else if (options != null) {`
`	final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();`
`	taskDisplayArea = daToken != null`
`			? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;`
`	if (taskDisplayArea == null) {`
`		final int launchDisplayId = options.getLaunchDisplayId();`
`		if (launchDisplayId != INVALID_DISPLAY) {`
`			final DisplayContent displayContent = getDisplayContent(launchDisplayId);`
`			if (displayContent != null) {`
`				taskDisplayArea = displayContent.getDefaultTaskDisplayArea();`
`			}`
`		}`
`	}`
`}`

`// *** 位置3`
`final int activityType = resolveActivityType(r, options, candidateTask);`
`// *** 位置4`
`if (taskDisplayArea != null) {`
`	if (canLaunchOnDisplay(r, taskDisplayArea.getDisplayId())) {`
		`// *** 位置5`
        `return taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,`
`				sourceTask, launchParams, launchFlags, activityType, onTop);`
`	} else {`
`		taskDisplayArea = null;`
`	}`
`}`

`// Give preference to the root task and display of the input task and activity if they`
`// match the mode we want to launch into.`
`Task rootTask = null;`
`if (candidateTask != null) {`
`	rootTask = candidateTask.getRootTask();`
`}`
`if (rootTask == null && r != null) {`
`	rootTask = r.getRootTask();`
`}`
`int windowingMode = launchParams != null ? launchParams.mWindowingMode`
`		: WindowConfiguration.WINDOWING_MODE_UNDEFINED;`
`if (rootTask != null) {`
`	taskDisplayArea = rootTask.getDisplayArea();`
`	if (taskDisplayArea != null`
`			&& canLaunchOnDisplay(r, taskDisplayArea.mDisplayContent.mDisplayId)) {`
`		if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {`
`			windowingMode = taskDisplayArea.resolveWindowingMode(r, options, candidateTask);`
`		}`
`		// Always allow organized tasks that created by organizer since the activity type`
`		// of an organized task is decided by the activity type of its top child, which`
`		// could be incompatible with the given windowing mode and activity type.`
`		if (rootTask.isCompatible(windowingMode, activityType)`
`				|| rootTask.mCreatedByOrganizer) {`
`			return rootTask;`
`		}`
`	} else {`
`		taskDisplayArea = null;`
`	}`

`}`

`// Falling back to default task container`
`if (taskDisplayArea == null) {`
`	taskDisplayArea = getDefaultTaskDisplayArea();`
`}`
`return taskDisplayArea.getOrCreateRootTask(r, options, candidateTask, sourceTask,`
`		launchParams, launchFlags, activityType, onTop);`
`}

这里是创建一个RootTask。

首先看位置1,candidateTaskId是空,就不复用了。位置2,launchParams.mPreferredTaskDisplayArea非空,为当前默认的DisplayContent下的TaskDisplayArea,于是taskDisplayArea的到赋值。位置3, activityType返回的是ACTIVITY_TYPE_STANDARD。

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java`

`int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,`
`		@Nullable Task task) {`
`	// Preference is given to the activity type for the activity then the task since the type`
`	// once set shouldn't change.`
`	int activityType = r != null ? r.getActivityType() : ACTIVITY_TYPE_UNDEFINED;`
`	if (activityType == ACTIVITY_TYPE_UNDEFINED && task != null) {`
`		activityType = task.getActivityType();`
`	}`
`	if (activityType != ACTIVITY_TYPE_UNDEFINED) {`
`		return activityType;`
`	}`
`	if (options != null) {`
`		activityType = options.getLaunchActivityType();`
`	}`
`	return activityType != ACTIVITY_TYPE_UNDEFINED ? activityType : ACTIVITY_TYPE_STANDARD;`
`}

位置4,canLaunchOnDisplay的检查,最终调用ActivityTaskSupervisor的canPlaceEntityOnDisplay:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java`

`private boolean canPlaceEntityOnDisplay(int displayId, int callingPid, int callingUid,`
`		Task task, ActivityInfo activityInfo) {`
`	if (displayId == DEFAULT_DISPLAY) {`
`		// No restrictions for the default display.`
`		return true;`
`	}`
`	if (!mService.mSupportsMultiDisplay) {`
`		// Can't launch on secondary displays if feature is not supported.`
`		return false;`
`	}`

`	if (!isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, displayId, activityInfo)) {`
`		// Can't place activities to a display that has restricted launch rules.`
`		// In this case the request should be made by explicitly adding target display id and`
`		// by caller with corresponding permissions. See #isCallerAllowedToLaunchOnDisplay().`
`		return false;`
`	}`

`	final DisplayContent displayContent =`
`			mRootWindowContainer.getDisplayContentOrCreate(displayId);`
`	if (displayContent != null) {`
`		final ArrayList<ActivityInfo> activities = new ArrayList<>();`
`		if (activityInfo != null) {`
`			activities.add(activityInfo);`
`		}`
`		if (task != null) {`
`			task.forAllActivities((r) -> {`
`				activities.add(r.info);`
`			});`
`		}`
`		return displayContent.mDwpcHelper.canContainActivities(activities,`
`					displayContent.getWindowingMode());`
`	}`

`	return true;`
`}

这里是针对多屏幕情况下,Activity是否可在对应屏幕上启动的合法性检查。如果只有一块屏幕,返回true。

所以,最终会在位置5调用taskDisplayArea.getOrCreateRootTask来创建Root Task:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/TaskDisplayArea.java`

`Task getOrCreateRootTask(@Nullable ActivityRecord r, @Nullable ActivityOptions options,`
`		@Nullable Task candidateTask, @Nullable Task sourceTask,`
`		@Nullable LaunchParams launchParams, int launchFlags, int activityType, boolean onTop) {`
`	int windowingMode = WINDOWING_MODE_UNDEFINED;`
`	if (launchParams != null) {`
`		// If launchParams isn't null, windowing mode is already resolved.`
`		windowingMode = launchParams.mWindowingMode;`
`	} else if (options != null) {`
`		// If launchParams is null and options isn't let's use the windowing mode in the`
`		// options.`
`		windowingMode = options.getLaunchWindowingMode();`
`	}`
`	// Validate that our desired windowingMode will work under the current conditions.`
`	// UNDEFINED windowing mode is a valid result and means that the new root task will inherit`
`	// it's display's windowing mode.`
`	windowingMode = validateWindowingMode(windowingMode, r, candidateTask);`
`	return getOrCreateRootTask(windowingMode, activityType, onTop, candidateTask, sourceTask,`
`			options, launchFlags);`
`}`

`/**`
` * When two level tasks are required for given windowing mode and activity type, returns an`
` * existing compatible root task or creates a new one.`
` * For one level task, the candidate task would be reused to also be the root task or create`
` * a new root task if no candidate task.`
` *`
` * @param windowingMode The windowing mode the root task should be created in.`
` * @param activityType  The activityType the root task should be created in.`
` * @param onTop         If true the root task will be created at the top of the display,`
` *                      else at the bottom.`
` * @param candidateTask The possible task the activity might be launched in. Can be null.`
` * @param sourceTask    The task requesting to start activity. Used to determine which of the`
` *                      adjacent roots should be launch root of the new task. Can be null.`
` * @param options       The activity options used to the launch. Can be null.`
` * @param launchFlags   The launch flags for this launch.`
` * @return The root task to use for the launch.`
` * @see #getRootTask(int, int)`
` */`
`Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,`
`		@Nullable Task candidateTask, @Nullable Task sourceTask,`
`		@Nullable ActivityOptions options, int launchFlags) {`
`	final int resolvedWindowingMode =`
`			windowingMode == WINDOWING_MODE_UNDEFINED ? getWindowingMode() : windowingMode;`
`	// Need to pass in a determined windowing mode to see if a new root task should be created,`
`	// so use its parent's windowing mode if it is undefined.`
`	if (!alwaysCreateRootTask(resolvedWindowingMode, activityType)) {`
`		Task rootTask = getRootTask(resolvedWindowingMode, activityType);`
`		if (rootTask != null) {`
`			return rootTask;`
`		}`
`	} else if (candidateTask != null) {`
`		final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;`
`		final Task launchParentTask = getLaunchRootTask(resolvedWindowingMode, activityType,`
`				options, sourceTask, launchFlags, candidateTask);`
`		if (launchParentTask != null) {`
`			if (candidateTask.getParent() == null) {`
`				launchParentTask.addChild(candidateTask, position);`
`			} else if (candidateTask.getParent() != launchParentTask) {`
`				candidateTask.reparent(launchParentTask, position);`
`			}`
`		} else if (candidateTask.getDisplayArea() != this`
`				|| candidateTask.getRootTask().mReparentLeafTaskIfRelaunch) {`
`			if (candidateTask.getParent() == null) {`
`				addChild(candidateTask, position);`
`			} else {`
`				candidateTask.reparent(this, onTop);`
`			}`
`		}`
`		// Update windowing mode if necessary, e.g. launch into a different windowing mode.`
`		if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()`
`				&& candidateTask.getWindowingMode() != windowingMode) {`
`			candidateTask.mTransitionController.collect(candidateTask);`
`			candidateTask.setWindowingMode(windowingMode);`
`		}`
`		return candidateTask.getRootTask();`
`	}`
`	return new Task.Builder(mAtmService)`
`			.setWindowingMode(windowingMode)`
`			.setActivityType(activityType)`
`			.setOnTop(onTop)`
`			.setParent(this)`
`			.setSourceTask(sourceTask)`
`			.setActivityOptions(options)`
`			.setLaunchFlags(launchFlags)`
`			.build();`
`}

首先我们知道,本文场景下,launchParams.mWindowingMode之前的代码赋值为了WINDOWING_MODE_FULLSCREEN, 因此这里windowingMode为WINDOWING_MODE_FULLSCREEN。所以接下来alwaysCreateRootTask这里:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java`

`static` `boolean` `alwaysCreateRootTask(int windowingMode, int activityType)` `{`
	`// Always create a root task for fullscreen, freeform, and multi windowing`
	`// modes so that we can manage visual ordering and return types correctly.`
	`return` `(activityType ==` `ACTIVITY_TYPE_STANDARD` `|| activityType ==` `ACTIVITY_TYPE_RECENTS)`
			`&&` `(windowingMode ==` `WINDOWING_MODE_FULLSCREEN`
			`|| windowingMode ==` `WINDOWING_MODE_FREEFORM`
			`|| windowingMode ==` `WINDOWING_MODE_PINNED`
			`|| windowingMode ==` `WINDOWING_MODE_MULTI_WINDOW);`
`}

由于前面知道activityType是ACTIVITY_TYPE_STANDARD, 所以本方法返回true。

在我们的场景candidateTask为空,因此会走Task.Builder(mAtmService)进行Task的创建。
看下Task.Builder的build方法:

复制代码
`
`Task build() {`
`            if (mParent != null && mParent instanceof TaskDisplayArea) {`
`                validateRootTask((TaskDisplayArea) mParent);`
`            }`

`            if (mActivityInfo == null) {`
`                mActivityInfo = new ActivityInfo();`
`                mActivityInfo.applicationInfo = new ApplicationInfo();`
`            }`

`            mUserId = UserHandle.getUserId(mActivityInfo.applicationInfo.uid);`
`            mTaskAffiliation = mTaskId;`
`            mLastTimeMoved = System.currentTimeMillis();`
`            mNeverRelinquishIdentity = true;`
`            mCallingUid = mActivityInfo.applicationInfo.uid;`
`            mCallingPackage = mActivityInfo.packageName;`
`            mResizeMode = mActivityInfo.resizeMode;`
`            mSupportsPictureInPicture = mActivityInfo.supportsPictureInPicture();`
`            if (!mRemoveWithTaskOrganizer && mActivityOptions != null) {`
`                mRemoveWithTaskOrganizer = mActivityOptions.getRemoveWithTaskOranizer();`
`            }`

`            final Task task = buildInner();`
`            task.mHasBeenVisible = mHasBeenVisible;`

`            // Set activity type before adding the root task to TaskDisplayArea, so home task can`
`            // be cached, see TaskDisplayArea#addRootTaskReferenceIfNeeded().`
`            if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {`
`                task.setActivityType(mActivityType);`
`            }`

`            if (mParent != null) {`
`                if (mParent instanceof Task) {`
`                    final Task parentTask = (Task) mParent;`
`                    parentTask.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM,`
`                            (mActivityInfo.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);`
`                } else {`
                `//` `**` `将本task加入到parent,也就是TaskDisplayArea的栈顶`
`                    mParent.addChild(task, mOnTop ? POSITION_TOP : POSITION_BOTTOM);`
`                }`
`            }`

`            // Set windowing mode after attached to display area or it abort silently.`
`            if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {`
`                task.setWindowingMode(mWindowingMode, true /* creating */);`
`            }`
`            return task;`
`        }

注意mParent.addChild将本task加入到了TaskDisplayArea的栈顶。

Activi ty 加入 Task

回到startActivityInner。getOrCreateRootTask()创建完Task后,紧接着判断newTask。因为前面newTask = targetTask == null, targetTast是指可复用的task,为空,所以这里newTask为true。因此紧接着调用setNewTask()。注意参数taskToAffiliate是null, 因为mSourceRecord是null。

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java`

`private void setNewTask(Task taskToAffiliate) {`
`        final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;`
         `//返回mTargetRootTask自身`
`        final Task task = mTargetRootTask.reuseOrCreateTask(`
`                mStartActivity.info, mIntent, mVoiceSession,`
`                mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);`
`        task.mTransitionController.collectExistenceChange(task);`
        `//` `将新new的activityRecord加入到mTargetRootTask`
`        addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");`

`        ProtoLog.v(WM_DEBUG_TASKS, "Starting new activity %s in new task %s",`
`                mStartActivity, mStartActivity.getTask());`

`        if (taskToAffiliate != null) {`
`            mStartActivity.setTaskToAffiliateWith(taskToAffiliate);`
`        }`
`    }

关注reuseOrCreateTask()方法, 其参数mStartActivity是之前新new的ActivityRecord, mIntent是客户端传过来intent, toTop为true:

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/Task.java`

`Task reuseOrCreateTask(ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession,`
`		IVoiceInteractor voiceInteractor,` `boolean toTop, ActivityRecord activity,`
`		ActivityRecord source, ActivityOptions options)` `{`

`	Task task;`
	`if` `(canReuseAsLeafTask())` `{`
       `// 会走这里`
		`// This root task will only contain one task, so just return itself since all root`
		`// tasks ara now tasks and all tasks are now root tasks.`
`		task =` `reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity);`
	`}` `else` `{`
		`// Create child task since this root task can contain multiple tasks.`
		`final` `int taskId = activity != null`
				`? mTaskSupervisor.getNextTaskIdForUser(activity.mUserId)`
				`: mTaskSupervisor.getNextTaskIdForUser();`
		`final` `int activityType =` `getActivityType();`
`		task =` `new` `Task.Builder(mAtmService)`
				`.setTaskId(taskId)`
				`.setActivityInfo(info)`
				`.setActivityOptions(options)`
				`.setIntent(intent)`
				`.setVoiceSession(voiceSession)`
				`.setVoiceInteractor(voiceInteractor)`
				`.setOnTop(toTop)`
				`.setParent(this)`
				`.build();`
	`}`

	`int displayId =` `getDisplayId();`
	`if` `(displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;`
	`final` `boolean isLockscreenShown = mAtmService.mTaskSupervisor.getKeyguardController()`
			`.isKeyguardOrAodShowing(displayId);`
	`if` `(!mTaskSupervisor.getLaunchParamsController()`
			`.layoutTask(task, info.windowLayout, activity, source, options)`
			`&&` `!getRequestedOverrideBounds().isEmpty()`
			`&& task.isResizeable()` `&&` `!isLockscreenShown)` `{`
`		task.setBounds(getRequestedOverrideBounds());`
	`}`

	`return task;`
`}

关注canReuseAsLeafTask():

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/Task.java`

`private` `boolean` `canReuseAsLeafTask()` `{`
	`// Cannot be reused as leaf task if this task is created by organizer or having child tasks.`
	`if` `(mCreatedByOrganizer ||` `!isLeafTask())` `{`
		`return` `false;`
	`}`

	`// Existing Tasks can be reused if a new root task will be created anyway.`
	`final` `int windowingMode =` `getWindowingMode();`
	`final` `int activityType =` `getActivityType();`
	`return DisplayContent.alwaysCreateRootTask(windowingMode, activityType);`
`}
复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/Task.java`

`boolean` `isLeafTask()` `{`
	`for` `(int i = mChildren.size()` `-` `1; i >=` `0;` `--i)` `{`
		`if` `(mChildren.get(i).asTask()` `!= null)` `{`
			`return` `false;`
		`}`
	`}`
	`return` `true;`
`}

canReuseAsLeafTask()返回true。因为isLeafTask()是true(此时还没有mChildren),而DisplayContent.alwaysCreateRootTask之前看过,返回true。

所以会执行:

task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity);

这句最终会返回task自身this。

整个setNewTask里的final Task task = mTargetRootTask.reuseOrCreateTask, 可以认为返回了mTargetRootTask自身,随后调用addOrReparentStartingActivity,把新new的mStartActivity加入到新new的mTargetRootTask

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/Task.java`

`private` `void` `addOrReparentStartingActivity(@NonNull Task task, String reason)` `{`
`	TaskFragment newParent = task;`
	`if` `(mInTaskFragment != null)` `{`
		`int embeddingCheckResult =` `canEmbedActivity(mInTaskFragment, mStartActivity, task);`
		`if` `(embeddingCheckResult == EMBEDDING_ALLOWED)` `{`
`			newParent = mInTaskFragment;`
`			mStartActivity.mRequestedLaunchingTaskFragmentToken =`
`					mInTaskFragment.getFragmentToken();`
		`}` `else` `{`
			`// Start mStartActivity to task instead if it can't be embedded to mInTaskFragment.`
			`sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult);`
		`}`
	`}` `else` `{`
`		TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;`
		`if` `(candidateTf == null)` `{`
			`// Puts the activity on the top-most non-isolated navigation TF, unless the`
			`// activity is launched from the same TF.`
			`final TaskFragment sourceTaskFragment =`
`					mSourceRecord != null ? mSourceRecord.getTaskFragment()` `: null;`
			`final ActivityRecord top = task.getActivity(r ->` `{`
				`if` `(!r.canBeTopRunning())` `{`
					`return` `false;`
				`}`
				`final TaskFragment taskFragment = r.getTaskFragment();`
				`return` `!taskFragment.isIsolatedNav()` `||` `(sourceTaskFragment != null`
						`&& sourceTaskFragment == taskFragment);`
			`});`
			`if` `(top != null)` `{`
`				candidateTf = top.getTaskFragment();`
			`}`
		`}`
		`if` `(candidateTf != null && candidateTf.isEmbedded()`
				`&&` `canEmbedActivity(candidateTf, mStartActivity, task)` `== EMBEDDING_ALLOWED)` `{`
			`// Use the embedded TaskFragment of the top activity as the new parent if the`
			`// activity can be embedded.`
`			newParent = candidateTf;`
		`}`
	`}`
	`if` `(mStartActivity.getTaskFragment()` `== null`
			`|| mStartActivity.getTaskFragment()` `== newParent)` `{`
`		newParent.addChild(mStartActivity, POSITION_TOP);`
	`}` `else` `{`
`		mStartActivity.reparent(newParent, newParent.getChildCount()` `/* top */, reason);`
	`}`
`}

此处TaskFragment newParent = task,getTaskFragment为null,newParent通过newParent.addChild(mStartActivity, POSITION_TOP)将activity加入task。

启动Activity

回到startActivityInner,来到位置4。由于上面的mTargetRootTask.startActivityLocked对整体流向没有影响,暂不讨论。下面看mDoResume条件满足内的逻辑:if (!mTargetRootTask.isTopActivityFocusable() ...。

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java`

`boolean isTopActivityFocusable() {`
`	final ActivityRecord r = topRunningActivity();`
`	return r != null ? r.isFocusable()`
`			: (isFocusable() && getWindowConfiguration().canReceiveKeys());`
`}

由于mTargetRootTask此时是刚创建的,所以topRunningActivity是空。所以这里用task本身的isFocusable(),

复制代码
platform/frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java`

`boolean isFocusable() {`
`	final WindowContainer parent = getParent();`
`	return (parent == null || parent.isFocusable()) && mIsFocusable;`
`}

由于mTargetRootTask自身的mIsFocusable属性默认是true,其parent TaskDisplayArea的isFocusable也是true,因此整体返回true。

那么startActivityInner里接下来就会走else分支里的mRootWindowContainer.resumeFocusedTasksTopActivities(

这里,activity将被启动。

相关推荐
似霰22 分钟前
安卓adb shell串口基础指令
android·adb
fatiaozhang95272 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO4 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫4 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白4 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong6 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519877 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111227 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发