Android U 分屏——SystemUI侧处理
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
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
@Overridepublic void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,boolean removeWithTaskOrganizer) {enforceTaskPermission("createRootTask()");final long origId = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {//获取当前DisplayContentDisplayContent 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);}}@VisibleForTestingTask 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.//创建Taskfinal 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的方式创建的。
分屏流程中的关键方法
设置分屏task
以前面设置分屏task方法为例
//设置分屏OptionsaddActivityOptions(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的tokenopts.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*/@NonNullpublic 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为truesetSideStagePosition(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赋值为传递过来的sideStagePositionmSideStagePosition = sideStagePosition;sendOnStagePositionChanged();//mSideStageListener.mVisible判断分屏的可见性//updateBounds传递了trueif (mSideStageListener.mVisible && updateBounds) {if (wct == null) {// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.//如果WindowContainerTransaction为空(这里一般不为空)//这个方法会创建一个WindowContainerTransaction对象//然后再调用updateWindowBounds方法onLayoutSizeChanged(mSplitLayout);} else {//更新窗口bounds,后续会讲updateWindowBounds(mSplitLayout, wct);//没有实际意义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,在没有更新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。
@ExternalThreadpublic 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.Stubimplements ExternalInterfaceBinder {private SplitScreenController mController;private final SingleInstanceRemoteListener<SplitScreenController,ISplitScreenListener> mListener;private final SplitScreen.SplitScreenListener mSplitScreenListener =new SplitScreen.SplitScreenListener() {@Overridepublic void onStagePositionChanged(int stage, int position) {mListener.call(l -> l.onStagePositionChanged(stage, position));}@Overridepublic 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));}......@Overridepublic 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;if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {//设置boundswct.setBounds(task1.token, mBounds1);wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));//记录新的boundsmWinBounds1.set(mBounds1);mWinToken1 = task1.token;boundsChanged = true;}if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {//设置boundswct.setBounds(task2.token, mBounds2);wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));//记录新的boundsmWinBounds2.set(mBounds2);mWinToken2 = task2.token;boundsChanged = true;}return boundsChanged;}
这个方法主要就是设置新bounds,并把新的bounds记录到Rect对象中。
这里通过WindowContainerTransaction对象对bounds进行设置。
设置bounds的实现(system_server侧)
/*** Resize a container.*/@NonNullpublic 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.*/@NonNullpublic 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侧便会进行真正的处理。
相关文章:
Android U 分屏——SystemUI侧处理
WMShell相关的dump命令 手机分屏启动应用后运行命令:adb shell dumpsys activity service SystemUIService WMShell 我们可以找到其中分屏的部分,如下图所示: 分屏的组成 简图 分屏是由上分屏(SideStage)、下分屏(MainStage)以及分割线组…...
面试基础---MySQL 事务隔离级别与 MVCC 深度解析
MySQL 事务隔离级别与 MVCC 深度解析:原理、实践与源码分析 引言 在高并发的互联网应用中,数据库事务的隔离级别是保证数据一致性和并发性能的关键。MySQL 通过多版本并发控制(MVCC)机制实现了不同的事务隔离级别。本文将深入探…...
第十二届蓝桥杯大学A组java省赛答案整理
货物摆放 题目描述 小蓝有一个超大的仓库,可以摆放很多货物。 现在,小蓝有 nn 箱货物要摆放在仓库,每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。 小蓝希望所…...
浅浅初识AI、AI大模型、AGI
前记:这里只是简单了解,后面有时间会专门来扩展和深入。 当前,人工智能(AI)及其细分领域(如AI算法工程师、自然语言处理NLP、通用人工智能AGI)的就业前景呈现高速增长态势,市场需求…...
flink集成tidb cdc
Flink TiDB CDC 详解 1. TiDB CDC 简介 1.1 TiDB CDC 的核心概念 TiDB CDC 是 TiDB 提供的变更数据捕获工具,能够实时捕获 TiDB 集群中的数据变更(如 INSERT、UPDATE、DELETE 操作),并将这些变更以事件流的形式输出。TiDB CDC 的…...
【flutter】TextField输入框工具栏文本为英文解决(不用安装插件版本
输入框长按选项菜单复制、粘贴、剪切、全选部分默认为英文,对于只需要对此部分做中文本地化,不需要考虑其他语言及全局本地化的项目,可以直接自定义一个本地化代理方法进行覆盖,不需要额外下载插件 // 自定义本地化代理 class _C…...
推荐1款OCR的扫描仪软件,无需安装,打开即用!
聊一聊 现在日常办公,很多时候还是需要扫描仪配合。 很多时候需要将文件搜索成PDF再传输。 今天给大家分享一款OCR扫描仪软件。 软件介绍 OCR的扫描仪软件 支持扫描仪共享。 支持WIA、TWAIN、SANE和ESCL驱动程序。 还可以批量多扫描仪配置扫描,支持…...
SpringBoot为什么默认使用CGLIB?
大家好,我是锋哥。今天分享关于【SpringBoot为什么默认使用CGLIB?】面试题。希望对大家有帮助; SpringBoot为什么默认使用CGLIB? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot 默认使用 CGLIB(Code Generation Li…...
去除HTML有序列表(ol)编号的多种解决方案
以下是去除HTML有序列表(ol)编号的多种解决方案: <!DOCTYPE html> <html> <head> <style> /* 基础方案:完全移除编号 */ ol.no-number {list-style-type: none; /* 移除默认编号 */padding-left: 0; /* 移除默认缩进 */…...
神经网络|(十三)|SOM神经网络
【1】引言 前序已经对神经网络有了基础认识,今天先学习SOM神经网络。 前序学习文章链接包括且不限于: 神经网络|(十一)|神经元和神经网络-CSDN博客 神经网络|(十二)|常见激活函数-CSDN博客 【2】SOM神经网络 SOM神经网络是一种结构比较简单、但是理…...
IP协议、DNS协议、DHCP协议、Telent协议的记忆总结
首先记忆一下几个协议的端口号 HTTP:超文本传输协议 80 HTTPS:安全传输协议 443 DHCP:动态主机配置协议 67/68 DNS:域名解析协议 53 FTP:文件传输协议 20/21 TFTP:简单文件传输协议 69 TELENT:远…...
Pico 4 Enterprise(企业版)与Unity的交互-有线串流调试篇
入手了Pico 4 E做VR开发,谁知入了天坑...根据官方文档,尝试了串流助手、企业串流、PICO Developer Center,陷入了各种版本问题、环境问题的陷阱。而且Pico4E的OS自24年12开始就不再更新,头盔中预装的企业串流版本也较低࿰…...
DeepSeek-R1:使用KTransformers实现高效部署指南
KTransformers作为一个开源框架,专门为优化大规模语言模型的推理过程而设计。它支持GPU/CPU异构计算,并针对MoE架构的稀疏性进行了特别优化,可以有效降低硬件要求,允许用户在有限的资源下运行像DeepSeek-R1这样庞大的模型。 硬件…...
企业日常工作中常用的 Linux 操作系统命令整理
Linux 操作系统命令整理 在企业级运维、开发和日常工作中,Linux 命令是绕不开的核心技能。不论是日志排查、进程管理,还是高效运维优化,掌握这些命令都能让你事半功倍!本篇文章整理了自己在日常工作中积累最常用的 Linux 命令&am…...
任务9:交换机基础及配置
CSDN 原创主页:不羁https://blog.csdn.net/2303_76492156?typeblog 一、交换机基础 交换机的概念:交换机是一种网络设备,用于连接多台计算机或网络设备,实现数据包在局域网内的快速交换。交换机基于MAC地址来转发数据包&#x…...
Notepad++ 8.6.7 安装与配置全攻略(Windows平台)
一、软件定位与核心优势 Notepad 是开源免费的代码/文本编辑器,支持超过80种编程语言的高亮显示,相比系统自带记事本具有以下优势: 轻量高效:启动速度比同类软件快30%插件扩展:支持NppExec、JSON Viewer等200插件跨文…...
SpringMVC请求处理流程:DispatcherServlet工作原理
文章目录 引言一、DispatcherServlet概述二、DispatcherServlet初始化过程三、请求接收与处理器匹配四、请求参数绑定与处理器执行五、视图解析与渲染六、异常处理机制总结 引言 SpringMVC框架是Java Web开发中最流行的MVC框架之一,其核心组件DispatcherServlet作为…...
YOLOv8目标检测推理流程及C++代码
这部分主要是使用c++对Onnx模型进行推理,边先贴代码,过段时间再详细补充下代码说明。 代码主要分成三部分,1.main_det.cpp推理函数主入口;2.inference_det.h 头文件及inference_det.cpp具体函数实现;3.CMakeList.txt. 1.main_det 推理配置信息全部写在config.txt中,执行…...
解锁数据潜能,永洪科技以数据之力简化中粮可口可乐决策之路
企业数字化转型是指企业利用数字技术和信息通信技术来改变自身的商业模式、流程和增值服务,以提高企业的竞争力和创新能力。数字化转型已经成为企业发展的重要战略,尤其在当前信息技术高速发展的时代。数字化转型还涉及到企业与消费者之间的互动和沟通。…...
Redis3 Hash 类型命令详解
1. 什么是 Redis Hash? Redis Hash 是一种 键值对集合,类似于 Java 里的 HashMap,可以用来存储对象的数据。例如,你可以将用户信息存储在 Redis 的 Hash 结构中,每个字段代表用户的一个属性。 示例: HSE…...
双链路提升网络传输的可靠性扩展可用带宽
为了提升网络传输的可靠性或增加网络可用带宽, 通常使用双链路冗余备份或者双链路聚合的方式。 本文介绍几种双链路网络通信的案例。 5GWiFi冗余传输 双Socket绑定不同网络接口:通过Android的ConnectivityManager绑定5G蜂窝网络和WiFi的Socket连接&…...
深入浅出:UniApp 从入门到精通全指南
https://juejin.cn/post/7440119937644101684 uni-app官网 uniapp安卓离线打包流程_uniapp离线打包-CSDN博客 本文是关于 UniApp 从入门到精通的全指南,涵盖基础入门(环境搭建、创建项目、项目结构、编写运行)、核心概念与进阶知识&#x…...
MDM 如何彻底改变医疗设备的远程管理
在现代医疗行业迅速发展的格局中,医院和诊所越来越依赖诸如医疗平板和移动工作站等移动设备。这些设备在提高工作效率和提供卓越的患者护理方面发挥着关键作用。然而,随着它们的广泛使用,也带来了一系列挑战,例如在不同地点确保数…...
前端性能优化之同时插入100000个元素页面不卡顿
面试官:同时插入100000个元素怎么让页面不卡顿 优化前写法 首先我们来看下面的一段,点击按钮后,循环100000次,每次都插入一个元素,并且插入区域上方还有一个小球在滚动,在插入的过程中我们可以观察小球的…...
PHP之Cookie和Session
在你有别的编程语言的基础下,你想学习PHP,可能要了解的一些关于cookie和session的信息。 Cookie 参数信息 setcookie(name,value,expire, path, domain); name : Cookie的名称。 value : Cookie的值。 expire : Cookie的过期时间,可以是一…...
vscode 配置debug的环境
vscode配置debug的环境 配置好python解释器, ctrl shift P 就可以指定python了。 当前环境下建立 .vscode 文件夹新建 .vscode/launch.json 文件文件的配置如下 {"version": "0.2.0","configurations": [{"name": &qu…...
socket基础学习以及java搭建
在 Java 中,Socket 编程用于实现网络通信。Java 提供了丰富的网络 API,使得通过 Socket 进行通信变得简单和高效。Java 的 Socket 编程常见于客户端-服务器应用中,比如聊天程序、文件传输工具等。 1. Socket 基本概念 Socket 编程的基本概念…...
Exoplayer2源码编译FFmpeg拓展模块实现音频软解码
在前面文章最新版本Exoplayer扩展FFmpeg音频软解码保姆级教程中介绍了最新版本的Exoplayer(androidx.Media3)编译FFmpeg模块的流程,有就是media3版本的explayer最低支持的sdk版本是21也就是Android5.x,但是市面上还是有很多IOT设备是很老的android4.4(sdk19)的&…...
Docker安装嵌入框架Text Embeddings Inference (TEI)
Docker安装Text Embeddings Inference (TEI) 1 简单介绍 文本嵌入推理(TEI,Text Embeddings Inference )是HuggingFace研发的一个用于部署和服务开源文本嵌入和序列分类模型的工具包。TEI兼容OpenAI的嵌入模型的规范。 # 官网地址 https:/…...
使用easyocr、PyPDF2对图像及PDF文档进行识别
一、概述 本 Python 脚本的主要功能是对当前目录及其子目录下的图片和 PDF 文件进行光学字符识别(OCR)处理。它使用 easyocr 库处理图片中的文字,使用 PyPDF2 库提取 PDF 文件中的文本,并将处理结果保存为文本文件。同时ÿ…...
