Android 12系统源码_多窗口模式(二)系统实现分屏的功能原理

前言

上一篇我们具体分析了系统处于多窗口模式下,Android应用和多窗口模式相关方法的调用顺序,对于应用如何适配多窗口模式有了一个初步的认识,本篇文章我们将会结合Android12系统源码,具体来梳理一下系统是如何触发多窗口分屏模式,以及实现多窗口分屏模式功能的原理。

一、Launcher3触发分屏

1、Android12的分屏模式触发入口,默认是在最近任务列表中的,而最近任务列表是包含在Launcher3里面的,当我们在最近任务列表中点击分屏按钮后,会先触发Launcher进入分屏的一系列悬浮动画以及初始的图标分屏。

在这里插入图片描述
悬浮动画

图标分屏动画
以上步骤都属于Launcher的业务逻辑。

2、接下来我们结合系统源码来简单看下Launcher3模块是如何触发分屏功能的。

packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java

public class QuickstepLauncher extends BaseQuickstepLauncher {@Overridepublic void onStateSetEnd(LauncherState state) {super.onStateSetEnd(state);switch (state.ordinal) {...代码省略...case QUICK_SWITCH_STATE_ORDINAL: {RecentsView rv = getOverviewPanel();TaskView tasktolaunch = rv.getTaskViewAt(0);if (tasktolaunch != null) {//调用TaskView的launchTask方法tasktolaunch.launchTask(success -> {if (!success) {getStateManager().goToState(OVERVIEW);} else {getStateManager().moveToRestState();}});} else {getStateManager().goToState(NORMAL);}break;}}}}

package/apps/Launcher3/quickstep/src/com/android/quickstep/views/GroupedTaskView.java

public class GroupedTaskView extends TaskView {@Nullable@Overridepublic RunnableList launchTaskAnimated() {if (mTask == null || mSecondaryTask == null) {return null;}RunnableList endCallback = new RunnableList();RecentsView recentsView = getRecentsView();// Callbacks run from remote animation when recents animation not currently running//调用RecentsView的getSplitPlaceholder方法,获取SplitSelectStateController对象实例,调用launchTasks方法recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,success -> endCallback.executeAllAndDestroy(),false /* freezeTaskList */);// Callbacks get run from recentsView for case when recents animation already runningrecentsView.addSideTaskLaunchCallback(endCallback);return endCallback;}@Overridepublic void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {//调用RecentsView的getSplitPlaceholder方法,获取SplitSelectStateController对象实例,调用launchTasks方法getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList,getSplitRatio());}
}

package/apps/Launcher3/quickstep/src/com/android/quickstep/views/RecentsView.java

public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>,STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,TaskVisualsChangeListener, SplitScreenBounds.OnChangeListener {public SplitSelectStateController getSplitPlaceholder() {return mSplitSelectStateController;}
}

package/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java

public class SplitSelectStateController {public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {// Assume initial task is for top/left part of screenfinal int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT? new int[]{task1.key.id, task2.key.id}: new int[]{task2.key.id, task1.key.id};if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {RemoteSplitLaunchTransitionRunner animationRunner =new RemoteSplitLaunchTransitionRunner(task1, task2);mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,ActivityThread.currentActivityThread().getApplicationThread()));} else {RemoteSplitLaunchAnimationRunner animationRunner =new RemoteSplitLaunchAnimationRunner(task1, task2, callback);//转场动画final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),300, 150,ActivityThread.currentActivityThread().getApplicationThread());ActivityOptions mainOpts = ActivityOptions.makeBasic();if (freezeTaskList) {mainOpts.setFreezeRecentTasksReordering();}//调用SystemUiProxy的startTasksWithLegacyTransition方法mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,splitRatio, adapter);}}}

packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java

public class SystemUiProxy implements ISystemUiProxy,SysUINavigationMode.NavigationModeChangeListener {public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =new MainThreadInitializedObject<>(SystemUiProxy::new);private ISplitScreen mSplitScreen;public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,IOneHanded oneHanded, IShellTransitions shellTransitions,IStartingWindow startingWindow, IRecentTasks recentTasks,ISmartspaceTransitionController smartSpaceTransitionController) {...代码省略...mSplitScreen = splitScreen;...代码省略.../*** 分屏模式同时打开多个任务*/public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,float splitRatio, RemoteAnimationAdapter adapter) {if (mSystemUiProxy != null) {try {//调用ISplitScreen的startTasksWithLegacyTransition方法触发分屏mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,sideOptions, sidePosition, splitRatio, adapter);} catch (RemoteException e) {Log.w(TAG, "Failed call startTasksWithLegacyTransition");}}}}

通过梳理以上代码,可以发现Launche3最终是通过调用SystemUiProxy的startTasksWithLegacyTransition方法触发分屏的,而该方法内部又进一步调用了类型为ISplitScreen的mSplitScreen对象的startTasksWithLegacyTransition方法。

3、SystemUiProxy的内部属性对象mSplitScreen最初是在TouchInteractionService的内部类TISBinder的onInitialize方法中被赋值的。

packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java

public class TouchInteractionService extends Serviceimplements ProtoTraceable<LauncherTraceProto.Builder> {private final TISBinder mTISBinder = new TISBinder();public class TISBinder extends IOverviewProxy.Stub {@BinderThreadpublic void onInitialize(Bundle bundle) {ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));//触发分屏就是调用的这个对象的方法ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN));IOneHanded onehanded = IOneHanded.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));ISmartspaceTransitionController smartspaceTransitionController =ISmartspaceTransitionController.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER));IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(bundle.getBinder(KEY_EXTRA_RECENT_TASKS));MAIN_EXECUTOR.execute(() -> {//调用SystemUiProxy的setProxy方法SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,smartspaceTransitionController);TouchInteractionService.this.initInputMonitor();preloadOverview(true /* fromInit */);});sIsInitialized = true;}}@Overridepublic IBinder onBind(Intent intent) {Log.d(TAG, "Touch service connected: user=" + getUserId());return mTISBinder;}}       

packages/apps/Launcher3/quickstep/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.android.launcher3"><application android:backupAgent="com.android.launcher3.LauncherBackupAgent"><service android:name="com.android.quickstep.TouchInteractionService"android:permission="android.permission.STATUS_BAR_SERVICE"android:directBootAware="true"android:exported="true"><intent-filter><action android:name="android.intent.action.QUICKSTEP_SERVICE"/></intent-filter></service></application></manifest>

TouchInteractionService是Launcher的一个服务,内部类TISBinder就是其他模块绑定TouchInteractionService服务时候所返回的IBinder类型的实例对象。

二、SystemUI触发分屏

1、默认情况下,SystemUI模块对Launcher3模块的TouchInteractionService服务进行了绑定。

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

public class OverviewProxyService extends CurrentUserTracker implementsCallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,Dumpable {private final Optional<SplitScreen> mSplitScreenOptional;//触发分屏模式的关键对象//唤起Launcher3模块TouchInteractionService的Actionprivate static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";//唤起Launcher3模块TouchInteractionService的Intentprivate final Intent mQuickStepIntent;//远程IPC通信是实现类private IOverviewProxy mOverviewProxy;private boolean mBound;public OverviewProxyService(Context context, CommandQueue commandQueue,Lazy<NavigationBarController> navBarControllerLazy,Lazy<Optional<StatusBar>> statusBarOptionalLazy,NavigationModeController navModeController,NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,Optional<Pip> pipOptional,Optional<LegacySplitScreen> legacySplitScreenOptional,Optional<SplitScreen> splitScreenOptional,Optional<OneHanded> oneHandedOptional,Optional<RecentTasks> recentTasks,Optional<StartingSurface> startingSurface,BroadcastDispatcher broadcastDispatcher,ShellTransitions shellTransitions,ScreenLifecycle screenLifecycle,SmartspaceTransitionController smartspaceTransitionController,UiEventLogger uiEventLogger,DumpManager dumpManager) {super(broadcastDispatcher);...代码省略...//获取最近应用列表组件名称,其实就是Launcher3的包名mRecentsComponentName = ComponentName.unflattenFromString(context.getString(com.android.internal.R.string.config_recentsComponentName));//创建最近应用列表Activity的意图对象mQuickStepIntent = new Intent(ACTION_QUICKSTEP).setPackage(mRecentsComponentName.getPackageName());...代码省略...startConnectionToCurrentUser();...代码省略...}//成功绑定服务所返回的ServiceConnection对象private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {...代码省略...mCurrentBoundedUserId = getCurrentUserId();//为mOverviewProxy赋值mOverviewProxy = IOverviewProxy.Stub.asInterface(service);Bundle params = new Bundle();params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);mPipOptional.ifPresent((pip) -> params.putBinder(KEY_EXTRA_SHELL_PIP,pip.createExternalInterface().asBinder()));//关键对象,Optional对象的的ifPresent方法会判断该对象内部的SplitScreen实例对象是否为空,//不为空则执行回调方法,也就是把splitscreen对象实例存放到params里面。mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN,splitscreen.createExternalInterface().asBinder()));mOneHandedOptional.ifPresent((onehanded) -> params.putBinder(KEY_EXTRA_SHELL_ONE_HANDED,onehanded.createExternalInterface().asBinder()));params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,mShellTransitions.createExternalInterface().asBinder());mStartingSurface.ifPresent((startingwindow) -> params.putBinder(KEY_EXTRA_SHELL_STARTING_WINDOW,startingwindow.createExternalInterface().asBinder()));params.putBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER,mSmartspaceTransitionController.createExternalInterface().asBinder());mRecentTasks.ifPresent(recentTasks -> params.putBinder(KEY_EXTRA_RECENT_TASKS,recentTasks.createExternalInterface().asBinder()));try {//调用mOverviewProxy的onInitialize,为相关参数进行服务mOverviewProxy.onInitialize(params);} catch (RemoteException e) {mCurrentBoundedUserId = -1;Log.e(TAG_OPS, "ServiceConnection Failed to call onInitialize()", e);}dispatchNavButtonBounds();// Force-update the systemui state flagsupdateSystemUiStateFlags();notifySystemUiStateFlags(mSysUiState.getFlags());notifyConnectionChanged();}};public void startConnectionToCurrentUser() {if (mHandler.getLooper() != Looper.myLooper()) {mHandler.post(mConnectionRunnable);} else {internalConnectToCurrentUser();}}private void internalConnectToCurrentUser() {...代码省略...       Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP).setPackage(mRecentsComponentName.getPackageName());try {//绑定服务mBound = mContext.bindServiceAsUser(launcherServiceIntent,mOverviewServiceConnection,Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,UserHandle.of(getCurrentUserId()));} catch (SecurityException e) {Log.e(TAG_OPS, "Unable to bind because of security error", e);}...代码省略...}public IOverviewProxy getProxy() {return mOverviewProxy;}        }

SystemUI模块的OverviewProxyService类的构造方法会对Launche3模块的TouchInteractionService服务进行绑定,并把调用该服务返回的Binder对象的onInitialize,将Launcher3模块需要的相关参数传了过去,这样Launch3模块才能拿到ISplitScreen的实例对象,通过调用该实例对象的startTasksWithLegacyTransition方法,最终触发分屏模式。那么问题有来了,OverviewProxyService里面的ISplitScreen对象实例是如何被赋值的?

2、重新再来看下OverviewProxyService的构造方法,这次我们重点关注一下mSplitScreenOptional这个对象。

public class OverviewProxyService extends CurrentUserTracker implementsCallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,Dumpable {private final Optional<SplitScreen> mSplitScreenOptional;//触发分屏模式的关键对象@Inject//Dagger2框架注解public OverviewProxyService(Context context, CommandQueue commandQueue,Lazy<NavigationBarController> navBarControllerLazy,Lazy<Optional<StatusBar>> statusBarOptionalLazy,NavigationModeController navModeController,NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,Optional<Pip> pipOptional,Optional<LegacySplitScreen> legacySplitScreenOptional,Optional<SplitScreen> splitScreenOptional,Optional<OneHanded> oneHandedOptional,Optional<RecentTasks> recentTasks,Optional<StartingSurface> startingSurface,BroadcastDispatcher broadcastDispatcher,ShellTransitions shellTransitions,ScreenLifecycle screenLifecycle,SmartspaceTransitionController smartspaceTransitionController,UiEventLogger uiEventLogger,DumpManager dumpManager) {super(broadcastDispatcher);...代码省略...mSplitScreenOptional = splitScreenOptional;//为mSplitScreenOptional赋值...代码省略...}private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {...代码省略...mSplitScreenOptional.ifPresent((splitscreen) -> params.putBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN,//这里调用splitscreen的createExternalInterface方法splitscreen.createExternalInterface().asBinder()));...代码省略...}}
}

OverviewProxyService的构造方法有一个关键注解 @Inject,这个注解是Dagger2的框架注解,该框架会根据我们的配置,当我们需要在某个对象的构造方法中传入特定参数对象的时候,只要添加@Inject注解,该框架会自动帮我们创建参数对象并传入。关于这个框架的原理,我在Android 12系统源码_SystemUI(一)SystemUI的启动流程这篇博客具体分析过,这里不做过多解释。

3、由于后续会多次提到Optional这种类型的数据类型,这里我们需要先简单看下这个类的相关代码。

public final class Optional<T> {private static final Optional<?> EMPTY = new Optional<>();//内部包含的真正对象private final T value;private Optional(T value) {this.value = Objects.requireNonNull(value);}//如果内部对象不为空,则执行consumer方法public void ifPresent(Consumer<? super T> consumer) {if (value != null)consumer.accept(value);}//如果内部对象为空,则返回空对象,执行mapper方法,并将该方法返回的对象封装成Optional<T>类型返回。public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {Objects.requireNonNull(mapper);if (!isPresent())return empty();else {return Optional.ofNullable(mapper.apply(value));}}
}

4、关于Optional这个对象的dagger2框架的配置信息,SystemUI配置在WMComponent这个接口里面的。

frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java

import com.android.wm.shell.dagger.WMShellModule;@WMSingleton//单例
@Subcomponent(modules = {WMShellModule.class})//需要进一步结合WMShellModule做分析
public interface WMComponent {/*** Initializes all the WMShell components before starting any of the SystemUI components.* 在初始化SystemUI组件之前,优先初始化WMShell模块的所有组件*/default void init() {//调用ShellInit的init,这个方法需要额外关注一下,后续我们会再次提到getShellInit().init();}//获取ShellInit对象实例@WMSingletonShellInit getShellInit();//获取Optional<SplitScreen>对象实例@WMSingletonOptional<SplitScreen> getSplitScreen();}
  • 有了以上配置信息,SystemUI模块的任何类的构造方法只要加上 @Inject注解,我们就可以在该对象的构造方法中拿到WMComponent 中返回的对象实例了。
  • 结合getShellInit方法和init方法我们可以知道,SystemUI模块在初始化该模块的SystemUI组件之前,会先初始化WMShell模块的组件,这就意味着SystemUI模块的组件都能拿到WMShell模块的组件,并调用对应的组件所提供的功能。
  • 而Optional到底是如何被创建出来的,这就需要我们进一步查看WMComponent的类注解@Subcomponent指向的WMShellModule这个类的相关代码了。

三、WMShell模块触发分屏

1、SystemUI模块最终是通过WindowManager模块下的Shell模块触发分屏功能的,来看下前面SystemUI模块中dagger2注解框架引用到的WMShellModule这个类。

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java

import com.android.wm.shell.splitscreen.SplitScreenController;@Module(includes = WMShellBaseModule.class)//需要进一步结合WMShellBaseModule做分析
public class WMShellModule {@WMSingleton@Provides@DynamicOverridestatic SplitScreenController provideSplitScreenController(ShellTaskOrganizer shellTaskOrganizer,SyncTransactionQueue syncQueue, Context context,RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,@ShellMainThread ShellExecutor mainExecutor,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController, Transitions transitions,TransactionPool transactionPool, IconProvider iconProvider,Optional<RecentTasksController> recentTasks,Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {//创建SplitScreenController对象实例return new SplitScreenController(shellTaskOrganizer, syncQueue, context,rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,displayInsetsController, transitions, transactionPool, iconProvider,recentTasks, stageTaskUnfoldControllerProvider);}//这个方法我们需要关注一下,后面会提到@WMSingleton@Providesstatic ShellInit provideShellInit(ShellInitImpl impl) {//调用ShellInitImpl的asShellInit方法返回ShellInit对象实例return impl.asShellInit();}@WMSingleton@Providesstatic ShellInitImpl provideShellInitImpl(DisplayController displayController,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,Optional<BubbleController> bubblesOptional,Optional<SplitScreenController> splitScreenOptional,Optional<AppPairsController> appPairsOptional,Optional<PipTouchHandler> pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,Optional<FullscreenUnfoldController> appUnfoldTransitionController,Optional<FreeformTaskListener> freeformTaskListener,Optional<RecentTasksController> recentTasksOptional,Transitions transitions,StartingWindowController startingWindow,@ShellMainThread ShellExecutor mainExecutor) {//创建ShellInitImpl的对象实例return new ShellInitImpl(displayController,displayImeController,displayInsetsController,dragAndDropController,shellTaskOrganizer,bubblesOptional,splitScreenOptional,appPairsOptional,pipTouchHandlerOptional,fullscreenTaskListener,appUnfoldTransitionController,freeformTaskListener,recentTasksOptional,transitions,startingWindow,mainExecutor);}
}

由于WMShellModule的类注解有依赖@Module(includes = WMShellBaseModule.class),要想完全搞明白Optional对象实例是如何被创建的,我们需要进一步结合WMShellBaseModule做分析。

2、WMShellBaseModule的关键代码如下所示。

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java

@Module(includes = WMShellConcurrencyModule.class)
public abstract class WMShellBaseModule {@WMSingleton@Providesstatic Optional<SplitScreen> provideSplitScreen(Optional<SplitScreenController> splitScreenController) {//结合前面Optional<T>这个类的代码可以知道,调用splitScreenController对象的asSplitScreen方法,并将该方法返回的SplitScreen对		        象实例封装成Optional<SplitScreen>类型的对象再返回。return splitScreenController.map((controller) -> controller.asSplitScreen());}@WMSingleton@Providesstatic Optional<SplitScreenController> providesSplitScreenController(@DynamicOverride Optional<SplitScreenController> splitscreenController,Context context) {//AMS是否支持多窗口模式,支持才返回SplitScreenController对象实例,否则返回空if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {return splitscreenController;}return Optional.empty();}
}
  • WMShellBaseModule的provideSplitScreen方法先是获取SplitScreenController对象实例,该对象是通过WMShellModule的provideSplitScreenController方法创建,但是会经过providesSplitScreenController做一层封装,只有当系统开启了支持多窗口模式的开关,也就是AMS支持多窗口模式的时候,才能拿到该对象实例,否则拿到的都是空
  • provideSplitScreen方法在得到该对象实例后,通过调用该对象的asSplitScreen方法,得到了SplitScreen对象实例,但是最终返回的是封装成Optional类型的对象实例返回的。
    到这里我们终于可以确定是SplitScreenController的asSplitScreen方法创建了SplitScreen对象实例。

3、接下来我们继续来梳理一下ISplitScreen和SplitScreenController类相关的代码。

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl

interface ISplitScreen {oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;oneway void setSideStageVisibility(boolean visible) = 3;oneway void removeFromSideStage(int taskId) = 4;oneway void exitSplitScreen(int toTopTaskId) = 5;oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;oneway void startTask(int taskId, int position, in Bundle options) = 7;oneway void startShortcut(String packageName, String shortcutId, int position,in Bundle options, in UserHandle user) = 8;oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int position,in Bundle options) = 9;oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,in Bundle sideOptions, int sidePosition, float splitRatio,in RemoteTransition remoteTransition) = 10;oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,int sideTaskId, in Bundle sideOptions, int sidePosition,float splitRatio, in RemoteAnimationAdapter adapter) = 11;}

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java

public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteCallable<SplitScreenController> {private final SplitScreenImpl mImpl = new SplitScreenImpl();private StageCoordinator mStageCoordinator;public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,SyncTransactionQueue syncQueue, Context context,RootTaskDisplayAreaOrganizer rootTDAOrganizer,ShellExecutor mainExecutor, DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,Optional<RecentTasksController> recentTasks,Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {...代码省略...}public SplitScreen asSplitScreen() {return mImpl;}//这个方法最初是被ShellInitImpl调用的public void onOrganizerRegistered() {if (mStageCoordinator == null) {//创建触发分屏功能的重要对象StageCoordinator的实例。mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);}}    //SplitScreen是一个接口,具体实现是内部类SplitScreenImpl@ExternalThreadprivate class SplitScreenImpl implements SplitScreen {private ISplitScreenImpl mISplitScreen;private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {@Overridepublic void onStagePositionChanged(int stage, int position) {...代码省略...}@Overridepublic void onTaskStageChanged(int taskId, int stage, boolean visible) {...代码省略...}@Overridepublic void onSplitVisibilityChanged(boolean visible) {...代码省略...}};@Overridepublic ISplitScreen createExternalInterface() {if (mISplitScreen != null) {mISplitScreen.invalidate();}mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);//返回实现了ISplitScreen接口的对象实例return mISplitScreen;}}//ISplitScreen是一个aidl,内部类ISplitScreenImpl实现了ISplitScreen的接口方法。@BinderThreadprivate static class ISplitScreenImpl extends ISplitScreen.Stub {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));}void invalidate() {mController = null;}@Overridepublic void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,float splitRatio, RemoteAnimationAdapter adapter) {//这里显示进行权限确认,然后会调用StageCoordinator的startTasksWithLegacyTransition方法。executeRemoteCallWithTaskPermission(mController, "startTasks",(controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,splitRatio, adapter));}}}
  • SplitScreenController的asSplitScreen方法返回了该类的一个内部对象SplitScreenImpl,SplitScreenImpl实现了SplitScreen这个接口的相关方法。
  • 前面第二节第1步OverviewProxyService类中我们有提到,SystemUI在成功绑定Launcher3模块的TouchInteractionService服务的时候,调用了SplitScreen 的createExternalInterface方法,结合这里我们可以知道此方法返回ISplitScreenImpl对象实例,此对象实现了ISplitScreen.aidl文件中声明的接口方法,Launcher3最终得以跨进程调用ISplitScreenImpl的startTasksWithLegacyTransition方法,最终触发分屏模式。
  • ISplitScreenImpl的startTasksWithLegacyTransition方法内部先是做了个权限判断,最终是调用了SplitScreenController的类型为StageCoordinator的内部对象mStageCoordinator的startTasksWithLegacyTransition方法。
  • SplitScreenController的内部属性对象mStageCoordinator是在onOrganizerRegistered方法中被赋值的,该方法最初是被ShellInitImpl对象触发的。

4、来看下ShellInitImpl的相关代码。

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java

public class ShellInitImpl {private static final String TAG = ShellInitImpl.class.getSimpleName();private final Optional<SplitScreenController> mSplitScreenOptional;private final InitImpl mImpl = new InitImpl();public ShellInitImpl(DisplayController displayController,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,Optional<BubbleController> bubblesOptional,Optional<SplitScreenController> splitScreenOptional,Optional<AppPairsController> appPairsOptional,Optional<PipTouchHandler> pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,Optional<FreeformTaskListener> freeformTaskListenerOptional,Optional<RecentTasksController> recentTasks,Transitions transitions,StartingWindowController startingWindow,ShellExecutor mainExecutor) {...代码省略...mSplitScreenOptional = splitScreenOptional;...代码省略...}public ShellInit asShellInit() {return mImpl;}private void init() {...代码省略...mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);...代码省略...}@ExternalThreadprivate class InitImpl implements ShellInit {@Overridepublic void init() {try {//进一步调用ShellInitImpl的Init方法。mMainExecutor.executeBlocking(() -> ShellInitImpl.this.init());} catch (InterruptedException e) {throw new RuntimeException("Failed to initialize the Shell in 2s", e);}}}
}

四、SystemUI模块初始化分屏组件

1、前面第三节第1步WMShellModule类中,我们有提到过和ShellInitImpl对象创建有关的代码。

import com.android.wm.shell.splitscreen.SplitScreenController;@Module(includes = WMShellBaseModule.class)
public class WMShellModule {@WMSingleton@Providesstatic ShellInit provideShellInit(ShellInitImpl impl) {//调用ShellInitImpl的asShellInit方法返回ShellInit对象实例return impl.asShellInit();}@WMSingleton@Providesstatic ShellInitImpl provideShellInitImpl(DisplayController displayController,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,Optional<BubbleController> bubblesOptional,Optional<SplitScreenController> splitScreenOptional,Optional<AppPairsController> appPairsOptional,Optional<PipTouchHandler> pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,Optional<FullscreenUnfoldController> appUnfoldTransitionController,Optional<FreeformTaskListener> freeformTaskListener,Optional<RecentTasksController> recentTasksOptional,Transitions transitions,StartingWindowController startingWindow,@ShellMainThread ShellExecutor mainExecutor) {//创建ShellInitImpl的对象实例return new ShellInitImpl(displayController,displayImeController,displayInsetsController,dragAndDropController,shellTaskOrganizer,bubblesOptional,splitScreenOptional,appPairsOptional,pipTouchHandlerOptional,fullscreenTaskListener,appUnfoldTransitionController,freeformTaskListener,recentTasksOptional,transitions,startingWindow,mainExecutor);}
}

2、前面第二节第4步WMComponent类中,我们有提到SystemUI模块在初始化SystemUI模块的组件之前,会先初始化WMShell模块的所有组件,这自然也包括分屏组件。

@WMSingleton//单例
@Subcomponent(modules = {WMShellModule.class})
public interface WMComponent {/*** Initializes all the WMShell components before starting any of the SystemUI components.* 在初始化SystemUI组件之前,优先初始化WMShell模块的所有组件*/default void init() {//调用ShellInit的initgetShellInit().init();}//获取ShellInit对象实例@WMSingletonShellInit getShellInit();}

WMComponent的init方法先是通过getShellInit方法获取到ShellInit对象实例,InitImpl实现了ShellInit这个接口,
并实现了init方法,该方法会进一步调用ShellInitImpl的init方法,最终会触发SplitScreenController的onOrganizerRegistered方法。

3、SplitScreenController的onOrganizerRegistered方法会创建控制分屏功能的分屏组件StageCoordinator的对象实例。

public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteCallable<SplitScreenController> {private StageCoordinator mStageCoordinator;//这个方法最初是被ShellInitImpl调用的public void onOrganizerRegistered() {if (mStageCoordinator == null) {//创建触发分屏功能的重要对象StageCoordinator的实例。mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);}}    
}

4、StageCoordinator的构造方法如下所示。

class StageCoordinator implements SplitLayout.SplitLayoutHandler,RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController, Transitions transitions,TransactionPool transactionPool, SplitscreenEventLogger logger,Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {mContext = context;mDisplayId = displayId;mSyncQueue = syncQueue;mRootTDAOrganizer = rootTDAOrganizer;mTaskOrganizer = taskOrganizer;mLogger = logger;mMainUnfoldController = unfoldControllerProvider.get().orElse(null);mSideUnfoldController = unfoldControllerProvider.get().orElse(null);//分屏对象mMainStage = new MainStage(mTaskOrganizer,mDisplayId,mMainStageListener,mSyncQueue,mSurfaceSession,mMainUnfoldController);//分屏对象mSideStage = new SideStage(mContext,mTaskOrganizer,mDisplayId,mSideStageListener,mSyncQueue,mSurfaceSession,mSideUnfoldController);mDisplayImeController = displayImeController;mDisplayInsetsController = displayInsetsController;mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);mRootTDAOrganizer.registerListener(displayId, this);final DeviceStateManager deviceStateManager =mContext.getSystemService(DeviceStateManager.class);deviceStateManager.registerCallback(taskOrganizer.getExecutor(),new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,mOnTransitionAnimationComplete);transitions.addHandler(this);} }

由此可知,SystemUI在进程初始化阶段就已经准备好分屏所需要的 MainStage和SideStage 对象,这两个对象很重要,分别负责分屏的一边,对象内部会创建一个 RootTask 节点了(这里利用了WindowOrganizer框架的能力),这个RootTask就是分屏的关键,通过把应用的Task节点挂载到RootTask下,然后修改RootTask节点的Bounds来改变应用显示的大小。

五、WMShell模块触发分屏

1、前面第三步第4节我们有做过分析,Launcher3经过层层调用,最终是调用StageCoordinator的startTasksWithLegacyTransition方法触发分屏功能的,继续来看下StageCoordinator的startTasksWithLegacyTransition方法。

class StageCoordinator implements SplitLayout.SplitLayoutHandler,RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {private final ShellTaskOrganizer mTaskOrganizer;StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController, Transitions transitions,TransactionPool transactionPool, SplitscreenEventLogger logger,IconProvider iconProvider,Optional<RecentTasksController> recentTasks,Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {mContext = context;mDisplayId = displayId;mSyncQueue = syncQueue;mRootTDAOrganizer = rootTDAOrganizer;mTaskOrganizer = taskOrganizer;//为mTaskOrganizer赋值...代码省略...}//Launcher3其实是调用了这个方法触发分屏模式的void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,float splitRatio, RemoteAnimationAdapter adapter) {// Init divider first to make divider leash for remote animation target.setDividerVisibility(true /* visible */);//设置分屏中间的分割线View可见// Set false to avoid record new bounds with old task still on top;mShouldUpdateRecents = false;final WindowContainerTransaction wct = new WindowContainerTransaction();final WindowContainerTransaction evictWct = new WindowContainerTransaction();prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);// Need to add another wrapper here in shell so that we can inject the divider bar// and also manage the process elevation via setRunningRemoteIRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {@Overridepublic void onAnimationStart(@WindowManager.TransitionOldType int transit,RemoteAnimationTarget[] apps,RemoteAnimationTarget[] wallpapers,RemoteAnimationTarget[] nonApps,final IRemoteAnimationFinishedCallback finishedCallback) {mIsDividerRemoteAnimating = true;RemoteAnimationTarget[] augmentedNonApps =new RemoteAnimationTarget[nonApps.length + 1];for (int i = 0; i < nonApps.length; ++i) {augmentedNonApps[i] = nonApps[i];}augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();IRemoteAnimationFinishedCallback wrapCallback =new IRemoteAnimationFinishedCallback.Stub() {@Overridepublic void onAnimationFinished() throws RemoteException {mIsDividerRemoteAnimating = false;mShouldUpdateRecents = true;mSyncQueue.queue(evictWct);mSyncQueue.runInSync(t -> applyDividerVisibility(t));finishedCallback.onAnimationFinished();}};try {try {ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(adapter.getCallingApplication());} catch (SecurityException e) {Slog.e(TAG, "Unable to boost animation thread. This should only happen"+ " during unit tests");}adapter.getRunner().onAnimationStart(transit, apps, wallpapers,augmentedNonApps, wrapCallback);} catch (RemoteException e) {Slog.e(TAG, "Error starting remote animation", e);}}@Overridepublic void onAnimationCancelled() {mIsDividerRemoteAnimating = false;mShouldUpdateRecents = true;mSyncQueue.queue(evictWct);mSyncQueue.runInSync(t -> applyDividerVisibility(t));try {adapter.getRunner().onAnimationCancelled();} catch (RemoteException e) {Slog.e(TAG, "Error starting remote animation", e);}}};RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());if (mainOptions == null) {mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();} else {ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));mainOptions = mainActivityOptions.toBundle();}sideOptions = sideOptions != null ? sideOptions : new Bundle();setSideStagePosition(sidePosition, wct);mSplitLayout.setDivideRatio(splitRatio);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对应的RootTask的Bounds,并将其移动到最前面mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);}// 设置mSideStage对应的RootTask的Bounds,并将其移动到最前面mSideStage.moveToTop(getSideStageBounds(), wct);// Make sure the launch options will put tasks in the corresponding split roots// 配置launch task的option,让分屏应用的task启动到RootTask节点之下addActivityOptions(mainOptions, mMainStage);addActivityOptions(sideOptions, mSideStage);// Add task launch requests// 启动分屏应用,启动方式和从任务管理器启动是一样的,startActivityFromRecentswct.startTask(mainTaskId, mainOptions);wct.startTask(sideTaskId, sideOptions);// Using legacy transitions, so we can't use blast sync since it conflicts.// 所有修改封装到WindowContainerTransaction中然后通过WindowOrganizer框架完成上面的变化mTaskOrganizer.applyTransaction(wct);}}
  • 显示分屏中间的View
  • 设置mMainStage对应的RootTask的Bounds并移动到最前面
  • 设置mSideStage对应的RootTask的Bounds并移动到最前面
  • 启动分屏应用,让分屏应用的task启动到RootTask节点之下,启动方式和从任务管理器启动是一样的,Framework侧对应的就是startActivityFromRecents方法

2、继续来看下ShellTaskOrganizer的applyTransaction方法。

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java

public class ShellTaskOrganizer extends TaskOrganizer implementsCompatUIController.CompatUICallback {
}

frameworks/base/core/java/android/window/TaskOrganizer.java

public class TaskOrganizer extends WindowOrganizer {}

frameworks/base/core/java/android/window/TaskOrganizer.java

public class WindowOrganizer {//applyTransaction是ShellTaskOrganizer的父类方法public void applyTransaction(@NonNull WindowContainerTransaction t) {try {if (!t.isEmpty()) {//调用IWindowOrganizerController的applyTransaction方法getWindowOrganizerController().applyTransaction(t);}} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}static IWindowOrganizerController getWindowOrganizerController() {return IWindowOrganizerControllerSingleton.get();}private static final Singleton<IWindowOrganizerController> IWindowOrganizerControllerSingleton =new Singleton<IWindowOrganizerController>() {@Overrideprotected IWindowOrganizerController create() {try {return ActivityTaskManager.getService().getWindowOrganizerController();} catch (RemoteException e) {return null;}}};
}

applyTransaction方法最终是其父类WindowOrganizer的方法,该方法先是获取到WindowOrganizerController的实例对象,然后调用该对象的applySyncTransaction方法。

3、IWindowOrganizerController是一个aidl,该接口的具体实现类是WindowOrganizerController。

frameworks/base/core/java/android/window/IWindowOrganizerController.aidl


interface IWindowOrganizerController {int applySyncTransaction(in WindowContainerTransaction t,in IWindowContainerTransactionCallback callback);}

frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java

class WindowOrganizerController extends IWindowOrganizerController.Stubimplements BLASTSyncEngine.TransactionReadyListener {@Overridepublic int applySyncTransaction(WindowContainerTransaction t,IWindowContainerTransactionCallback callback) {if (t == null) {throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");}enforceTaskPermission("applySyncTransaction()");final CallerInfo caller = new CallerInfo();final long ident = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {int syncId = -1;if (callback != null) {syncId = startSyncWithOrganizer(callback);}applyTransaction(t, syncId, null /*transition*/, caller);if (syncId >= 0) {setSyncReady(syncId);}return syncId;}} finally {Binder.restoreCallingIdentity(ident);}}}

这里还是运用了WindowOrganizer框架的能力,把所有修改点封装到 WindowContainerTransaction中,然后通过mTaskOrganizer.applyTransaction(wct); 转交给Framework,Framework解析WindowContainerTransaction,然后执行对应的变化
我们可以看看WindowContainerTransaction的内容
在这里插入图片描述

Android12上两个应用都得是从任务管理器中起 startActivityFromRecents
在Android13上支持通过wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions)新起一个应用。

六、通过命令行触发分屏

1、除了调用WMShell模块组件提供的方法触发分屏意外,我们还可以通过命令行来触发分屏。

// taskId 可以通过adb shell am stack list 来查看应用对应的taskId
// SideStagePosition 0 代表左边, 1 代表右边
adb shell dumpsys activity service SystemUIService WMShell moveToSideStage <taskId> <SideStagePosition> 

命令行会把taskId对应的task挂载到SideStage对应的RootTask下,然后SideStage监听到task变化,然后就会激活MainStage,然后申请分屏操作。

2、这部分代码如下。

frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

class StageCoordinator implements SplitLayout.SplitLayoutHandler,RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {private void onStageHasChildrenChanged(StageListenerImpl stageListener) {final boolean hasChildren = stageListener.mHasChildren;final boolean isSideStage = stageListener == mSideStageListener;if (!hasChildren) {if (isSideStage && mMainStageListener.mVisible) {// Exit to main stage if side stage no longer has children.exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED);} else if (!isSideStage && mSideStageListener.mVisible) {// Exit to side stage if main stage no longer has children.exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);}} else if (isSideStage) {//SideStage对应的RootTask监听到task变化,然后就会触发分屏操作final WindowContainerTransaction wct = new WindowContainerTransaction();//Make sure the main stage is active.//这里的reparent是关键,为true后会把后台的Task作为分屏的一部分,如果没有后台task,不能触发分屏mMainStage.activate(getMainStageBounds(), wct, true /* reparent */);mSideStage.moveToTop(getSideStageBounds(), wct);mSyncQueue.queue(wct);mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));}if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {mShouldUpdateRecents = true;updateRecentTasksSplitPair();if (!mLogger.hasStartedSession()) {mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),getMainStagePosition(), mMainStage.getTopChildTaskUid(),getSideStagePosition(), mSideStage.getTopChildTaskUid(),mSplitLayout.isLandscape());}}}}

这里需要注意 mMainStage.activate(getMainStageBounds(), wct, true /* reparent */ ); 这里的reparent是关键,为true后会把后台的Task作为分屏的一部分,如果没有后台task,不能触发分屏,而且命令行分屏由于缺少了Launcher3的参与,缺少分屏之前的动画,效果上就是直接硬切的。

参考文档:https://juejin.cn/post/7346977510514884619

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/15451.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 热插拔AOP,动态的实现AOP【简单易懂,有大用】

B站学习地址 文章目录 一、理论二、核心代码2-1、自定义操作类型枚举2-2、自定义 Advisor2-3、动态添加/删除advisor 工具类2-4、提供测试的 Controller 三、测试3-1、自定义注解3-2、自定义拦截器3-3、测试 四、源码获取 前段时间在学习sentinel和dubbo的时候&#xff0c;很好…

HarmonyOS 鸿蒙应用开发 - 多态样式 stateStyles

前言&#xff1a;Styles和Extend仅仅应用于静态页面的样式复用&#xff0c;stateStyles可以依据组件的内部状态的不同&#xff0c;快速设置不同样式&#xff0c;类似于css伪类&#xff0c;但语法不同。 ArkUI提供以下四种状态&#xff1a; focused&#xff1a;获焦态。normal&…

请解释Java Web应用的开发流程,包括前后端分离和交互方式。请解释Java中的锁分离技术,并讨论其在提高并发性能方面的作用。

请解释Java Web应用的开发流程&#xff0c;包括前后端分离和交互方式。 Java Web应用的开发流程通常包括多个阶段&#xff0c;这些阶段涵盖了从需求分析到部署和维护的全过程。同时&#xff0c;随着技术的发展&#xff0c;前后端分离已经成为了现代Web应用开发的一种主流方式。…

请解释Java Web中的Filter过滤器的作用和常见应用场景。什么是Java Web中的Servlet API?请列举其核心接口和类。

请解释Java Web中的Filter过滤器的作用和常见应用场景。 在Java Web开发中&#xff0c;Filter&#xff08;过滤器&#xff09;是一个非常重要的组件&#xff0c;它位于客户端与服务器端之间&#xff0c;用于拦截客户端发送到服务器的请求&#xff0c;或者在服务器将响应返回给…

就业班 第三阶段(ELK) 2401--5.20 day1 ELK 企业实战 ES+head+kibana+logstash部署(最大集群)

ELKkafkafilebeat企业内部日志分析系统 1、组件介绍 1、Elasticsearch&#xff1a; 是一个基于Lucene的搜索服务器。提供搜集、分析、存储数据三大功能。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasticsearch是用Java开发的&#xff…

Git 仓库中 -- 代码冲突产生、定位、解决的流程

目录 前置知识1 工具环境2 冲突的产生2.1 仓库中的源代码2.2 人员 A 首先更改代码2.3 人员 B 更改代码&#xff0c;产生冲突2.3.1 第一次错误提示&#xff1a;2.3.2 第二次错误提示&#xff1a; 3 查看冲突4 手动解决冲突4.1 方式一4.2 方式二&#xff08;tortoisegit&#xff…

【Linux网络】端口及UDP协议

文章目录 1.再看四层2.端口号2.1引入linux端口号和进程pid的区别端口号是如何生成的传输层有了pid还设置端口号端口号划分 2.2问题2.3netstat 3.UDP协议3.0每学一个协议 都要讨论一下问题3.1UDP协议3.2谈udp/tcp实际上是在讨论什么&#xff1f; 1.再看四层 2.端口号 端口号(Po…

基于Android studio 使用SQLite数据库完成登录注册功能——保姆级教程

&#x1f345;文章末尾有获取完整项目源码方式&#x1f345; 点击快捷传送地址&#xff1a; 保姆级教学——制作登陆注册功能页面 目录 一、准备工作 二、创建相关文件 三、页面布局 四、DabaHelper帮助类的编写 五、RegisterActivity注册页面 六、LoginActivity登录页面…

Dev-c++的资本道路

Dev-c是美国发明的&#xff0c;c原来是编写软件的东西。可是&#xff0c;正所谓一方水土养一方人&#xff0c;美国是一个发达的资本主义国家&#xff1b;所以&#xff0c;一些投靠资本的&#xff0c;高层的美国人&#xff0c;用c赚取了许多利益。底层的人在用&#xff0c;高层的…

代码随想录算法训练营第三十七天|435. 无重叠区间、763.划分字母区间、56. 合并区间、738.单调递增的数字、968.监控二叉树

435. 无重叠区间 文档讲解&#xff1a;代码随想录 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 本道题与上个题目相似&#xff0c;都是求重叠区间 统计重叠区间的个数&#xff0c;减去重叠区间的个数就是无重叠区间了 主要就是为了让区间尽可能的重叠。&a…

机器学习中的时卷积神经网络

时卷积神经网络(Temporal Convolutional Network, TCN)是一种特殊的卷积神经网络架构,它主要用于处理时间序列数据。与传统的卷积神经网络(Convolutional Neural Network, CNN)相比,TCN有以下几个主要特点: 1. 因果性(Causality): - 传统的CNN在特定位置的输出取决于当前及…

微信小程序源码-基于Java后端的会议发布与预约系统毕业设计(附源码+演示录像+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设…

数字识别技术

数字识别技术是一种在人工智能和计算机视觉领域广泛应用的技术&#xff0c;旨在从图像中识别出数字&#xff0c;并将其正确地分类为0到9之间的一个数字。以下是对数字识别技术的详细介绍&#xff1a; 一、数字识别技术的定义与应用 数字识别技术是一种计算机视觉任务&#xf…

Java的结构与运行机制

1. JDK JRE JVM三者的区别 JDK(Java Development Kit)&#xff1a;Java开发工具包 JDK包含JRE&#xff0c;还包括其他例如&#xff1a;编译器(javac)、javadoc、jar等&#xff0c;JDK是能够创建和编译程序的。 JRE(Java runtime environment)&#xff1a;Java运行环境 JRE是运…

手把手一起学习Python NumPy

NumPy 是用于处理数组的 python 库&#xff0c;NumPy 中的数组对象称为 ndarray&#xff0c;它提供了许多支持函数&#xff0c;使得利用 ndarray 非常容易。Numpy官方网址 NumPy 安装 使用pip安装NumPy 模块&#xff1a; pip install numpyNumPy 入门 创建numpy数组&#x…

虚拟化技术[4]之桌面虚拟化

桌面虚拟化 桌面虚拟化技术现状&#xff1a;桌面虚拟化技术问题案例分析&#xff1a;VMware View 桌面虚拟化 每个桌面镜像是一个带有应用程序的操作系统&#xff0c;终端用户通过一个虚拟显示协议来访问桌面系统。目的是使用户的使用体验同使用桌面上的PC一样。 桌面虚拟化是一…

基于Python的k-means聚类分析算法的实现与应用,可以用在电商评论、招聘信息等各个领域的文本聚类及指标聚类,效果很好

以微博考研话题为例 思路步骤&#xff1a; 数据清洗&#xff1a; 使用pandas读取数据文件&#xff0c;并进行数据清洗和预处理&#xff0c;包括去除重复值、数据替换等。 数据处理实现&#xff1a; 数据处理的过程如下&#xff1a; 数据清洗主要包括去重和数据转换两个步骤…

Magisk + JustTrustMe 安装配置

操作步骤&#xff1a; 安装 Magisk 面具&#xff08;手机root&#xff09;在面具中刷入 LSPosed框架安装 JustTrustMe在LSPosed框架中配置并启动 JustTrustMe 一&#xff0c;Magisk面具 请根据自己手机的机型去root并安装面具&#xff0c;参考链接&#xff1a; https://www…

代码随想录算法训练营Day50 | 309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费、股票问题总结 | Python | 个人记录向

本文目录 309.最佳买卖股票时机含冷冻期做题看文章 714.买卖股票的最佳时机含手续费做题看文章 股票问题总结以往忽略的知识点小结个人体会 309.最佳买卖股票时机含冷冻期 代码随想录&#xff1a;309.最佳买卖股票时机含冷冻期 Leetcode&#xff1a;309.最佳买卖股票时机含冷冻…

QAnything 1.4.1 中的文档解析

2024年初我们开源了QAnything&#xff0c;一个基于检索增强生成式应用&#xff08;RAG&#xff09;的本地知识库问答系统。对于本地知识库&#xff0c;QAnything支持多种格式的文档输入&#xff0c;允许用户上传包括PDF、图片、Word、PowerPoint、Excel、TXT&#xff0c;甚至音…