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

news2024/11/24 8:14:20

前言

上一篇我们具体分析了系统处于多窗口模式下,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 {

    @Override
    public 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
    @Override
    public 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 running
        recentsView.addSideTaskLaunchCallback(endCallback);
        return endCallback;
    }

    @Override
    public 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 screen
        final 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 Service
        implements ProtoTraceable<LauncherTraceProto.Builder> {
  
    private final TISBinder mTISBinder = new TISBinder();
            
    public class TISBinder extends IOverviewProxy.Stub {

        @BinderThread
        public 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;
        }
     }
     
     @Override
    public 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 implements
        CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
        Dumpable {
    
        private final Optional<SplitScreen> mSplitScreenOptional;//触发分屏模式的关键对象

        //唤起Launcher3模块TouchInteractionService的Action
        private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
        //唤起Launcher3模块TouchInteractionService的Intent
        private 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() {
            @Override
            public 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 flags
	            updateSystemUiStateFlags();
	            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 implements
        CallbackController<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() {
            @Override
            public 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对象实例
    @WMSingleton
    ShellInit getShellInit();

	//获取Optional<SplitScreen>对象实例
    @WMSingleton
    Optional<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
    @DynamicOverride
    static 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
    @Provides
    static ShellInit provideShellInit(ShellInitImpl impl) {
    	//调用ShellInitImpl的asShellInit方法返回ShellInit对象实例
        return impl.asShellInit();
    }

    @WMSingleton
    @Provides
    static 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
    @Provides
    static Optional<SplitScreen> provideSplitScreen(
            Optional<SplitScreenController> splitScreenController) {
            //结合前面Optional<T>这个类的代码可以知道,调用splitScreenController对象的asSplitScreen方法,并将该方法返回的SplitScreen对		        象实例封装成Optional<SplitScreen>类型的对象再返回。
        return splitScreenController.map((controller) -> controller.asSplitScreen());
    }

    @WMSingleton
    @Provides
    static 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
    @ExternalThread
    private class SplitScreenImpl implements SplitScreen {
        private ISplitScreenImpl mISplitScreen;
        private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
        private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
       
            @Override
            public void onStagePositionChanged(int stage, int position) {
      			...代码省略...
            }

            @Override
            public void onTaskStageChanged(int taskId, int stage, boolean visible) {
      			...代码省略...
            }

            @Override
            public void onSplitVisibilityChanged(boolean visible) {
      			...代码省略...
            }
        };

        @Override
        public ISplitScreen createExternalInterface() {
            if (mISplitScreen != null) {
                mISplitScreen.invalidate();
            }
            mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
           	//返回实现了ISplitScreen接口的对象实例
            return mISplitScreen;
        }

    }

	//ISplitScreen是一个aidl,内部类ISplitScreenImpl实现了ISplitScreen的接口方法。
    @BinderThread
    private static class ISplitScreenImpl extends ISplitScreen.Stub {
        private SplitScreenController mController;
        private final SingleInstanceRemoteListener<SplitScreenController,
                ISplitScreenListener> mListener;
        private final SplitScreen.SplitScreenListener mSplitScreenListener =
                new SplitScreen.SplitScreenListener() {
                    @Override
                    public void onStagePositionChanged(int stage, int position) {
                        mListener.call(l -> l.onStagePositionChanged(stage, position));
                    }

                    @Override
                    public void onTaskStageChanged(int taskId, int stage, boolean visible) {
                        mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
                    }
                };

        public ISplitScreenImpl(SplitScreenController controller) {
            mController = controller;
            mListener = new SingleInstanceRemoteListener<>(controller,
                    c -> c.registerSplitScreenListener(mSplitScreenListener),
                    c -> c.unregisterSplitScreenListener(mSplitScreenListener));
        }

        void invalidate() {
            mController = null;
        }

        @Override
        public 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);
		...代码省略...
    }

    @ExternalThread
    private class InitImpl implements ShellInit {
        @Override
        public 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
    @Provides
    static ShellInit provideShellInit(ShellInitImpl impl) {
    	//调用ShellInitImpl的asShellInit方法返回ShellInit对象实例
        return impl.asShellInit();
    }
    
    @WMSingleton
    @Provides
    static 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的init
        getShellInit().init();
    }

	//获取ShellInit对象实例
    @WMSingleton
    ShellInit 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 setRunningRemote
        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
            @Override
            public 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() {
                            @Override
                            public 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);
                }
            }

            @Override
            public 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
        // 启动分屏应用,启动方式和从任务管理器启动是一样的,startActivityFromRecents
        wct.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 implements
        CompatUIController.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>() {
            @Override
            protected 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.Stub
    implements BLASTSyncEngine.TransactionReadyListener {
    
     @Override
    public 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.coloradmin.cn/o/1689927.html

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

相关文章

StringMVC

目录 一&#xff0c;MVC定义 二&#xff0c;SpringMVC的基本使用 2.1建立连接 - RequestMapping("/...") ​编辑 2.2请求 1.传递单个参数 2.传递多个参数 3.传递对象 4.参数重命名 5.传递数组 6. 传递集合 7.传递JSON数据 8. 获取url中数据 9. 传递文…

mysql实战——异步复制(gtid复制)

一、搭建前准备 主库 192.168.1.76 从库 192.168.1.78 二、搭建 1、编辑配置文件 主库 server-id76 gtid_modeon enforce_gtid_consistencyon log_binmaster-binlog log-slave-updates1 binlog_formatrow 从库 gtid_modeon enforce_gtid_consistencyon server_id7…

huggingface笔记:LLama 2

1 前提tip 1.1 使用什么数据类型训练模型&#xff1f; Llama2模型是使用bfloat16训练的 上传到Hub的检查点使用torch_dtype float16&#xff0c;这将通过AutoModel API将检查点从torch.float32转换为torch.float16。在线权重的数据类型通常无关紧要&#xff0c;这是因为模型…

基于springboot+vue+Mysql的校园台球厅人员与设备管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

在docker中运行SLAM十四讲程序

《十四讲》的示例程序依赖比较多&#xff0c;而且系统有点旧。可以在容器中运行。 拉取镜像 docker pull ddhogan/slambook:v0.1这个docker对应的github&#xff1a;HomeLH/slambook2-docker 拉下来之后&#xff0c;假如是Windows系统&#xff0c;需要使用XLaunch用于提供X11…

Playwright 隐藏浏览器指纹特征:注入stealth.min.js

引言 浏览器指纹技术通过分析用户的浏览器和操作系统信息来识别用户&#xff0c;这包括浏览器类型、版本、插件、屏幕分辨率等。在自动化测试和爬虫操作中&#xff0c;这些信息可能会暴露脚本的身份&#xff0c;导致被目标网站阻止。Playwright是一个跨浏览器的自动化库&#…

Spring Security整合Gitee第三方登录

文章目录 学习链接环境准备1. 搭建基本web应用引入依赖ThirdApp启动类创建index页面application.yml配置访问测试 2. 引入security引入依赖ProjectConfig访问测试 第三方认证简介注册gitee客户端实现1引入依赖application.yml配置文件创建index.html页面启动类InfoControllerPr…

html中table表格的行、列怎么进行合并

在HTML中&#xff0c;使用 <table> 元素来创建表格&#xff0c;而行&#xff08;tr&#xff09;和列&#xff08;td或th&#xff09;的合并可以通过 colspan和 rowspan 属性来实现。这两个属性允许单个表格单元格&#xff08;td或th&#xff09;跨越多个列或行。 colspa…

【FixBug】超级大Json转POJO失败

今天遇到了一个问题&#xff1a;使用Jackson将一个超级大的JSON字符串转换POJO失败&#xff0c;debug看没问题&#xff0c;将JSON字符串粘贴到main方法中测试&#xff0c;提示错误信息如下&#xff1a; 自己猜测是因为字符串超长导致转换时先截断字符串导致JSON格式不正确&…

QT5.15.2及以上版本安装

更新时间&#xff1a;2024-05-20 安装qt5.15以上版本 系统&#xff1a;ubuntu20.04.06 本文安装&#xff1a;linux-5.15.2 下载安装 # 安装编译套件g sudo apt-get install build-essential #安装OpenGL sudo apt-get install libgl1-mesa-dev# 下载qt安装器 https://downl…

安卓 逆向高级-人均瑞数

引言&#xff1a; JS 爬虫&#xff0c;绕不过瑞数这道坎&#xff0c;卡的死死的。一般网上的教程就是补环境什么的&#xff0c;我尝试了&#xff0c;可以但是比较麻烦。 今天说一种&#xff0c;秒过的方式&#xff0c;抗并发。那就是牛逼的RPC&#xff0c;hook JS 技术。 前期…

RedisTemplateAPI:List

文章目录 ⛄介绍⛄List的常见命令有⛄RedisTemplate API❄️❄️添加缓存❄️❄️将List放入缓存❄️❄️设置过期时间(单独设置)❄️❄️获取List缓存全部内容&#xff08;起始索引&#xff0c;结束索引&#xff09;❄️❄️从左或从右弹出一个元素❄️❄️根据索引查询元素❄…

【日记】跟奇安信斗智斗勇,败下阵来(416 字)

正文 今天一个客户都没有&#xff0c;让我快怀疑我们银行是不是要倒闭了…… 因为内外网 u 盘不知所踪&#xff0c;所以重新制了一个。深刻体会到了奇安信有多烂。有两个 u 盘&#xff0c;奇安信似乎把主控写坏了&#xff0c;插上电脑有反应&#xff0c;但是看不见盘符&#xf…

游戏陪玩/在线租号/任务系统网站源码

源码介绍 游戏陪玩系统/在线租号系统/小姐姐陪玩任务系统/网游主播任务威客平台源码/绝地吃鸡LOL在线下单/带手机端/声优线上游戏任务系统网站源码 界面美观,功能齐全,已对接支付,安装教程放源码压缩包里了! 界面截图 源码下载 https://download.csdn.net/download/huayula…

【Fiddler抓包工具】第四节.断点设置和弱网测试

文章目录 前言一、断点设置 1.1 全局断点 1.2 局部断点 1.3 打断点的几种常用命令 1.4 篡改响应报文二、弱网测试 2.1 网络限速 2.2 精准限速总结 前言 一、断点设置 1.1 全局断点 特点&#xff1a; 中断Fiddler捕获的所有请求&#xff0c;包括…

系统架构师考试(九)

TCP/IP协议族 SMTP是简单邮件传输协议 DNS 域名解析协议 URL - IP&#xff0c;通过URL解析ip是哪一台电脑 DHCP 动态IP地址分配的协议 SNMP 简单网络管理协议 TFTP 简单文件管理协议 ICMP 是网络中差错校验&#xff0c;差错报错的协议 IGMP G是组&#xff0c;组…

cuda11.2安装哪个版本的tensorflow-gpu

在官网上找到这个表格&#xff0c;因为自己的电脑一直配置的11.2的cuda&#xff0c;所以也不想换&#xff0c;最好就是安装一般能适应该版本的tensorflow&#xff0c;我配置了python3.8的环境&#xff0c;然后进行 pip install tensorflow-gpu2.6 回车就会自动从清华镜像上进…

基于vue3速学angular

因为工作原因&#xff0c;需要接手新的项目&#xff0c;新的项目是angular框架的&#xff0c;自学下和vue3的区别&#xff0c;写篇博客记录下&#xff1a; 参考&#xff1a;https://zhuanlan.zhihu.com/p/546843290?utm_id0 1.结构上&#xff1a; vue3:一个vue文件&#xff…

金职优学:分析央国企面试如何通关?

在当今竞争激烈的就业市场中&#xff0c;中央和国有企业&#xff08;以下简称“央国企”&#xff09;的面试机会对求职者来说是非常有吸引力的。这些企业通常拥有稳定的发展前景、良好的薪酬福利和广阔的职业发展空间。但是&#xff0c;要想成功通过央国企的面试&#xff0c;求…

力扣HOT100 - 31. 下一个排列

解题思路&#xff1a; 数字是逐步增大的 步骤如下&#xff1a; class Solution {public void nextPermutation(int[] nums) {int i nums.length - 2;while (i > 0 && nums[i] > nums[i 1]) i--;if (i > 0) {int j nums.length - 1;while (j > 0 &&…