hi,上一节已经讲解清楚了分屏在Launcher端的一个启动情况,本节课开始将进行systemui部分的详细讲解:
b站免费视频教程讲解:
https://www.bilibili.com/video/BV1wj411o7A9/
1、systemui部分触发分屏的调用流程
回忆上一节launcher端会调用以下方法来调用到systemui端
//packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
pendingAnimation.addEndListener(aBoolean -> {
mSplitSelectStateController.launchSplitTasks(
aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
});
public void launchSplitTasks(Consumer<Boolean> callback) {
//省略
launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition,
callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
}
public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
@Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
//省略
//跨进程调用到systemui进程的startIntentAndTaskWithLegacyTransition
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
stagePosition, splitRatio, adapter);
}
//packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
@SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter) {
if (mSystemUiProxy != null) {
try {
//这里有调用到了SplitScreen的startIntentAndTaskWithLegacyTransition
if (taskPendingIntent == null) {
mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
splitRatio, adapter);
}
}
}
}
这里注意最后桌面调用了mSplitScreen.startIntentAndTaskWithLegacyTransition,这里的mSplitScreen就是一个binder代理,会调用到systemui的服务端
这里对应systemui端的代码如下:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
private static class ISplitScreenImpl extends ISplitScreen.Stub {
//省略
@Override
public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter) {
//注意这里有线程切换哈,binder线程到main线程
executeRemoteCallWithTaskPermission(mController, "startTasks",
(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
splitRatio, adapter));
}
线程切换后调用到了最关键方法:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
private void startWithLegacyTransition(int mainTaskId, int sideTaskId,
@Nullable PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
@SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
//初始化分屏的分割线的布局
mSplitLayout.init();
//省略
final WindowContainerTransaction wct = new WindowContainerTransaction();//初始化用来跨进程传递的WindowContainerTransaction
//初始化一个远程动画的运行回调binder对象
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
public void onAnimationStart(@WindowManager.TransitionOldType int transit,
RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
final IRemoteAnimationFinishedCallback finishedCallback) {
//暂时省略这里后续分析
};
RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());//包装一下成为RemoteAnimationAdapter类型
//省略
//构建出对应的mainOptions,及上分屏的启动option
mainOptions = mainActivityOptions.toBundle();
//准备好对应的sideOptions,下分屏的option
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition, wct);
mSplitLayout.setDivideRatio(splitRatio);//设置分界线比例,一般大小上下屏幕都一样大,为0.5,这里就把对应的上下分屏的bound计算出来了
if (!mMainStage.isActive()) {//设置为active
mMainStage.activate(wct, false /* reparent */);
}
updateWindowBounds(mSplitLayout, wct);//这里把计算出来的上下分屏的bound都设置给对应的configration,传递到systemserver端,然后方便systemserver更新task的bound
wct.reorder(mRootTaskInfo.token, true);//需要让装载分屏的roottask进行reorder,主要就是为了把分屏移到最前面, 注意这里的mRootTaskInfo其实就是一开始systemui负责创建的mutilwindow的task这里默认id一般为4
//准备好对应的option参数
// Make sure the launch options will put tasks in the corresponding split roots
addActivityOptions(mainOptions, mMainStage);
addActivityOptions(sideOptions, mSideStage);
//分别准备好对应上下分屏启动task的transition
// Add task launch requests
wct.startTask(mainTaskId, mainOptions);
if (withIntent) {
wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
} else {
wct.startTask(sideTaskId, sideOptions);
}
//最后把前面准备好的WindowContainerTranstion统一进行apply到systemserver
// Using legacy transitions, so we can't use blast sync since it conflicts.
mTaskOrganizer.applyTransaction(wct);
mSyncQueue.runInSync(t -> {
//这里主要进行相关的divider分界线显示
setDividerVisibility(true, t);
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
});
}
ps:mMainStage和mSideStage属于和RootTask一样,分屏的RootTask一般会带两个子task,他们就分别是mMainStage和mSideStage的RootTaskInfo
#0 Task=4 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
#1 Task=6 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
#0 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
执行堆栈情况:
startWithLegacyTransition:503, StageCoordinator (com.android.wm.shell.splitscreen)
startTasksWithLegacyTransition:392, StageCoordinator (com.android.wm.shell.splitscreen)
lambda$startTasksWithLegacyTransition$8:684, SplitScreenController$ISplitScreenImpl (com.android.wm.shell.splitscreen)
$r8$lambda$YwxG8mhKer2INb6KsZqR3kn2_Zg:-1, SplitScreenController$ISplitScreenImpl (com.android.wm.shell.splitscreen)
accept:-1, SplitScreenController$ISplitScreenImpl$$ExternalSyntheticLambda6 (com.android.wm.shell.splitscreen)
lambda$executeRemoteCallWithTaskPermission$1:60, ExecutorUtils (com.android.wm.shell.common)
$r8$lambda$s8eUOdyrqpqzzyFwAMGxO-MaCg4:-1, ExecutorUtils (com.android.wm.shell.common)
run:-1, ExecutorUtils$$ExternalSyntheticLambda1 (com.android.wm.shell.common)
handleCallback:942, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
main:7897, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:548, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:936, ZygoteInit (com.android.internal.os)
时序图:
2、systemui开启分屏干的关键事情
2.1 SplitLayout创建分割线区域部分
刚开始是SplitLayout的init
public void init() {
if (mInitialized) return;
mInitialized = true;
//调用mSplitWindowManager初始化,主要是创建对应window出来,注意这里的window创建和pip那个window创建类是,不是用普通的windowmanager,所以dumpsys window windows是看不到的,得通过dumpsys SurfaceFlinger才可以看到
mSplitWindowManager.init(this, mInsetsState);
mDisplayImeController.addPositionProcessor(mImePositionProcessor);
}
/** Inflates {@link DividerView} on to the root surface. */
void init(SplitLayout splitLayout, InsetsState insetsState) {
if (mDividerView != null || mViewHost != null) {
throw new UnsupportedOperationException(
"Try to inflate divider view again without release first");
}
mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
mDividerView = (DividerView) LayoutInflater.from(mContext)
.inflate(R.layout.split_divider, null /* root */);
final Rect dividerBounds = splitLayout.getDividerBounds();
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
| FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle(mWindowName);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mViewHost.setView(mDividerView, lp);
mDividerView.setup(splitLayout, this, mViewHost, insetsState);
}
再来是区域的计算确定部分,setDivideRatio方法:
public void setDivideRatio(float ratio) {
//注意这里的position是非常关键的,这个为roottask的0.5一般情况,上下屏幕相等情况
final int position = isLandscape()
? mRootBounds.left + (int) (mRootBounds.width() * ratio)
: mRootBounds.top + (int) (mRootBounds.height() * ratio);
final DividerSnapAlgorithm.SnapTarget snapTarget =
mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
//这里会设置对应的位置position,非常关键会触发bound的计算
setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
void setDividePosition(int position, boolean applyLayoutChange) {
mDividePosition = position;
updateBounds(mDividePosition);//根据新新的mDividePosition计算新的bound
if (applyLayoutChange) {
mSplitLayoutHandler.onLayoutSizeChanged(this);
}
}
/** Updates recording bounds of divider window and both of the splits. */
private void updateBounds(int position) {
mDividerBounds.set(mRootBounds);
mBounds1.set(mRootBounds);
mBounds2.set(mRootBounds);
final boolean isLandscape = isLandscape(mRootBounds);
if (isLandscape) {
position += mRootBounds.left;
mDividerBounds.left = position - mDividerInsets;
mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth;
mBounds1.right = position;
mBounds2.left = mBounds1.right + mDividerSize;
} else {
position += mRootBounds.top;
mDividerBounds.top = position - mDividerInsets;
mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth;
mBounds1.bottom = position;
mBounds2.top = mBounds1.bottom + mDividerSize;
}
DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */);
mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
}
已经计算好了分屏的bound后,就需要把bound设置到WindowContainerTransition中进行传递,到了关键的:updateWindowBounds(mSplitLayout, wct);
private void 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;
//分别有了上下屏task信息后,要对这些task的bound进行修改,applyTaskChanges就是关键的方法(注意这里的task还不是直接app的task,还是分屏的mMainStage及mSideStage对应的容器task)
layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
}
/** Apply recorded task layout to the {@link WindowContainerTransaction}. */
public void applyTaskChanges(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
wct.setBounds(task1.token, mBounds1);//WindowContainerTransaction设置好对应的task的bound数据
wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
mWinBounds1.set(mBounds1);
mWinToken1 = task1.token;
}
if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
wct.setBounds(task2.token, mBounds2);
wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
mWinBounds2.set(mBounds2);
mWinToken2 = task2.token;
}
}
/**
* Resize a container.
*/
@NonNull
public WindowContainerTransaction setBounds(
@NonNull WindowContainerToken container,@NonNull Rect bounds) {
Change chg = getOrCreateChange(container.asBinder());//注意这里进行了Change的构造,即bounds变化靠这个Change变量进行传递
chg.mConfiguration.windowConfiguration.setBounds(bounds);
chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
return this;
}
2.2 对分屏的RootTask进行reorder
@NonNull
public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop));
return this;
}
//创建对应的HIERARCHY_OP_TYPE_REORDER的HierarchyOp进行传递
public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER)
.setContainer(container)
.setReparentContainer(container)
.setToTop(toTop)
.build();
}
2.3 对分屏Task进行startTask
这个和reorder没啥区别
public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
return this;
}
/** Create a hierarchy op for launching a task. */
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();
}