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
相关文章:
Android 12系统源码_多窗口模式(二)系统实现分屏的功能原理
前言 上一篇我们具体分析了系统处于多窗口模式下,Android应用和多窗口模式相关方法的调用顺序,对于应用如何适配多窗口模式有了一个初步的认识,本篇文章我们将会结合Android12系统源码,具体来梳理一下系统是如何触发多窗口分屏模…...
字符函数:分类函数与转换函数
字符函数 一.字符分类函数二.字符转换函数 在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语⾔标准库中提供了一系列库函数,接下来我们就学习⼀下这些函数。 一.字符分类函数 C语言中有⼀系列的函数是专门…...
SpringBoot 集成Mybatis
SpringBoot集成第三方技术,一般都分为导坐标,改配置,写代码三个步骤。 集成Mybatis也类似,新建一个SpringBoot项目。修改:pom.xml文件。 一、导入坐标 <!--druid--> <dependency><groupId>com.al…...
C语言-atoi()库函数的模拟实现
文章目录 前言一、atoi()库函数的介绍及使用1.1 atoi()库函数介绍1.2 atoi()库函数使用 二、atoi()库函数的模拟实现2.1 函数设计2.2 函数实现思路2.3 具体实现2.4 测试 总结 前言 本篇文章介绍c语言中库函数atoi()的使用,以及模拟实现库函数。 一、atoi()库函数的…...
定时监测服务器磁盘是否超过阈值,超过就删除docker 镜像
达到指定百分比 删除镜像脚本 df -h 查找到 内存占用信息 ,得到的 文件系统名称是 overlay的,Use% 达到70就进行删除docker 镜像 #!/bin/bash# 设置磁盘使用阈值 THRESHOLD70# 获取 overlay 文件系统的磁盘使用百分比 DISK_USAGES$(df -h | grep overl…...
UDP聊天室
服务器端 #include <myhead.h>#define SER_IP "192.168.124.38" #define SER_PORT 8888 #define RBUFSIZE 128 #define WBUFSIZE 128typedef struct node{char usrName[20];struct sockaddr_in cli_sockaddr;struct node* next; }node, *node_p;node_p create…...
LLM多模态——GPT-4o改变人机交互的多模式 AI 模型应用
1. 概述 OpenAI 发布了迄今为止最新、最先进的语言模型 – GPT-4o也称为“全“ 模型。这一革命性的人工智能系统代表了一次巨大的飞跃,其能力模糊了人类和人工智能之间的界限。 GPT-4o 的核心在于其原生的多模式特性,使其能够无缝处理和生成文本、音频…...
安卓手机APP开发__蓝牙功能概述
安卓手机APP开发__蓝牙功能概述 目录 概述 基本内容 关键的类和接口 概述 安卓平台支持了蓝牙网络栈,它允许一个设备和其它的蓝牙设备进行无线的交换数据。 APP的框架…...
get和post的区别,二者是幂等的吗?
一、什么是幂等 所谓幂等性通俗的将就是一次请求和多次请求同一个资源产生相同的副作用。 维基百科定义:幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 在编程中一个幂等操作的特点是其任意多次执…...
农场--Kruskal应用--c++
【题目要求】 农场里有一些奶牛,作为食物的草料不够了。农场主需要去别的农场借草料。该地区有N (2 < N < 2,000) 个农场,农场名称用数字N标识,农场之间的道路是双向的,一共有M (1 < M < 10,000)条道路,单…...
【Crypto】Rabbit
文章目录 一、Rabbit解题感悟 一、Rabbit 题目提示很明显是Rabbit加密,直接解 小小flag,拿下! 解题感悟 提示的太明显了...
IRFB3207PBF TO-220 N沟道75V/180A 直插MOSFET场效应管
英飞凌(Infineon)的 IRFB3207PBF 是一款高性能的 N 沟道 MOSFET,适用于多种电子设备和系统中的高侧开关应用。以下是 IRFB3207PBF 的一些典型应用场景: 1. 电源管理:在电源管理系统中,IRFB3207PBF 可以作为…...
基于单张图片快速生成Metahuman数字人(模型贴图绑定)的工作流演示
基于单张图片快速生成Metahuman数字人(模型贴图绑定)的工作流演示 MetahumanModeler, 是我基于facebuilder以及metahuman的理解开发而成,插件可以基于单张图片生成metahuman拓扑结构的面部3d模型,同时生成对应的面部的贴图&#…...
MySQL数据库下的Explain命令深度解析
Explain是一个非常有的命令,可以用来获取关于查询执行计划的信息,以及如何解释输出。Explain命令是查看查询优化器如何决定执行查询的主要方法。这个功能有一定的局限性,并不总是会说出真相,但是它的输出是可以获取的最好信息&…...
防火墙技术基础篇:基于IP地址的转发策略
防火墙技术基础篇:基于IP地址的转发策略的应用场景及实现 什么是基于IP地址的转发策略? 基于IP地址的转发策略是一种网络管理方法,它允许根据目标IP地址来选择数据包的转发路径。这种策略比传统的基于目的地地址的路由更灵活,因…...
OpenFeign快速入门 替代RestTemplate
1.引入依赖 <!--openFeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--负载均衡器--><dependency><groupId>org.spr…...
自动化测试--利用pytest实现整条业务链路测试
概述 前面一章讲解了单个接口的测试,但是实际项目中,因为权限和登录状态的限制,大部分接口没办法直接访问到,这时候我们想访问到一个系统的接口,就需要模拟用户登录拿到用户的token和所拥有的权限之后再将这些信息…...
学习其他推理判断
学习其他推理判断 1.类比推理1.1语义关系1.2逻辑关系1.3 语法关系2.定义判断3.翻译推理3.1前推后:A→B3.2后推前:B→A3.3推理规则4.组合排列5.日常结论6.逻辑论证6.1削弱题型6.2加强题型7.原因解释1.类比推理 类比推理:给出一组相关的词,通过观察分析,在备选答案中找出一组…...
Centos7环境下MySQL5.7.38 安装开源审计插件 mysql-audit
MySQL安装开源审计插件 mysql-audit MySQL 5.7.38安装审计插件 mysql-audit安装MySQL1.查看Linux服务器版本和glibc版本2.根据自己的系统下载对应的MySQL版本,由于mysql-audit并不支持所有版本的MySQL,所以在确定MySQL版本之前请注意下插件支持的MySQL版…...
基于深度学习的表情识别系统
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着人工智能技术的快速发展,表情识别成为了人机交互领域的一个研究热点。表情识别技术旨…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
