简介
本文主要描述在分屏流程中常出现的方法,如果有没有提及的常用方法,请参考分屏系列相关文章。
WMShell相关的dump命令
手机分屏启动应用后运行命令:adb shell dumpsys activity service SystemUIService WMShell
我们可以找到其中分屏的部分,如下图所示:
分屏的组成
上下分屏简图
分屏是由上分屏(SideStage)、下分屏(MainStage)以及分割线组成。这里我们主要关注分屏的Stage部分,如下图所示:
我们这里上分屏是电话,下分屏是短信。
通过adb shell dumpsys activity containers命令可以看层级结构,这里我们看看上下分屏指的是什么
这里
Task=331其实就是分屏的RootTask,通过wct.reorder(mRootTaskInfo.token, true);(在system_server进程中)设置的,使其显示到最前面。其下面挂着Task=332(MainStage,下分屏)和Task=333(SideStage,上分屏),这段代码也就是为了把这两个task下面挂上对应应用的task,即Task=333(SideStage,上分屏)下面挂着应用task=335(电话Task),Task=332(MainStage,下分屏)下面挂着应用task=334(短信Task)
总之,我们需要分清楚分屏的task和应用的task,不要弄混淆。
注:在android T(13) 中,上分屏为MainStage,下分屏为SideStage
分屏task的创建与stage的挂载
下图为系统刚启动时的层级结构树
分屏的布局会在系统启动之后预留相关task。即图中
task=13,因此会触发TaskOrganizer.onTaskAppeared调用到分屏的StageCoordinator.onTaskAppeared方法。而图中的task=15(SideStage,上分屏)和task=14(MainStage,下分屏),这个两个stage正是在StageCoordinator.onTaskAppeared方法中创建的。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mRootTaskInfo != null || taskInfo.hasParentTask()) {
throw new IllegalArgumentException(this + "\n Unknown task appeared: " + taskInfo);
}
mRootTaskInfo = taskInfo;
mRootTaskLeash = leash;
//如果mSplitLayout为空,则初始化分屏布局
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
mDisplayController, mDisplayImeController, mTaskOrganizer,
PARALLAX_ALIGN_CENTER /* parallaxType */);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
//挂载对应stage到分屏task
onRootTaskAppeared();
}
这里mRootTaskInfo就是对应图中task=13,我们再看看onRootTaskAppeared();,该方法主要就是挂载对应stage到分屏task。
void onRootTaskAppeared() {
// Wait unit all root tasks appeared.
if (mRootTaskInfo == null
|| !mMainStageListener.mHasRootTask
|| !mSideStageListener.mHasRootTask) {
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
这里我们主要看这两句
wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
mMainStage.mRootTaskInfo.token就是task=14(MainStage,下分屏)
mSideStage.mRootTaskInfo.token就是task=15(SideStage,上分屏)
把MainStage和SideStage挂载到分屏task上,这里就是我们看到图中task=13下挂载着task=15(SideStage,上分屏)和task=14(MainStage,下分屏)的原因。
分屏的StageCoordinator.onTaskAppeared方法一般情况只会调用一次,除非上图中的分屏Task没了,需要重新创建。
我们分屏之后就加载对应的应用task到了分屏task下面,如图:
stage的创建
在StageCoordinator构造方法中创建了MainStage和SideStage。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider, ShellExecutor mainExecutor,
Optional<RecentTasksController> recentTasks) {
......
mMainStage = new MainStage(
mContext,
mTaskOrganizer,
mDisplayId,
mMainStageListener,
mSyncQueue,
mSurfaceSession,
iconProvider);
mSideStage = new SideStage(
mContext,
mTaskOrganizer,
mDisplayId,
mSideStageListener,
mSyncQueue,
mSurfaceSession,
iconProvider);
......
}
MainStage 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
class MainStage extends StageTaskListener {
......
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession, IconProvider iconProvider) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
iconProvider);
}
SideStage 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
class SideStage extends StageTaskListener {
......
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession, IconProvider iconProvider) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
iconProvider);
}
这里我们可以看到MainStage和SideStage的构造方法都调用其父类构造方法,而他们的父类都是StageTaskListener,所以我们只需要关注StageTaskListener构造方法即可。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession, IconProvider iconProvider) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
这里我们可以看到最关键的创建方法就是createRootTask,传递了当前Display(displayId)、当前窗口模式(WINDOWING_MODE_MULTI_WINDOW)和当前Stage(this)。
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
/**
* Creates a persistent root task in WM for a particular windowing-mode.
* @param displayId The display to create the root task on.
* @param windowingMode Windowing mode to put the root task in.
* @param listener The listener to get the created task callback.
*/
public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
createRootTask(displayId, windowingMode, listener, false /* removeWithTaskOrganizer */);
}
/**
* Creates a persistent root task in WM for a particular windowing-mode.
* @param displayId The display to create the root task on.
* @param windowingMode Windowing mode to put the root task in.
* @param listener The listener to get the created task callback.
* @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
*/
public void createRootTask(int displayId, int windowingMode, TaskListener listener,
boolean removeWithTaskOrganizer) {
ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s" ,
displayId, windowingMode, listener.toString());
final IBinder cookie = new Binder();
setPendingLaunchCookieListener(cookie, listener);
super.createRootTask(displayId, windowingMode, cookie, removeWithTaskOrganizer);
}
设置了removeWithTaskOrganizer参数为false,继续传递参数调用到其父类TaskOrganizer的createRootTask方法
代码路径:frameworks/base/core/java/android/window/TaskOrganizer.java
/**
* Creates a persistent root task in WM for a particular windowing-mode.
* @param displayId The display to create the root task on.
* @param windowingMode Windowing mode to put the root task in.
* @param launchCookie Launch cookie to associate with the task so that is can be identified
* when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
* @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
boolean removeWithTaskOrganizer) {
try {
mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie,
removeWithTaskOrganizer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
这里mTaskOrganizerController是ITaskOrganizerController对象,通过跨进程到system_server侧创建Task,其实现方法在TaskOrganizerController中。
代码路径:frameworks/base/services/core/java/com/android/server/wm/TaskOrganizerController.java
@Override
public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
boolean removeWithTaskOrganizer) {
enforceTaskPermission("createRootTask()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
//获取当前DisplayContent
DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId);
if (display == null) {
ProtoLog.e(WM_DEBUG_WINDOW_ORGANIZER,
"createRootTask unknown displayId=%d", displayId);
return;
}
createRootTask(display, windowingMode, launchCookie, removeWithTaskOrganizer);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
@VisibleForTesting
Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) {
return createRootTask(display, windowingMode, launchCookie,
false /* removeWithTaskOrganizer */);
}
Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie,
boolean removeWithTaskOrganizer) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
display.mDisplayId, windowingMode);
// We want to defer the task appear signal until the task is fully created and attached to
// to the hierarchy so that the complete starting configuration is in the task info we send
// over to the organizer.
//创建Task
final Task task = new Task.Builder(mService)
.setWindowingMode(windowingMode)
.setIntent(new Intent())
.setCreatedByOrganizer(true)
.setDeferTaskAppear(true)
.setLaunchCookie(launchCookie)
.setParent(display.getDefaultTaskDisplayArea())
.setRemoveWithTaskOrganizer(removeWithTaskOrganizer)
.build();
task.setDeferTaskAppear(false /* deferTaskAppear */);
return task;
}
这个方法很简单,就是获取了当前DisplayContent,然后创建了Task。这里还设置setCreatedByOrganizer(true),表示是通过TaskOrganizer的方式创建的。
分屏surface创建相关
surface分析
使用命令查看对应的层级结构 adb shell dumpsys activity containers
这里
Task=3为分屏的总Task,Task=5和Task=4分别是上分屏和下分屏的Task。
使用命令查看对应的Surface adb shell dumpsys SurfaceFlinger
从Surface中找到了分割线的图层,通过搜索该图层分析其层级结构
在图层中显示的分割线为
StageCoordinatorSplitDivider其父图层为SplitWindowManager,也是分屏侧实际操作的图层。
这里我们可以看到SplitWindowManager挂载在Task=3(分屏的总Task)下面。
分屏分割线surface创建堆栈
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@Override
protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) {
// Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName(TAG)
.setHidden(true)
.setCallsite("SplitWindowManager#attachToParentSurface");
mParentContainerCallbacks.attachToParentSurface(builder);
mLeash = builder.build();
mParentContainerCallbacks.onLeashReady(mLeash);
android.util.Log.i("SplitWindowManager","getParentSurface() mLeash:"+mLeash.toString(),new Exception());
return mLeash;
}
该方法重写WindowlessWindowManager.getParentSurface方法,并在WindowlessWindowManager.addToDisplay中调用。
代码路径:frameworks/base/core/java/android/view/WindowlessWindowManager.java
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
final SurfaceControl leash = new SurfaceControl.Builder(mSurfaceSession)
.setName(attrs.getTitle().toString() + "Leash")
.setCallsite("WindowlessWindowManager.addToDisplay")
.setParent(getParentSurface(window, attrs))
.build();
final SurfaceControl sc = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
.setBLASTLayer()
.setName(attrs.getTitle().toString())
.setCallsite("WindowlessWindowManager.addToDisplay")
.setHidden(false)
.setParent(leash)
.build();
这里我们可以看到sc的父图层为leash。
堆栈如下:
SplitWindowManager: getParentSurface() mLeash:Surface(name=SplitWindowManager)/@0x8fe904e
SplitWindowManager: java.lang.Exception
SplitWindowManager: at com.android.wm.shell.common.split.SplitWindowManager.getParentSurface(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:108)
WindowlessWindowManager: addToDisplay() leash:Surface(name=StageCoordinatorSplitDividerLeash)/@0x470327c sc:Surface(name=StageCoordinatorSplitDivider)/@0xb0f7705
WindowlessWindowManager: at android.view.WindowlessWindowManager.addToDisplay(WindowlessWindowManager.java:197)
WindowlessWindowManager: at android.view.WindowlessWindowManager.addToDisplayAsUser(WindowlessWindowManager.java:262)
SplitWindowManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:1340)
SplitWindowManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:1215)
SplitWindowManager: at android.view.SurfaceControlViewHost.setView(SurfaceControlViewHost.java:443)
SplitWindowManager: at com.android.wm.shell.common.split.SplitWindowManager.init(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:135)
SplitWindowManager: at com.android.wm.shell.common.split.SplitLayout.init(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:411)
SplitWindowManager: at com.android.wm.shell.common.split.SplitLayout.update(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:435)
SplitWindowManager: at com.android.wm.shell.splitscreen.StageCoordinator.finishEnterSplitScreen(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:1608)
SplitWindowManager: at com.android.wm.shell.splitscreen.StageCoordinator.startPendingEnterAnimation(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:2854)
SplitWindowManager: at com.android.wm.shell.splitscreen.StageCoordinator.startPendingAnimation(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:2696)
SplitWindowManager: at com.android.wm.shell.splitscreen.StageCoordinator.startAnimation(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:2632)
SplitWindowManager: at com.android.wm.shell.transition.Transitions.playTransition(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:885)
SplitWindowManager: at com.android.wm.shell.transition.Transitions.processReadyQueue(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:819)
SplitWindowManager: at com.android.wm.shell.transition.Transitions.dispatchReady(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:760)
SplitWindowManager: at com.android.wm.shell.transition.Transitions.onTransitionReady(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:669)
SplitWindowManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.lambda$onTransitionReady$0(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:1353)
SplitWindowManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.$r8$lambda$qsRfWn1ItrZqnFeABBdxU50jPc4(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:0)
SplitWindowManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl$$ExternalSyntheticLambda0.run(go/retraceme 0d9615079747c5776f764e530e7c5bffe361ce574e8eadd276d93db88b4b553a:0)
SplitWindowManager: at android.os.Handler.handleCallback(Handler.java:958)
SplitWindowManager: at android.os.Handler.dispatchMessage(Handler.java:99)
SplitWindowManager: at android.os.Looper.loopOnce(Looper.java:205)
SplitWindowManager: at android.os.Looper.loop(Looper.java:294)
SplitWindowManager: at android.os.HandlerThread.run(HandlerThread.java:67)
进入分屏后通过SplitWindowManager.init初始化,后续在system_server侧的WindowlessWindowManager.addToDisplay创建StageCoordinatorSplitDivider以及其父Surface StageCoordinatorSplitDividerLeash,并调用SplitWindowManager.getParentSurface创建SplitWindowManager。
上下分屏surface创建堆栈
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@Override
protected SurfaceControl getParentSurface(IWindow window, WindowManager.LayoutParams attrs) {
// Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName(TAG)
.setHidden(true)
.setParent(mHostLeash)
.setCallsite("SplitDecorManager#attachToParentSurface");
mIconLeash = builder.build();
android.util.Log.i("SplitDecorManager","getParentSurface() mIconLeash:"+mIconLeash+" mHostLeash:"+mHostLeash,new Exception());
return mIconLeash;
}
mHostLeash其实就是对应的分屏Task的Surface。
该方法重写WindowlessWindowManager.getParentSurface方法,并在WindowlessWindowManager.addToDisplay中调用。
代码路径:frameworks/base/core/java/android/view/WindowlessWindowManager.java
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
final SurfaceControl leash = new SurfaceControl.Builder(mSurfaceSession)
.setName(attrs.getTitle().toString() + "Leash")
.setCallsite("WindowlessWindowManager.addToDisplay")
.setParent(getParentSurface(window, attrs))
.build();
final SurfaceControl sc = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
.setBLASTLayer()
.setName(attrs.getTitle().toString())
.setCallsite("WindowlessWindowManager.addToDisplay")
.setHidden(false)
.setParent(leash)
.build();
这里我们可以看到sc的父图层为leash。
堆栈如下:
SplitDecorManager: getParentSurface() mIconLeash:Surface(name=SplitDecorManager)/@0x47519da mHostLeash:Surface(name=Task=4)/@0x5c8746a
SplitDecorManager: java.lang.Exception
SplitDecorManager: at com.android.wm.shell.common.split.SplitDecorManager.getParentSurface(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:109)
WindowlessWindowManager: addToDisplay() leash:Surface(name=SplitDecorManagerLeash)/@0x964d4e8 sc:Surface(name=SplitDecorManager)/@0xc3d8701
WindowlessWindowManager: at android.view.WindowlessWindowManager.addToDisplay(WindowlessWindowManager.java:197)
WindowlessWindowManager: at android.view.WindowlessWindowManager.addToDisplayAsUser(WindowlessWindowManager.java:262)
SplitDecorManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:1340)
SplitDecorManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:1215)
SplitDecorManager: at android.view.SurfaceControlViewHost.setView(SurfaceControlViewHost.java:443)
SplitDecorManager: at com.android.wm.shell.common.split.SplitDecorManager.inflate(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:140)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.finishEnterSplitScreen(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:1609)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.startPendingEnterAnimation(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:2854)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.startPendingAnimation(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:2696)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.startAnimation(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:2632)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.playTransition(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:885)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.processReadyQueue(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:819)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.dispatchReady(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:760)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.onTransitionReady(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:669)
SplitDecorManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.lambda$onTransitionReady$0(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:1353)
SplitDecorManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.$r8$lambda$qsRfWn1ItrZqnFeABBdxU50jPc4(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:0)
SplitDecorManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl$$ExternalSyntheticLambda0.run(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:0)
SplitDecorManager: at android.os.Handler.handleCallback(Handler.java:958)
SplitDecorManager: at android.os.Handler.dispatchMessage(Handler.java:99)
SplitDecorManager: at android.os.Looper.loopOnce(Looper.java:205)
SplitDecorManager: at android.os.Looper.loop(Looper.java:294)
SplitDecorManager: at android.os.HandlerThread.run(HandlerThread.java:67)
SplitDecorManager: getParentSurface() mIconLeash:Surface(name=SplitDecorManager)/@0xcdb5900 mHostLeash:Surface(name=Task=5)/@0x82f99f8
SplitDecorManager: java.lang.Exception
SplitDecorManager: at com.android.wm.shell.common.split.SplitDecorManager.getParentSurface(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:109)
WindowlessWindowManager: addToDisplay() leash:Surface(name=SplitDecorManagerLeash)/@0xf5d977e sc:Surface(name=SplitDecorManager)/@0x87614df
WindowlessWindowManager: at android.view.WindowlessWindowManager.addToDisplay(WindowlessWindowManager.java:197)
WindowlessWindowManager: at android.view.WindowlessWindowManager.addToDisplayAsUser(WindowlessWindowManager.java:262)
SplitDecorManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:1340)
SplitDecorManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:1215)
SplitDecorManager: at android.view.SurfaceControlViewHost.setView(SurfaceControlViewHost.java:443)
SplitDecorManager: at com.android.wm.shell.common.split.SplitDecorManager.inflate(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:140)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.finishEnterSplitScreen(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:1611)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.startPendingEnterAnimation(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:2854)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.startPendingAnimation(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:2696)
SplitDecorManager: at com.android.wm.shell.splitscreen.StageCoordinator.startAnimation(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:2632)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.playTransition(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:885)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.processReadyQueue(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:819)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.dispatchReady(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:760)
SplitDecorManager: at com.android.wm.shell.transition.Transitions.onTransitionReady(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:669)
SplitDecorManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.lambda$onTransitionReady$0(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:1353)
SplitDecorManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl.$r8$lambda$qsRfWn1ItrZqnFeABBdxU50jPc4(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:0)
SplitDecorManager: at com.android.wm.shell.transition.Transitions$TransitionPlayerImpl$$ExternalSyntheticLambda0.run(go/retraceme 68a42a0724b247a6035322d0bfaa0664da04ae43d52a60e64aa4fda3985af039:0)
SplitDecorManager: at android.os.Handler.handleCallback(Handler.java:958)
SplitDecorManager: at android.os.Handler.dispatchMessage(Handler.java:99)
SplitDecorManager: at android.os.Looper.loopOnce(Looper.java:205)
SplitDecorManager: at android.os.Looper.loop(Looper.java:294)
SplitDecorManager: at android.os.HandlerThread.run(HandlerThread.java:67)
上下分屏Surface堆栈流程是相同的,进入分屏后通过SplitDecorManager.inflate初始化,后续在system_server侧的WindowlessWindowManager.addToDisplay创建SplitDecorManager @0xc3d8701以及其父Surface SplitDecorManagerLeash,并调用SplitWindowManager.getParentSurface创建SplitWindowManager @0x47519da。
注意这里的两个SplitWindowManager名称相同,但是是不同的图层。
简图
结合dumpsys信息和代码获得如下简图:
分屏流程中的关键方法
设置分屏task
以前面设置分屏task方法为例
//设置分屏Options
addActivityOptions(options1, mSideStage);
//添加启动分屏task(system_server进程)
wct.startTask(taskId1, options1);
mSideStage为SideStage对象,MainStage和SideStage,他们都是继承StageTaskListener。
并且其后会调用WindowContainerTransaction的startTask方法启动分屏的task,这里是在system_server进程中进行的。
设置分屏Options
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
if (launchTarget != null) {
//设置sideStage的WindowContainerToken,也就是上分屏的task的token
//设置mainStage的WindowContainerToken,也就是下分屏的task的token
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
}
// Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
// will be canceled.
//允许其使用pendingInetent方式启动
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
}
launchTarget.mRootTaskInfo.token是WindowContainerToken对象,这里就是设置上分屏的task的token(sideStage的WindowContainerToken)到传递进来的Bundle对象中。
后续在system_server侧会通过ActivityOptions构造方法设置sideStage的WindowContainerToken。
代码路径:frameworks/base/core/java/android/app/ActivityOptions.java
public ActivityOptions(Bundle opts) {
super(opts);
......
mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, android.window.WindowContainerToken.class);
......
}
在ActivityOptions构造方法中,会取出之前存放的WindowContainerToken对象赋值给mLaunchRootTask。
即把sideStage的WindowContainerToken设置为mLaunchRootTask,mainStage也是同理。
添加启动分屏应用task(system_server进程)
代码路径:frameworks/base/core/java/android/window/WindowContainerTransaction.java
/**
* Starts a task by id. The task is expected to already exist (eg. as a recent task).
* @param taskId Id of task to start.
* @param options bundle containing ActivityOptions for the task's top activity.
* @hide
*/
@NonNull
public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
return this;
}
通过应用taskId来启动应用task,此时只是将该应用task以及之前设置的options设置到层级结构树中,尚未提交事务,真正的添加在后续通过SplitTransitions.startEnterTransition(涉及RemoteTransition的情况,我们这里从多任务启动分屏就是这种情况)或者SyncTransactionQueue(涉及RemoteAnimationAdapter的情况)提交事务到系统侧才会真正的启动。
设置分屏位置
setSideStagePosition(splitPosition, wct);
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
//传递参数updateBounds为true
setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
}
private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
@Nullable WindowContainerTransaction wct) {
//mSideStagePosition默认为SPLIT_POSITION_BOTTOM_OR_RIGHT,下分屏位置
//mSideStagePosition与传递过来的sideStagePosition相同,则不修改
if (mSideStagePosition == sideStagePosition) return;
//不同时,将mSideStagePosition赋值为传递过来的sideStagePosition
mSideStagePosition = sideStagePosition;
//通知stage位置发生改变
sendOnStagePositionChanged();
//mSideStageListener.mVisible判断分屏的可见性
//updateBounds传递了true
if (mSideStageListener.mVisible && updateBounds) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
//如果WindowContainerTransaction为空(这里一般不为空)
//这个方法会创建一个WindowContainerTransaction对象
//然后再调用updateWindowBounds方法
//之后MainStage和SideStage分别调用SplitDecorManager.onResized调整分屏size
onLayoutSizeChanged(mSplitLayout);
} else {
//更新窗口bounds,后续会讲
updateWindowBounds(mSplitLayout, wct);
//通知bounds改变
sendOnBoundsChanged();
}
}
}
mSideStagePosition
这个方法主要就是SideStage的分屏位置进行设置
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
其中mSideStagePosition从代码中可以发现默认值为SPLIT_POSITION_BOTTOM_OR_RIGHT,即值为1
但在多任务启动分屏的流程中传递过来的值是0,即sideStagePosition值为0,在上分屏显示。
mSideStageListener.mVisible分屏可见性
- 多任务中进入分屏时,会在shell动画流程中最后调用StageCoordinator.finishEnterSplitScreen去调用StageCoordinator.setSplitsVisible设置分屏可见性为true。
- HOME键退出分屏时,会在远程动画流程中(涉及APP的切换)最后调用通过StageCoordinator.onRecentsInSplitAnimationFinish去调用StageCoordinator.setSplitsVisible设置分屏可见性为false。
- 返回键退出分屏时,会在shell动画流程中最后调用StageCoordinator.prepareDismissAnimation去调用StageCoordinator.setSplitsVisible设置分屏可见性为false。
不管是哪种方式,最终都会通过StageCoordinator.setSplitsVisible设置分屏可见性。
onLayoutSizeChanged(mSplitLayout);
在多任务启动分屏流程中,WindowContainerTransaction对象不会为空,因此不会走到该流程。 这个方法主要是创建一个WindowContainerTransaction对象,并调用updateWindowBounds方法更新bounds,之后MainStage和SideStage分别调用SplitDecorManager.onResized调整分屏size,在没有更新bounds的情况下清除一些状态。
updateWindowBounds(mSplitLayout, wct);
见后文【更新分屏task的bound】
sendOnBoundsChanged();
这个方法本地验证注释过,没有发现什么影响。这里把这段代码上库时的注释放出来,仅供参考。
Adds real unfold animation for split-screen tasks when
doing the Shell unfold transition.
The approach is similar to full-screen tasks:
we animate the surfaces using shell transition
only when unfolding, when folding we are doing
it in the old way (by directly accessing
the surfaces from TaskOrganizer).
Refactored the previous fullscreen/splitscreen unfold
controllers flow to have one controller where we can
register diferrent 'animators'. This controller listens
to all task events in the shell task organizer.
简单分析下这个方法:
private void sendOnBoundsChanged() {
if (mSplitLayout == null) return;
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(),
getMainStageBounds(), getSideStageBounds());
}
}
从这个方法中我们可以看到,主要就是调用了onSplitBoundsChanged。
@ExternalThread
public interface SplitScreen {
......
interface SplitScreenListener {
default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
default void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {}
default void onSplitVisibilityChanged(boolean visible) {}
}
......
}
onSplitBoundsChanged是SplitScreen.SplitScreenListener的接口方法,需要找到其实现在哪。
因此先来看看前面的mListeners里面存放是什么
private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
if (mListeners.contains(listener)) return;
mListeners.add(listener);
sendStatusToListener(listener);
}
存放的是注册的SplitScreen.SplitScreenListener对象,这里添加的listener指的就是ISplitScreenImpl中创建的对象,我们找到其中创建的对象。
private static class ISplitScreenImpl extends ISplitScreen.Stub
implements ExternalInterfaceBinder {
private SplitScreenController mController;
private final SingleInstanceRemoteListener<SplitScreenController,
ISplitScreenListener> mListener;
private final SplitScreen.SplitScreenListener mSplitScreenListener =
new SplitScreen.SplitScreenListener() {
@Override
public void onStagePositionChanged(int stage, int position) {
mListener.call(l -> l.onStagePositionChanged(stage, position));
}
@Override
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
}
};
public ISplitScreenImpl(SplitScreenController controller) {
mController = controller;
mListener = new SingleInstanceRemoteListener<>(controller,
c -> c.registerSplitScreenListener(mSplitScreenListener),
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
}
......
@Override
public void registerSplitScreenListener(ISplitScreenListener listener) {
executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
(controller) -> mListener.register(listener));
}
......
}
这里(controller) -> mListener.register(listener));实际上就是调用的c -> c.registerSplitScreenListener(mSplitScreenListener)。
也就是说在registerSplitScreenListener的实现中mListeners.add(listener);,其中的listener指的就是这里的mSplitScreenListener。
但是我们可以看到mSplitScreenListener中并没有实现接口中的onSplitBoundsChanged方法,因此什么都没有做。
且本地验证SplitScreen接口中其他的实现也没有在设置分屏位置场景调用,感兴趣的可以研究补充。
具体流程不在赘述,附registerSplitScreenListener调用堆栈流程
registerSplitScreenListener: listener:com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl$1@ef57f97
registerSplitScreenListener: java.lang.Exception
registerSplitScreenListener: at com.android.wm.shell.splitscreen.StageCoordinator.registerSplitScreenListener(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:1662)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController.registerSplitScreenListener(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:451)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.lambda$new$0(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:1074)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.$r8$lambda$LTc1wMcZo9Of3RPyGCWtg6YiS5s(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl$$ExternalSyntheticLambda15.accept(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: at com.android.wm.shell.common.SingleInstanceRemoteListener.register(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:97)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.lambda$registerSplitScreenListener$2(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:1091)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.$r8$lambda$3asGbaEmeTX8SI0BiI_eYjbgpQA(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl$$ExternalSyntheticLambda11.accept(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: at com.android.wm.shell.common.ExecutorUtils.lambda$executeRemoteCallWithTaskPermission$1(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:60)
registerSplitScreenListener: at com.android.wm.shell.common.ExecutorUtils.$r8$lambda$s8eUOdyrqpqzzyFwAMGxO-MaCg4(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: at com.android.wm.shell.common.ExecutorUtils$$ExternalSyntheticLambda1.run(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: at android.os.Handler.handleCallback(Handler.java:958)
registerSplitScreenListener: at android.os.Handler.dispatchMessage(Handler.java:99)
registerSplitScreenListener: at android.os.Looper.loopOnce(Looper.java:205)
registerSplitScreenListener: at android.os.Looper.loop(Looper.java:294)
registerSplitScreenListener: at android.os.HandlerThread.run(HandlerThread.java:67)
设置分屏比例
mSplitLayout.setDivideRatio(splitRatio);
/** Updates divide position and split bounds base on the ratio within root bounds. */
public void setDivideRatio(float ratio) {
final int position = isLandscape()
? mRootBounds.left + (int) (mRootBounds.width() * ratio)
: mRootBounds.top + (int) (mRootBounds.height() * ratio);
final DividerSnapAlgorithm.SnapTarget snapTarget =
mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
这里会先根据ratio计算出一个位置position,但是这个position并不是直接的SnapTarget的position,需要把这个position传递到calculateNonDismissingSnapTarget方法计算出SnapTarget,然后在使用SnapTarget的position。 具体见分屏分割线相关 (留坑,尚未更新)
更新分屏task的bounds
updateWindowBounds(mSplitLayout, wct);
传递上下分屏task信息
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
/**
* Populates `wct` with operations that match the split windows to the current layout.
* To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
*
* @return true if stage bounds actually .
*/
private boolean updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
bottomRightStage.mRootTaskInfo);
}
这个方法传递了上下屏task信息后,要对这些task的bound进行修改。 注意:这里传递的是SideStage和MainStage这个两个上下分屏容器task信息,而非这个两个Stage下面的挂着的应用的task信息。 layout.applyTaskChanges方法传递了WindowContainerTransaction对象和上下分屏的task信息。
设置bounds
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
public boolean applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
boolean boundsChanged = false;
//两个条件满足其一即可
//1.新bounds的值与之前bounds的值不一致
//2.或者新task与之前task不一致(上下分屏交换的场景)
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
//设置bounds
wct.setBounds(task1.token, mBounds1);
wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
//记录新的bounds
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
boundsChanged = true;
}
//两个条件满足其一即可
//1.新bounds的值与之前bounds的值不一致
//2.或者新task与之前task不一致(上下分屏交换的场景)
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
//设置bounds
wct.setBounds(task2.token, mBounds2);
wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
//记录新的bounds
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
boundsChanged = true;
}
return boundsChanged;
}
这个方法主要就是在bounds发生改变,或者上下分屏切换时,设置新bounds,并把新的bounds记录到Rect对象中。 这里通过WindowContainerTransaction对象对bounds进行设置。
设置bounds的实现(system_server侧)
/**
* Resize a container.
*/
@NonNull
public WindowContainerTransaction setBounds(
@NonNull WindowContainerToken container,@NonNull Rect bounds) {
Change chg = getOrCreateChange(container.asBinder());
chg.mConfiguration.windowConfiguration.setBounds(bounds);
chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
return this;
}
/**
* Set the smallestScreenWidth of a container.
*/
@NonNull
public WindowContainerTransaction setSmallestScreenWidthDp(
@NonNull WindowContainerToken container, int widthDp) {
Change cfg = getOrCreateChange(container.asBinder());
cfg.mConfiguration.smallestScreenWidthDp = widthDp;
cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
return this;
}
这里就是把设置的bounds保存到Change对象中,后续提交WindowContainerTransaction后,在system_server侧便会进行真正的处理。
更新SurfaceBounds
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
boolean applyResizingOffset) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
(layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
applyResizingOffset);
}
这个方法主要就获取对应的stage传递他们的leash用来后续更新对应的SurfaceBounds。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
public void applySurfaceChanges(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, SurfaceControl dimLayer1, SurfaceControl dimLayer2,
boolean applyResizingOffset) {
//获取分割线图层
final SurfaceControl dividerLeash = getDividerLeash();
if (dividerLeash != null) {
//获取前面更新的分割线bounds(mDividerBounds),将其保存到mTempRect中
getRefDividerBounds(mTempRect);
//更新图层位置
t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
// Resets layer of divider bar to make sure it is always on top.
t.setLayer(dividerLeash, Integer.MAX_VALUE);
}
//获取更新后的mBounds1(上分屏bounds),将其保存到mTempRect中
getRefBounds1(mTempRect);
//更新上分屏图层位置
t.setPosition(leash1, mTempRect.left, mTempRect.top)
.setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
//获取更新后的mBounds2(下分屏bounds),将其保存到mTempRect中
getRefBounds2(mTempRect);
//更新下分屏图层位置
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
if (mImePositionProcessor.adjustSurfaceLayoutForIme(
t, dividerLeash, leash1, leash2, dimLayer1, dimLayer2)) {
return;
}
mSurfaceEffectPolicy.adjustDimSurface(t, dimLayer1, dimLayer2);
if (applyResizingOffset) {
mSurfaceEffectPolicy.adjustRootSurface(t, leash1, leash2);
}
}