1、知识前言
刚入门的同学或许猜测分屏的两个应用是在同个Window?同个DecorView? 实则对两个分屏应用的管理即是对两个Task的管理,这里会引出一个概念也就是Android系统的界面结构,由于本文基于Android12进行分析,所以结构图如下
图中你会看到有四个Task,这四个都是Task的根节点,因此我们称之为RootTask,它可以嵌套子Task。
其中Task#4和Task#5下分别持有子Task#24和Task#25的引用,没错!这便是Android分屏情况下Task的结构布局。而#4和#5是在系统启动时便由TaskOrganizerController创建好供 分屏使用的Task,我们暂时称#4为MainRootTask,#5为SideRootTask ,总的来说,在启动分屏时会将两个应用的Task分别ReParent到分屏RootTask中,不同的系统版本或许会有微小差异,但是总的结构体系不会有变化,可以通过dumpsys命令输出
adb
adb shell dumpsys activity activities
启动分屏有两种方式:
第一种是通过Intent启动
第二种则是Launcher程序通过调用startTasksWithLegacyTransition(int taskId,int taskId)启动
接下来我将解释这两种方式启动分屏的流程
2、通过Intent启动
启动代码如下
java
Intent mainIntent = new Intent(context, MainActivity.class);
mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent sideIntent = new Intent(context, SideActivity.class);
sideIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
context.startActivities(new Intent[]{mainIntent, sideIntent});
2.1、startActivityInner(MainRootTask)
提交完Intent后,都是给ActivityStarterController去处理的,所以我们需要从ActivityStarterController.startActivities()起关注,最终发现交由ActivityStarter.startActivityInner执行请求,核心代码如下:
java
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment, boolean restrictedBgActivity,
NeededUriGrants intentGrants) {
......
//初始化sourceRooTask 如果是mCreateByOrganizer 则sourceRooTask为其新task的parent
//即:分屏应用再次启动新应用,新应用默认是会reparent到分屏的rootTask下,这部分的代码在Task.validateRootTask下
computeSourceRootTask();
......
final Task reusedTask = getReusableTask();//获取可重用的task(给app的task),首次打开app都是为null
......
//如果reusedTask为空,这里会去调用computeTargetTask
//如果带有FLAG_ACTIVITY_NEW_TASK该方法必然返回null,表示新建一个TASK
final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
final boolean newTask = targetTask == null;//
mTargetTask = targetTask;
......
final ActivityRecord targetTaskTop = newTask
? null : targetTask.getTopNonFinishingActivity();
if (targetTaskTop != null) {
startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
......
}
......
if (mTargetRootTask == null) {
//新应用task创建的地方,每次启动新应用都会进入到该逻辑。后面会继续延伸解读该方法
mTargetRootTask = getLaunchRootTask(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");
}
......
mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);
return START_SUCCESS;
}
2.2、getLaunchRootTask
无论分屏启动还是全屏启动,每次启动新应用都会进入到该逻辑
arduino
private Task getLaunchRootTask(ActivityRecord r, int launchFlags, Task task,
ActivityOptions aOptions) {
......
return mRootWindowContainer.getLaunchRootTask(r, aOptions, task, mSourceRootTask, onTop,mLaunchParams, launchFlags, mRequest.realCallingPid, mRequest.realCallingUid);
}
less
Task getLaunchRootTask(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable Task candidateTask,
@Nullable Task sourceTask, boolean onTop,
@Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags,
int realCallingPid, int realCallingUid) {
int taskId = INVALID_TASK_ID;
int displayId = INVALID_DISPLAY;
TaskDisplayArea taskDisplayArea = null;
......
// 1、获取taskDisplayArea
// Next preference for root task goes to the taskDisplayArea candidate.
if (launchParams != null && launchParams.mPreferredTaskDisplayArea != null) {
taskDisplayArea = launchParams.mPreferredTaskDisplayArea;
}
//候选项
if (taskDisplayArea == null && displayId != INVALID_DISPLAY) {
final DisplayContent displayContent = getDisplayContent(displayId);
if (displayContent != null) {
taskDisplayArea = displayContent.getDefaultTaskDisplayArea();
}
}
//2、获取rootTask
if (taskDisplayArea != null) {
......
// 获取DefaultTaskDisplayArea
taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
//获取或创建Task,不为空则直接return,新应用都是通过taskDisplayArea去创建的task
rootTask = taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
sourceTask, launchParams, launchFlags, activityType, onTop);
if (rootTask != null) {
return rootTask;
}
}
......
}
看一下taskDisplayArea.getOrCreateRootTask
2.2.1、taskDisplayArea.getOrCreateRootTask
less
Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
@Nullable Task candidateTask, @Nullable Task sourceTask,
@Nullable ActivityOptions options, int launchFlags) {
......
新task都是在此处build
return new Task.Builder(mAtmService)
.setWindowingMode(windowingMode)
.setActivityType(activityType)
.setOnTop(onTop)
.setParent(this)
.setSourceTask(sourceTask)
.setActivityOptions(options)
.setLaunchFlags(launchFlags)
.build();
}
到此,我们完成了新task的创建,即结构图中Task#24的创建,然后我们再看setNewTask方法
2.3、setNewTask
java
private void setNewTask(Task taskToAffiliate) {
final Task task = mTargetRootTask.reuseOrCreateTask(
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
task.mTransitionController.collectExistenceChange(task);
//代码就不展示了,该方法会将要启动的ActivityRecord ReParent到Task#24下
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
......
}
到这一步#24的Task便创建完成了,或许你想问,这也没涉及到#24 reparent到#4啊??别急,我们不是启动了两个Activity吗?我们从第二个分屏应用中找答案!这部分逻辑与MainRootTask大致相同,如果上述逻辑已透彻了解可以从下述2.4.1、taskDisplayArea.getOrCreateRootTask看起
2.3.1、startActivityInner(SideRootTask)
提交完Intent后,都是给ActivityStarterController去处理的,所以我们需要从ActivityStarterController.startActivities()起关注,最终发现交由ActivityStarter.startActivityInner执行请求,核心代码如下:(与MainRootTask相同)
java
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment, boolean restrictedBgActivity,
NeededUriGrants intentGrants) {
......
//初始化sourceRooTask 如果是mCreateByOrganizer 则sourceRooTask为其新task的parent
//即:分屏应用再次启动新应用,新应用默认是会reparent到分屏的rootTask下,这部分的代码在Task.validateRootTask下
computeSourceRootTask();
......
final Task reusedTask = getReusableTask();//获取可重用的task(给app的task),首次打开app都是为null
......
//如果reusedTask为空,这里会去调用computeTargetTask
//如果带有FLAG_ACTIVITY_NEW_TASK该方法必然返回null,表示新建一个TASK
final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
final boolean newTask = targetTask == null;//
mTargetTask = targetTask;
......
final ActivityRecord targetTaskTop = newTask
? null : targetTask.getTopNonFinishingActivity();
if (targetTaskTop != null) {
//会给mTargetRootTask赋值,同时如果是分屏flag就给其reparent,后面会继续延伸解读该方法
startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
......
}
......
if (mTargetRootTask == null) {
//新应用task创建的地方,每次启动新应用都会进入到该逻辑。后面会继续延伸解读该方法
mTargetRootTask = getLaunchRootTask(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");
}
......
mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);
return START_SUCCESS;
}
2.4、getLaunchRootTask
无论分屏启动还是全屏启动,每次启动新应用都会进入到该逻辑 (与MainRootTask相同)
arduino
private Task getLaunchRootTask(ActivityRecord r, int launchFlags, Task task,
ActivityOptions aOptions) {
......
return mRootWindowContainer.getLaunchRootTask(r, aOptions, task, mSourceRootTask, onTop,mLaunchParams, launchFlags, mRequest.realCallingPid, mRequest.realCallingUid);
}
less
Task getLaunchRootTask(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable Task candidateTask,
@Nullable Task sourceTask, boolean onTop,
@Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags,
int realCallingPid, int realCallingUid) {
int taskId = INVALID_TASK_ID;
int displayId = INVALID_DISPLAY;
TaskDisplayArea taskDisplayArea = null;
......
// 1、获取taskDisplayArea
// Next preference for root task goes to the taskDisplayArea candidate.
if (launchParams != null && launchParams.mPreferredTaskDisplayArea != null) {
taskDisplayArea = launchParams.mPreferredTaskDisplayArea;
}
//候选项
if (taskDisplayArea == null && displayId != INVALID_DISPLAY) {
final DisplayContent displayContent = getDisplayContent(displayId);
if (displayContent != null) {
taskDisplayArea = displayContent.getDefaultTaskDisplayArea();
}
}
//2、获取rootTask
if (taskDisplayArea != null) {
......
// 获取DefaultTaskDisplayArea
taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
//获取或创建Task,不为空则直接return,新应用都是通过taskDisplayArea去创建的task
rootTask = taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
sourceTask, launchParams, launchFlags, activityType, onTop);
if (rootTask != null) {
return rootTask;
}
}
......
}
2.4.1、taskDisplayArea.getOrCreateRootTask
less
Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
@Nullable Task candidateTask, @Nullable Task sourceTask,
@Nullable ActivityOptions options, int launchFlags) {
......
新task都是在此处build
return new Task.Builder(mAtmService)
.setWindowingMode(windowingMode)
.setActivityType(activityType)
.setOnTop(onTop)
.setParent(this)//此时的parent是TaskDisplayArea
.setSourceTask(sourceTask)
.setActivityOptions(options)
.setLaunchFlags(launchFlags)
.build();
}
接下来我们看一下Build的内部逻辑
2.4.1.1、Task.build
scss
Task build() {
if (mParent != null && mParent instanceof TaskDisplayArea) {
validateRootTask((TaskDisplayArea) mParent); 核心方法
}
.....
}
2.4.1.1.1、Task.validateRootTask
java
private void validateRootTask(TaskDisplayArea tda) {
......
//mCreatedByOrganizer这个属性是判断这个Task是否是TaskOrganizerController创建的,如果是它创建的一般都是rootTask,需要特殊处理
//这里Task#25肯定是false,因此调用tda.getLaunchRootTask
final Task launchRootTask = mCreatedByOrganizer ? null :tda.getLaunchRootTask(mWindowingMode, mActivityType, mActivityOptions,
mSourceTask, mLaunchFlags);
if (launchRootTask != null) {
mParent = launchRootTask;
}
}
其实MainRootTask也会调用到这一步,只不过tda.getLaunchRootTask返回为null。那么为什么SideRootTask有值呢,我们具体看看getLaunchRootTask
2.4.1TaskDisplayArea.getLaunchRootTask
less
@Nullable
Task getLaunchRootTask(int windowingMode, int activityType, @Nullable ActivityOptions options,
@Nullable Task sourceTask, int launchFlags) {
......
//这里判断是否有FLAG_ACTIVITY_LAUNCH_ADJACENT
if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& mLaunchAdjacentFlagRootTask != null) {
//因为sourceTask.getRootTask().mTaskId == mLaunchAdjacentFlagRootTask.mTaskId为false
//所以return mLaunchAdjacentFlagRootTask,它就是SideRootTask
if (sourceTask != null
&& sourceTask.getRootTask().mTaskId == mLaunchAdjacentFlagRootTask.mTaskId
&& mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null) {
return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
} else {
return mLaunchAdjacentFlagRootTask;
}
}
//与上述startActivityInner.computeSourceRootTask对应,如果rootTask是分屏的rootTask那么返回它
if (sourceTask != null && sourceTask.getRootTask().mCreatedByOrganizer) {
return sourceTask.getRootTask();
}
return null;
}
看到这或许大家应该就理解了,我们在开头通过Intent启动时setFlag为FLAG_ACTIVITY_LAUNCH_ADJACENT,此刻派上了用场。我们将mLaunchAdjacentFlagRootTask返回出去,而它正好就是SideRootTask,对应Task#5
Tips:mLaunchAdjacentFlagRootTask在系统开始时就创建好了,因为它是TaskOrganizerController创建的,所以mCreatedByOrganizer为true
回到 2.4.1TaskDisplayArea.getLaunchRootTask,将build出的task其parent设为mLaunchAdjacentFlagRootTask,Task#25便reparent到SideRootTask#5下了,那么Task#24什么时候reparent到MainRootTask#4下呢?
2.5、思考点
可以通过FLAG_ACTIVITY_LAUNCH_ADJACENT判断要打开的应用需要reparent到sideRootTask,那么系统如何判断要打开的应用是否需要reParent到mainRootTask呢?或许你会蹦出一个想法,仿照side咱们也制定一个flag,需要启动分屏时,让Task#24直接reparent到mainRootTask#4上不就好了?
然而谷歌的同学采用另一个思路,监听到sideRootTask启动后把sideRootTask#24reparent到mainRootTask#4
我们先回到起点ActivityStarterController.startActivities() startActivities方法的finally块中会执行 ActivityTaskManagerService.continueWindowLayout();
ok!我们继续出发
2.6、ActivityTaskManagerService.continueWindowLayout
该方法调用链比较长,所以就不一一贴代码了,展示一下时序
是不是很长!!!! 我们只要关注最后一步的TaskOrganizerCallbacks.onTaskInfoChanged就好
2.6.1、TaskOrganizerCallbacks.onTaskInfoChanged
javascript
void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) {
try {
mTaskOrganizer.onTaskInfoChanged(taskInfo);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskInfoChanged callback", e);
}
}
这里的代码很简单,就是通过binder接口回调至binder客户端,那么这里的客户端是谁呢?那就是SystemUI进程中的ShellTaskOrganizer,这里简单介绍一下它,ShellTaskOrganizer是TaskOrganizer的子类,创建side和main的rootTask都是通过调用它的方法,可以说它就是systemUI进程和ATMS沟通的桥梁,通过匿名binder进行沟通。
2.6.2、ShellTaskOrganizer.onTaskInfoChanged
java
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
final TaskListener oldListener = getTaskListener(data.getTaskInfo());
final TaskListener newListener = getTaskListener(taskInfo);
mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
final boolean updated = updateTaskListenerIfNeeded(
taskInfo, data.getLeash(), oldListener, newListener);
if (!updated && newListener != null) {
newListener.onTaskInfoChanged(taskInfo);
}
......
}
}
这里会取出接口回调至外部,这个外部是哪呢?没错就是TaskStateListener,就是MainStage和SideStage 的父类,这两个是在Stagecoordator中进行初始化的,onTaskInfoChanged也是回调到此,具体的类依赖关系如下图
接下来我们看一下处理回调的地方
2.6.3、StageListenerImpl.onStatusChanged
kotlin
@Override
public void onStatusChanged(boolean visible, boolean hasChildren) {
if (!mHasRootTask) return;
if (mHasChildren != hasChildren) {
mHasChildren = hasChildren;
//走这
StageCoordinator.this.onStageHasChildrenChanged(this);
}
if (mVisible != visible) {
mVisible = visible;
StageCoordinator.this.onStageVisibilityChanged(this);
}
}
java
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
if (!hasChildren) {
......
} else if (isSideStage) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make sure the main stage is active.
mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);
mSideStage.moveToTop(getSideStageBounds(), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
}
......
}
}
WindowContainerTransaction其实就是对Task的修改项进行封装,而后通过runInSync提交至WindowOrganizerController 我们先看MainStage.activate
2.6.3.1、MainStage.activate
java
void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) {
if (mIsActive) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
//修改task的bounds
wct.setBounds(rootToken, rootBounds)
.reorder(rootToken, true /* onTop */);
if (includingTopTask) {
重新reparent,currentParent为null 表示使用TDA
wct.reparentTasks(
null /* currentParent */,
rootToken,
CONTROLLED_WINDOWING_MODES,
CONTROLLED_ACTIVITY_TYPES,
true /* onTop */,
true /* reparentTopOnly */);
}
}
这里就是发起MainRootTask#4 reparent请求的地方,并设置了bounds 会有同学问。这里的rootToken是啥?其实rootToken就是MainRootTask#4 日常存在MainStage下,WindowOrganizerController收到后 会转成windowcainer,也就是Task的父类去做reparent处理
2.6.3.2、mSideStage.moveToTop
java
void moveToTop(Rect rootBounds, WindowContainerTransaction wct) {
final WindowContainerToken rootToken = mRootTaskInfo.token;
wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
}
设置bounds并将其移至台前(onTop为true)
2.6.3.3、mSyncQueue.queue(wct)
scss
public void queue(WindowContainerTransaction wct) {
.........
synchronized (mQueue) {
if (DEBUG) Slog.d(TAG, "Queueing up " + wct);
mQueue.add(cb);
if (mQueue.size() == 1) {
cb.send();
}
}
}
scss
void send() {
......
mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
入队并发送给WindowOrganizerController.applySyncTransaction,最终调用到applyTransaction
2.7、WindowOrganizerController.applyTransaction
less
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition, @NonNull CallerInfo caller) {
..........
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
......处理change修改项
}
// Hierarchy changes
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
if (hopSize > 0) {
final boolean isInLockTaskMode = mService.isInLockTaskMode();
for (int i = 0; i < hopSize; ++i) {
//处理change修改项
effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,
isInLockTaskMode, caller, t.getErrorCallbackToken(),
t.getTaskFragmentOrganizer());
}
}
}
2.7.1、WindowOrganizerController.applyHierarchyOp
通过type的方式定位到要reparent,而后调用reparentChildrenTasksHierarchyOp
吐槽一下,这里为为什么要用两个switch???
2.7.1.1、WindowOrganizerController.reparentChildrenTasksHierarchyOp
java
private int reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop,
@Nullable Transition transition, int syncId) {
WindowContainer currentParent = hop.getContainer() != null
? WindowContainer.fromBinder(hop.getContainer()) : null;
WindowContainer newParent = hop.getNewParent() != null
? WindowContainer.fromBinder(hop.getNewParent()) : null;
if (currentParent == null && newParent == null) {
throw new IllegalArgumentException("reparentChildrenTasksHierarchyOp: " + hop);
} else if (currentParent == null) {
currentParent = newParent.asTask().getDisplayContent().getDefaultTaskDisplayArea();
} else if (newParent == null) {
newParent = currentParent.asTask().getDisplayContent().getDefaultTaskDisplayArea();
}
//如果currentParent/newParent为空 则使用DefaultTaskDisplayArea,退出分屏就利用了这个特性
.....
//开始找到合适的task
final ArrayList<Task> tasksToReparent = new ArrayList<>();
currentParent.forAllTasks((Function<Task, Boolean>) task -> {
Slog.i(TAG, " Processing task=" + task);
final boolean reparent;
////mCreatedByOrganizer创建 或者 task的父容器不是tda 则跳过
if (task.mCreatedByOrganizer || task.getParent() != finalCurrentParent) {
// We only care about non-organized task that are direct children of the thing we
// are reparenting from.
return false;
}
//不支持多窗口 则跳过
if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) {
Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window,"
+ " task=" + task);
return false;
}
//hop中不包含的type和mode
if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())
|| !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
return false;
}
//找到对应的task 添加到列表中
if (hop.getToTop()) {
tasksToReparent.add(0, task);
} else {
tasksToReparent.add(task);
}
return hop.getReparentTopOnly() && tasksToReparent.size() == 1;
});
final int count = tasksToReparent.size();
for (int i = 0; i < count; ++i) {
........
//区分tda和roottask的情况,这里执行的是else
if (newParent instanceof TaskDisplayArea) {
// For now, reparenting to display area is different from other reparents...
task.reparent((TaskDisplayArea) newParent, hop.getToTop());
} else {
task.reparent((Task) newParent,
hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
}
}
return TRANSACT_EFFECTS_LIFECYCLE;
}
这部分代码分三步,
1、重置currentParent和newParent的值
2、从currentParent(null的话则是TDA)遍历所有task,找出结构图中的Task#24,forAllTasks表示倒叙遍历,过滤掉mCreatedByOrganizer&不支持分屏&activity类型和windowMode不匹配后 就加入到要reparent的队列中,然后直接return。
这块逻辑有点难以自洽,像是谷歌同学开发的半成品,
定义了reparent变量又不去使用?
只找到一个就返回那定义数组队列的意义在哪?
倒叙遍历TDA就一定能找出对应的Task#24吗?
翻看了最新代码,在Android15上依然是这套逻辑。如果有同学有不同见解,还请不吝告知!
3、最后一步也就是reparent了,MainRootTask#必然执行的else逻辑,执行了reParent
3、Launcher调用startTasksWithLegacyTransition
因为分屏的应用场景包含从最近任务列表选择两个最近任务而后启动,而这部分的交互是由Launcher程序来做的,故而这部分的代码源头在Launcher中。 具体的方法封装在SystemUIProxy下,代码如下
csharp
/**
* Start multiple tasks in split-screen simultaneously.
*/
public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
sideOptions, sidePosition, splitRatio, adapter);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTasksWithLegacyTransition");
}
}
}
细心的朋友应该一眼就能看出这个是个Binder接口吧?(RemoteException)没错mSplitScreen正是个匿名Binder,与它bind的服务正是SystemUI的OverviewProxyService。当OverviewProxyService初始化的时候,会主动bind到launcher的TouchInteractionService。这块就不延伸了,感兴趣的同学可以自行查看。
less
@Override
public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
splitRatio, adapter));
}
收到调用请求后,交由mStageCoordinator处理
3.1、StageCoordinator.startTasksWithLegacyTransition
less
void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
// 显示分割线
setDividerVisibility(false /* visible */);
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
............略一些动画代码
//设置分割线的比例
mSplitLayout.setDivideRatio(splitRatio);
//判断mMainStage是否激活,已经激活就将其移动到前台,未激活则激活
if (mMainStage.isActive()) {
mMainStage.moveToTop(getMainStageBounds(), wct);
} else {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
}
mSideStage.moveToTop(getSideStageBounds(), wct);
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
addActivityOptions(sideOptions, mSideStage);
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
wct.startTask(sideTaskId, sideOptions);
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
}
这里也是三个步骤 1、初始化分割线 2、配置MainStage和SideStage 3、提交通过ShellTaskOrganizer提交WindowContainerTransaction,wct是封装task调整项的类,封装好调整项会提交到WindowOrganizerController.applyTransaction
其中第2步,这里调用了main.activate,具体代码如下(同2.6.3.1)和side.moveToTop(同2.6.3.2),有些遗忘的同学可以回顾一下~
此时或许会有同学有疑问,为什么side没有activate?这次没有FLAG_ACTIVITY_LAUNCH_ADJACENT这个flag呀?莫慌!这边看一下addActivityOptions、wct.startTask
3.1.1、StageCoordinator.addActivityOptions
typescript
private void addActivityOptions(Bundle opts, StageTaskListener stage) {
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
}
注意:这边把main和side的rootTask都放到了这个Bundle里面了,WindowOrganizerController会解析这个bundle拿到rootToken 重新reparent
3.1.2、wct.startTask
less
public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
return this;
}
java
public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) {
final Bundle fullOptions = options == null ? new Bundle() : options;
fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK)
.setToTop(true)
.setLaunchOptions(fullOptions)
.build();
}
这里封装一个HierarchyOp修改项,表示启动这个task;并将上面封装的bundle设置到这里 此刻SytemUI的分屏启动任务基本完成,我们再回到WindowOrganizerController 看看他的处理。
WindowOrganizerController接收到applyTransaction请求的调用链上面讲过,此刻就不重复描述了,我们直接从applyHierarchyOp 解析和应用Hierarchy看起
3.2、WindowOrganizerController.applyHierarchyOp
3.2.1、WindowOrganizerController.sanitizeAndApplyHierarchyOp
arduino
private int sanitizeAndApplyHierarchyOp(WindowContainer container,
WindowContainerTransaction.HierarchyOp hop) {
.......
//isReparent会判断它提交时的Type是否是HIERARCHY_OP_TYPE_REPARENT,这里肯定是false
//所以执行else分支
if (hop.isReparent()) {
.......
} else {
//这里就是调用其父容器,将其移动到前台
task.getParent().positionChildAt(
hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
task, false /* includingParents */);
}
return TRANSACT_EFFECTS_LIFECYCLE;
}
上面就是对mSideStage.moveToTop的处理,其实就是找到其parent,重新调整task在那内部的位置。 这样它就能现实到前台了
最后我们来看看如何处理wct.startTask,还是一样,找到对应的type然后全局搜索,发现HIERARCHY_OP_TYPE_LAUNCH_TASK就在HIERARCHY_OP_TYPE_LAUNCH_TASK下方,代码如下:
3.2.2、WindowOrganizerController.sanitizeAndApplyHierarchyOp
ini
case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
.....
//还记得上面的setLaunchOptions(fullOptions)吗?此刻用到了
//这里将其转化成ActivityOptions,而ActivityOptions的构造函数就会从bundle中获取到其rootTask
final SafeActivityOptions safeOptions =
SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
final Integer[] starterResult = {null};
.......
mService.mH.post(() -> {
try {
//和后台启动应用的方式一致,都是调用startActivityFromRecents方法
starterResult[0] = mService.mTaskSupervisor.startActivityFromRecents(
caller.mPid, caller.mUid, taskId, safeOptions);
} catch (Throwable t) {
.....
}
.....
});
.....
break;
}
和后台启动应用的方式一致,都是最终调用startActivityFromRecents方法 最后我们看看startActivityFromRecents内部做了什么?
3.2.2.1、ActivityTaskSupervisor
java
int startActivityFromRecents(int callingPid, int callingUid, int taskId,
SafeActivityOptions options) {
final Task task;
final int taskCallingUid;
final String callingPackage;
final String callingFeatureId;
final Intent intent;
final int userId;
final ActivityOptions activityOptions = options != null
? options.getOptions(this)
: null;
.....
try {
//获取rootTask
task = mRootWindowContainer.anyTaskForId(taskId,
MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
.......
try {
//移动到前台
mService.moveTaskToFrontLocked(null /* appThread */,
null /* callingPackage */, task.mTaskId, 0, options);
targetActivity.applyOptionsAnimation();
} finally {
mActivityMetricsLogger.notifyActivityLaunched(launchingState,
START_TASK_TO_FRONT, false /* newActivityCreated */,
targetActivity, activityOptions);
}
.........
try {
return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
null, 0, 0, options, userId, task, "startActivityFromRecents",
false /* validateIncomingUser */, null /* originatingPendingIntent */,
false /* allowBackgroundActivityStart */);
} finally {
synchronized (mService.mGlobalLock) {
mService.continueWindowLayout();
}
}
}
这里会调用mRootWindowContainer.anyTaskForId获取到rootTask并reparent,代码如下 mRootWindowContainer
less
Task anyTaskForId(int id, @RootWindowContainer.AnyTaskForIdMatchTaskMode int matchMode,
@Nullable ActivityOptions aOptions, boolean onTop) {
......
Task task = getTask(p);
......
if (task != null) {
if (aOptions != null) {
//这里最终就是调用到rootWindowContainer.getLaunchRootTask方法
//rootWindowContainer最终从aOptions取出rootTask
final Task launchRootTask =
getLaunchRootTask(null, aOptions, task, onTop);
if (launchRootTask != null && task.getRootTask() != launchRootTask) {
final int reparentMode = onTop
? REPARENT_MOVE_ROOT_TASK_TO_FRONT : REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
//取出rootTask后立刻reparent,而后直接return
task.reparent(launchRootTask, onTop, reparentMode, ANIMATE, DEFER_RESUME,
"anyTaskForId");
}
}
return task;
}
}
到此 从launcher中启动分屏的流程就解析完毕了
4、总结
总的来说,对分屏的启动,其实就是对Task的管理,在系统初始化时,SystemUI创建好分屏和画中画(PIP)的rootTask,需要进入分屏时将进入分屏应用的Task reparent到分屏的rootTask下,然后moveToTop ,同理退出分屏也就是将task重新reparent到TaskDisplayArea下
在这个过程中也不乏一些有趣的知识点,SystemUI主动bind到Launcher,客户端通过对WCT的封装和提交来修改Task,Intent通过监听的方式启动分屏,而不是选择重新制定一个Flag,以及谷歌同学的"迷之代码",或许是在下的水平不够,难以理解。后续会尝试新增一个分配rootTask,实现三分屏应用到真实车机上,届时也会同步更新博客!
bye~