赞
踩
上一篇我们具体分析了系统处于多窗口模式下,Android应用和多窗口模式相关方法的调用顺序,对于应用如何适配多窗口模式有了一个初步的认识,本篇文章我们将会结合Android12系统源码,具体来梳理一下系统是如何触发多窗口分屏模式,以及实现多窗口分屏模式功能的原理。
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类型的实例对象。
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(); }
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(); } }
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)); } } }
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); } } } }
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来改变应用显示的大小。
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); } }
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。