当前位置: 首页 > news >正文

电商网站首页开发工装公司经营范围

电商网站首页开发,工装公司经营范围,网站建设计划,网站建设公司如何签单前言 上一篇我们具体分析了系统处于多窗口模式下#xff0c;Android应用和多窗口模式相关方法的调用顺序#xff0c;对于应用如何适配多窗口模式有了一个初步的认识#xff0c;本篇文章我们将会结合Android12系统源码#xff0c;具体来梳理一下系统是如何触发多窗口分屏模…前言 上一篇我们具体分析了系统处于多窗口模式下Android应用和多窗口模式相关方法的调用顺序对于应用如何适配多窗口模式有了一个初步的认识本篇文章我们将会结合Android12系统源码具体来梳理一下系统是如何触发多窗口分屏模式以及实现多窗口分屏模式功能的原理。 一、Launcher3触发分屏 1、Android12的分屏模式触发入口默认是在最近任务列表中的而最近任务列表是包含在Launcher3里面的当我们在最近任务列表中点击分屏按钮后会先触发Launcher进入分屏的一系列悬浮动画以及初始的图标分屏。 以上步骤都属于Launcher的业务逻辑。 2、接下来我们结合系统源码来简单看下Launcher3模块是如何触发分屏功能的。 packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java public class QuickstepLauncher extends BaseQuickstepLauncher {Overridepublic void onStateSetEnd(LauncherState state) {super.onStateSetEnd(state);switch (state.ordinal) {...代码省略...case QUICK_SWITCH_STATE_ORDINAL: {RecentsView rv getOverviewPanel();TaskView tasktolaunch rv.getTaskViewAt(0);if (tasktolaunch ! null) {//调用TaskView的launchTask方法tasktolaunch.launchTask(success - {if (!success) {getStateManager().goToState(OVERVIEW);} else {getStateManager().moveToRestState();}});} else {getStateManager().goToState(NORMAL);}break;}}}}package/apps/Launcher3/quickstep/src/com/android/quickstep/views/GroupedTaskView.java public class GroupedTaskView extends TaskView {NullableOverridepublic RunnableList launchTaskAnimated() {if (mTask null || mSecondaryTask null) {return null;}RunnableList endCallback new RunnableList();RecentsView recentsView getRecentsView();// Callbacks run from remote animation when recents animation not currently running//调用RecentsView的getSplitPlaceholder方法获取SplitSelectStateController对象实例调用launchTasks方法recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,success - endCallback.executeAllAndDestroy(),false /* freezeTaskList */);// Callbacks get run from recentsView for case when recents animation already runningrecentsView.addSideTaskLaunchCallback(endCallback);return endCallback;}Overridepublic void launchTask(NonNull ConsumerBoolean 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 RecentsViewACTIVITY_TYPE extends StatefulActivitySTATE_TYPE,STATE_TYPE extends BaseStateSTATE_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,ConsumerBoolean callback, boolean freezeTaskList, float splitRatio) {// Assume initial task is for top/left part of screenfinal int[] taskIds stagePosition STAGE_POSITION_TOP_OR_LEFT? new int[]{task1.key.id, task2.key.id}: new int[]{task2.key.id, task1.key.id};if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {RemoteSplitLaunchTransitionRunner animationRunner new RemoteSplitLaunchTransitionRunner(task1, task2);mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,ActivityThread.currentActivityThread().getApplicationThread()));} else {RemoteSplitLaunchAnimationRunner animationRunner new RemoteSplitLaunchAnimationRunner(task1, task2, callback);//转场动画final RemoteAnimationAdapter adapter new RemoteAnimationAdapter(RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),300, 150,ActivityThread.currentActivityThread().getApplicationThread());ActivityOptions mainOpts ActivityOptions.makeBasic();if (freezeTaskList) {mainOpts.setFreezeRecentTasksReordering();}//调用SystemUiProxy的startTasksWithLegacyTransition方法mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,splitRatio, adapter);}}}packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java public class SystemUiProxy implements ISystemUiProxy,SysUINavigationMode.NavigationModeChangeListener {public static final MainThreadInitializedObjectSystemUiProxy INSTANCE new MainThreadInitializedObject(SystemUiProxy::new);private ISplitScreen mSplitScreen;public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,IOneHanded oneHanded, IShellTransitions shellTransitions,IStartingWindow startingWindow, IRecentTasks recentTasks,ISmartspaceTransitionController smartSpaceTransitionController) {...代码省略...mSplitScreen splitScreen;...代码省略.../*** 分屏模式同时打开多个任务*/public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,Bundle sideOptions, SplitConfigurationOptions.StagePosition int sidePosition,float splitRatio, RemoteAnimationAdapter adapter) {if (mSystemUiProxy ! null) {try {//调用ISplitScreen的startTasksWithLegacyTransition方法触发分屏mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,sideOptions, sidePosition, splitRatio, adapter);} catch (RemoteException e) {Log.w(TAG, Failed call startTasksWithLegacyTransition);}}}}通过梳理以上代码可以发现Launche3最终是通过调用SystemUiProxy的startTasksWithLegacyTransition方法触发分屏的而该方法内部又进一步调用了类型为ISplitScreen的mSplitScreen对象的startTasksWithLegacyTransition方法。 3、SystemUiProxy的内部属性对象mSplitScreen最初是在TouchInteractionService的内部类TISBinder的onInitialize方法中被赋值的。 packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java public class TouchInteractionService extends Serviceimplements ProtoTraceableLauncherTraceProto.Builder {private final TISBinder mTISBinder new TISBinder();public class TISBinder extends IOverviewProxy.Stub {BinderThreadpublic void onInitialize(Bundle bundle) {ISystemUiProxy proxy ISystemUiProxy.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));IPip pip IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));//触发分屏就是调用的这个对象的方法ISplitScreen splitscreen ISplitScreen.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN));IOneHanded onehanded IOneHanded.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));IShellTransitions shellTransitions IShellTransitions.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));IStartingWindow startingWindow IStartingWindow.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));ISmartspaceTransitionController smartspaceTransitionController ISmartspaceTransitionController.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER));IRecentTasks recentTasks IRecentTasks.Stub.asInterface(bundle.getBinder(KEY_EXTRA_RECENT_TASKS));MAIN_EXECUTOR.execute(() - {//调用SystemUiProxy的setProxy方法SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,smartspaceTransitionController);TouchInteractionService.this.initInputMonitor();preloadOverview(true /* fromInit */);});sIsInitialized true;}}Overridepublic IBinder onBind(Intent intent) {Log.d(TAG, Touch service connected: user getUserId());return mTISBinder;}} packages/apps/Launcher3/quickstep/AndroidManifest.xml ?xml version1.0 encodingutf-8? manifest xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:toolshttp://schemas.android.com/toolspackagecom.android.launcher3application android:backupAgentcom.android.launcher3.LauncherBackupAgentservice android:namecom.android.quickstep.TouchInteractionServiceandroid:permissionandroid.permission.STATUS_BAR_SERVICEandroid:directBootAwaretrueandroid:exportedtrueintent-filteraction android:nameandroid.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 implementsCallbackControllerOverviewProxyListener, NavigationModeController.ModeChangedListener,Dumpable {private final OptionalSplitScreen mSplitScreenOptional;//触发分屏模式的关键对象//唤起Launcher3模块TouchInteractionService的Actionprivate static final String ACTION_QUICKSTEP android.intent.action.QUICKSTEP_SERVICE;//唤起Launcher3模块TouchInteractionService的Intentprivate final Intent mQuickStepIntent;//远程IPC通信是实现类private IOverviewProxy mOverviewProxy;private boolean mBound;public OverviewProxyService(Context context, CommandQueue commandQueue,LazyNavigationBarController navBarControllerLazy,LazyOptionalStatusBar statusBarOptionalLazy,NavigationModeController navModeController,NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,OptionalPip pipOptional,OptionalLegacySplitScreen legacySplitScreenOptional,OptionalSplitScreen splitScreenOptional,OptionalOneHanded oneHandedOptional,OptionalRecentTasks recentTasks,OptionalStartingSurface startingSurface,BroadcastDispatcher broadcastDispatcher,ShellTransitions shellTransitions,ScreenLifecycle screenLifecycle,SmartspaceTransitionController smartspaceTransitionController,UiEventLogger uiEventLogger,DumpManager dumpManager) {super(broadcastDispatcher);...代码省略...//获取最近应用列表组件名称其实就是Launcher3的包名mRecentsComponentName ComponentName.unflattenFromString(context.getString(com.android.internal.R.string.config_recentsComponentName));//创建最近应用列表Activity的意图对象mQuickStepIntent new Intent(ACTION_QUICKSTEP).setPackage(mRecentsComponentName.getPackageName());...代码省略...startConnectionToCurrentUser();...代码省略...}//成功绑定服务所返回的ServiceConnection对象private final ServiceConnection mOverviewServiceConnection new ServiceConnection() {Overridepublic void onServiceConnected(ComponentName name, IBinder service) {...代码省略...mCurrentBoundedUserId getCurrentUserId();//为mOverviewProxy赋值mOverviewProxy IOverviewProxy.Stub.asInterface(service);Bundle params new Bundle();params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);mPipOptional.ifPresent((pip) - params.putBinder(KEY_EXTRA_SHELL_PIP,pip.createExternalInterface().asBinder()));//关键对象Optional对象的的ifPresent方法会判断该对象内部的SplitScreen实例对象是否为空//不为空则执行回调方法也就是把splitscreen对象实例存放到params里面。mSplitScreenOptional.ifPresent((splitscreen) - params.putBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN,splitscreen.createExternalInterface().asBinder()));mOneHandedOptional.ifPresent((onehanded) - params.putBinder(KEY_EXTRA_SHELL_ONE_HANDED,onehanded.createExternalInterface().asBinder()));params.putBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,mShellTransitions.createExternalInterface().asBinder());mStartingSurface.ifPresent((startingwindow) - params.putBinder(KEY_EXTRA_SHELL_STARTING_WINDOW,startingwindow.createExternalInterface().asBinder()));params.putBinder(KEY_EXTRA_SMARTSPACE_TRANSITION_CONTROLLER,mSmartspaceTransitionController.createExternalInterface().asBinder());mRecentTasks.ifPresent(recentTasks - params.putBinder(KEY_EXTRA_RECENT_TASKS,recentTasks.createExternalInterface().asBinder()));try {//调用mOverviewProxy的onInitialize为相关参数进行服务mOverviewProxy.onInitialize(params);} catch (RemoteException e) {mCurrentBoundedUserId -1;Log.e(TAG_OPS, ServiceConnection Failed to call onInitialize(), e);}dispatchNavButtonBounds();// Force-update the systemui state flagsupdateSystemUiStateFlags();notifySystemUiStateFlags(mSysUiState.getFlags());notifyConnectionChanged();}};public void startConnectionToCurrentUser() {if (mHandler.getLooper() ! Looper.myLooper()) {mHandler.post(mConnectionRunnable);} else {internalConnectToCurrentUser();}}private void internalConnectToCurrentUser() {...代码省略... Intent launcherServiceIntent new Intent(ACTION_QUICKSTEP).setPackage(mRecentsComponentName.getPackageName());try {//绑定服务mBound mContext.bindServiceAsUser(launcherServiceIntent,mOverviewServiceConnection,Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,UserHandle.of(getCurrentUserId()));} catch (SecurityException e) {Log.e(TAG_OPS, Unable to bind because of security error, e);}...代码省略...}public IOverviewProxy getProxy() {return mOverviewProxy;} } SystemUI模块的OverviewProxyService类的构造方法会对Launche3模块的TouchInteractionService服务进行绑定并把调用该服务返回的Binder对象的onInitialize将Launcher3模块需要的相关参数传了过去这样Launch3模块才能拿到ISplitScreen的实例对象通过调用该实例对象的startTasksWithLegacyTransition方法最终触发分屏模式。那么问题有来了OverviewProxyService里面的ISplitScreen对象实例是如何被赋值的 2、重新再来看下OverviewProxyService的构造方法这次我们重点关注一下mSplitScreenOptional这个对象。 public class OverviewProxyService extends CurrentUserTracker implementsCallbackControllerOverviewProxyListener, NavigationModeController.ModeChangedListener,Dumpable {private final OptionalSplitScreen mSplitScreenOptional;//触发分屏模式的关键对象Inject//Dagger2框架注解public OverviewProxyService(Context context, CommandQueue commandQueue,LazyNavigationBarController navBarControllerLazy,LazyOptionalStatusBar statusBarOptionalLazy,NavigationModeController navModeController,NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,OptionalPip pipOptional,OptionalLegacySplitScreen legacySplitScreenOptional,OptionalSplitScreen splitScreenOptional,OptionalOneHanded oneHandedOptional,OptionalRecentTasks recentTasks,OptionalStartingSurface startingSurface,BroadcastDispatcher broadcastDispatcher,ShellTransitions shellTransitions,ScreenLifecycle screenLifecycle,SmartspaceTransitionController smartspaceTransitionController,UiEventLogger uiEventLogger,DumpManager dumpManager) {super(broadcastDispatcher);...代码省略...mSplitScreenOptional splitScreenOptional;//为mSplitScreenOptional赋值...代码省略...}private final ServiceConnection mOverviewServiceConnection new ServiceConnection() {Overridepublic void onServiceConnected(ComponentName name, IBinder service) {...代码省略...mSplitScreenOptional.ifPresent((splitscreen) - params.putBinder(KEY_EXTRA_SHELL_SPLIT_SCREEN,//这里调用splitscreen的createExternalInterface方法splitscreen.createExternalInterface().asBinder()));...代码省略...}} }OverviewProxyService的构造方法有一个关键注解 Inject这个注解是Dagger2的框架注解该框架会根据我们的配置当我们需要在某个对象的构造方法中传入特定参数对象的时候只要添加Inject注解该框架会自动帮我们创建参数对象并传入。关于这个框架的原理我在Android 12系统源码_SystemUI一SystemUI的启动流程这篇博客具体分析过这里不做过多解释。 3、由于后续会多次提到Optional这种类型的数据类型这里我们需要先简单看下这个类的相关代码。 public final class OptionalT {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方法并将该方法返回的对象封装成OptionalT类型返回。publicU OptionalU map(Function? super T, ? extends U mapper) {Objects.requireNonNull(mapper);if (!isPresent())return empty();else {return Optional.ofNullable(mapper.apply(value));}} }4、关于Optional这个对象的dagger2框架的配置信息SystemUI配置在WMComponent这个接口里面的。 frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java import com.android.wm.shell.dagger.WMShellModule;WMSingleton//单例 Subcomponent(modules {WMShellModule.class})//需要进一步结合WMShellModule做分析 public interface WMComponent {/*** Initializes all the WMShell components before starting any of the SystemUI components.* 在初始化SystemUI组件之前优先初始化WMShell模块的所有组件*/default void init() {//调用ShellInit的init这个方法需要额外关注一下后续我们会再次提到getShellInit().init();}//获取ShellInit对象实例WMSingletonShellInit getShellInit();//获取OptionalSplitScreen对象实例WMSingletonOptionalSplitScreen 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 {WMSingletonProvidesDynamicOverridestatic SplitScreenController provideSplitScreenController(ShellTaskOrganizer shellTaskOrganizer,SyncTransactionQueue syncQueue, Context context,RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,ShellMainThread ShellExecutor mainExecutor,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController, Transitions transitions,TransactionPool transactionPool, IconProvider iconProvider,OptionalRecentTasksController recentTasks,ProviderOptionalStageTaskUnfoldController stageTaskUnfoldControllerProvider) {//创建SplitScreenController对象实例return new SplitScreenController(shellTaskOrganizer, syncQueue, context,rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,displayInsetsController, transitions, transactionPool, iconProvider,recentTasks, stageTaskUnfoldControllerProvider);}//这个方法我们需要关注一下后面会提到WMSingletonProvidesstatic ShellInit provideShellInit(ShellInitImpl impl) {//调用ShellInitImpl的asShellInit方法返回ShellInit对象实例return impl.asShellInit();}WMSingletonProvidesstatic ShellInitImpl provideShellInitImpl(DisplayController displayController,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,OptionalBubbleController bubblesOptional,OptionalSplitScreenController splitScreenOptional,OptionalAppPairsController appPairsOptional,OptionalPipTouchHandler pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,OptionalFullscreenUnfoldController appUnfoldTransitionController,OptionalFreeformTaskListener freeformTaskListener,OptionalRecentTasksController 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 {WMSingletonProvidesstatic OptionalSplitScreen provideSplitScreen(OptionalSplitScreenController splitScreenController) {//结合前面OptionalT这个类的代码可以知道调用splitScreenController对象的asSplitScreen方法并将该方法返回的SplitScreen对 象实例封装成OptionalSplitScreen类型的对象再返回。return splitScreenController.map((controller) - controller.asSplitScreen());}WMSingletonProvidesstatic OptionalSplitScreenController providesSplitScreenController(DynamicOverride OptionalSplitScreenController 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, RemoteCallableSplitScreenController {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,OptionalRecentTasksController recentTasks,ProviderOptionalStageTaskUnfoldController 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是一个接口具体实现是内部类SplitScreenImplExternalThreadprivate class SplitScreenImpl implements SplitScreen {private ISplitScreenImpl mISplitScreen;private final ArrayMapSplitScreenListener, Executor mExecutors new ArrayMap();private final SplitScreen.SplitScreenListener mListener new SplitScreenListener() {Overridepublic void onStagePositionChanged(int stage, int position) {...代码省略...}Overridepublic void onTaskStageChanged(int taskId, int stage, boolean visible) {...代码省略...}Overridepublic void onSplitVisibilityChanged(boolean visible) {...代码省略...}};Overridepublic ISplitScreen createExternalInterface() {if (mISplitScreen ! null) {mISplitScreen.invalidate();}mISplitScreen new ISplitScreenImpl(SplitScreenController.this);//返回实现了ISplitScreen接口的对象实例return mISplitScreen;}}//ISplitScreen是一个aidl内部类ISplitScreenImpl实现了ISplitScreen的接口方法。BinderThreadprivate static class ISplitScreenImpl extends ISplitScreen.Stub {private SplitScreenController mController;private final SingleInstanceRemoteListenerSplitScreenController,ISplitScreenListener mListener;private final SplitScreen.SplitScreenListener mSplitScreenListener new SplitScreen.SplitScreenListener() {Overridepublic void onStagePositionChanged(int stage, int position) {mListener.call(l - l.onStagePositionChanged(stage, position));}Overridepublic void onTaskStageChanged(int taskId, int stage, boolean visible) {mListener.call(l - l.onTaskStageChanged(taskId, stage, visible));}};public ISplitScreenImpl(SplitScreenController controller) {mController controller;mListener new SingleInstanceRemoteListener(controller,c - c.registerSplitScreenListener(mSplitScreenListener),c - c.unregisterSplitScreenListener(mSplitScreenListener));}void invalidate() {mController null;}Overridepublic void startTasksWithLegacyTransition(int mainTaskId, Nullable Bundle mainOptions,int sideTaskId, Nullable Bundle sideOptions, SplitPosition int sidePosition,float splitRatio, RemoteAnimationAdapter adapter) {//这里显示进行权限确认然后会调用StageCoordinator的startTasksWithLegacyTransition方法。executeRemoteCallWithTaskPermission(mController, startTasks,(controller) - controller.mStageCoordinator.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,splitRatio, adapter));}}}SplitScreenController的asSplitScreen方法返回了该类的一个内部对象SplitScreenImplSplitScreenImpl实现了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 OptionalSplitScreenController mSplitScreenOptional;private final InitImpl mImpl new InitImpl();public ShellInitImpl(DisplayController displayController,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,OptionalBubbleController bubblesOptional,OptionalSplitScreenController splitScreenOptional,OptionalAppPairsController appPairsOptional,OptionalPipTouchHandler pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,OptionalFullscreenUnfoldController fullscreenUnfoldTransitionController,OptionalFreeformTaskListener freeformTaskListenerOptional,OptionalRecentTasksController recentTasks,Transitions transitions,StartingWindowController startingWindow,ShellExecutor mainExecutor) {...代码省略...mSplitScreenOptional splitScreenOptional;...代码省略...}public ShellInit asShellInit() {return mImpl;}private void init() {...代码省略...mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);...代码省略...}ExternalThreadprivate class InitImpl implements ShellInit {Overridepublic void init() {try {//进一步调用ShellInitImpl的Init方法。mMainExecutor.executeBlocking(() - ShellInitImpl.this.init());} catch (InterruptedException e) {throw new RuntimeException(Failed to initialize the Shell in 2s, e);}}} }四、SystemUI模块初始化分屏组件 1、前面第三节第1步WMShellModule类中我们有提到过和ShellInitImpl对象创建有关的代码。 import com.android.wm.shell.splitscreen.SplitScreenController;Module(includes WMShellBaseModule.class) public class WMShellModule {WMSingletonProvidesstatic ShellInit provideShellInit(ShellInitImpl impl) {//调用ShellInitImpl的asShellInit方法返回ShellInit对象实例return impl.asShellInit();}WMSingletonProvidesstatic ShellInitImpl provideShellInitImpl(DisplayController displayController,DisplayImeController displayImeController,DisplayInsetsController displayInsetsController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,OptionalBubbleController bubblesOptional,OptionalSplitScreenController splitScreenOptional,OptionalAppPairsController appPairsOptional,OptionalPipTouchHandler pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,OptionalFullscreenUnfoldController appUnfoldTransitionController,OptionalFreeformTaskListener freeformTaskListener,OptionalRecentTasksController recentTasksOptional,Transitions transitions,StartingWindowController startingWindow,ShellMainThread ShellExecutor mainExecutor) {//创建ShellInitImpl的对象实例return new ShellInitImpl(displayController,displayImeController,displayInsetsController,dragAndDropController,shellTaskOrganizer,bubblesOptional,splitScreenOptional,appPairsOptional,pipTouchHandlerOptional,fullscreenTaskListener,appUnfoldTransitionController,freeformTaskListener,recentTasksOptional,transitions,startingWindow,mainExecutor);} }2、前面第二节第4步WMComponent类中我们有提到SystemUI模块在初始化SystemUI模块的组件之前会先初始化WMShell模块的所有组件这自然也包括分屏组件。 WMSingleton//单例 Subcomponent(modules {WMShellModule.class}) public interface WMComponent {/*** Initializes all the WMShell components before starting any of the SystemUI components.* 在初始化SystemUI组件之前优先初始化WMShell模块的所有组件*/default void init() {//调用ShellInit的initgetShellInit().init();}//获取ShellInit对象实例WMSingletonShellInit getShellInit();}WMComponent的init方法先是通过getShellInit方法获取到ShellInit对象实例InitImpl实现了ShellInit这个接口 并实现了init方法该方法会进一步调用ShellInitImpl的init方法最终会触发SplitScreenController的onOrganizerRegistered方法。 3、SplitScreenController的onOrganizerRegistered方法会创建控制分屏功能的分屏组件StageCoordinator的对象实例。 public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteCallableSplitScreenController {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,ProviderOptionalStageTaskUnfoldController 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,OptionalRecentTasksController recentTasks,ProviderOptionalStageTaskUnfoldController unfoldControllerProvider) {mContext context;mDisplayId displayId;mSyncQueue syncQueue;mRootTDAOrganizer rootTDAOrganizer;mTaskOrganizer taskOrganizer;//为mTaskOrganizer赋值...代码省略...}//Launcher3其实是调用了这个方法触发分屏模式的void startTasksWithLegacyTransition(int mainTaskId, Nullable Bundle mainOptions,int sideTaskId, Nullable Bundle sideOptions, SplitPosition int sidePosition,float splitRatio, RemoteAnimationAdapter adapter) {// Init divider first to make divider leash for remote animation target.setDividerVisibility(true /* visible */);//设置分屏中间的分割线View可见// Set false to avoid record new bounds with old task still on top;mShouldUpdateRecents false;final WindowContainerTransaction wct new WindowContainerTransaction();final WindowContainerTransaction evictWct new WindowContainerTransaction();prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct);prepareEvictChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, evictWct);// Need to add another wrapper here in shell so that we can inject the divider bar// and also manage the process elevation via setRunningRemoteIRemoteAnimationRunner wrapper new IRemoteAnimationRunner.Stub() {Overridepublic void onAnimationStart(WindowManager.TransitionOldType int transit,RemoteAnimationTarget[] apps,RemoteAnimationTarget[] wallpapers,RemoteAnimationTarget[] nonApps,final IRemoteAnimationFinishedCallback finishedCallback) {mIsDividerRemoteAnimating true;RemoteAnimationTarget[] augmentedNonApps new RemoteAnimationTarget[nonApps.length 1];for (int i 0; i nonApps.length; i) {augmentedNonApps[i] nonApps[i];}augmentedNonApps[augmentedNonApps.length - 1] getDividerBarLegacyTarget();IRemoteAnimationFinishedCallback wrapCallback new IRemoteAnimationFinishedCallback.Stub() {Overridepublic void onAnimationFinished() throws RemoteException {mIsDividerRemoteAnimating false;mShouldUpdateRecents true;mSyncQueue.queue(evictWct);mSyncQueue.runInSync(t - applyDividerVisibility(t));finishedCallback.onAnimationFinished();}};try {try {ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(adapter.getCallingApplication());} catch (SecurityException e) {Slog.e(TAG, Unable to boost animation thread. This should only happen during unit tests);}adapter.getRunner().onAnimationStart(transit, apps, wallpapers,augmentedNonApps, wrapCallback);} catch (RemoteException e) {Slog.e(TAG, Error starting remote animation, e);}}Overridepublic void onAnimationCancelled() {mIsDividerRemoteAnimating false;mShouldUpdateRecents true;mSyncQueue.queue(evictWct);mSyncQueue.runInSync(t - applyDividerVisibility(t));try {adapter.getRunner().onAnimationCancelled();} catch (RemoteException e) {Slog.e(TAG, Error starting remote animation, e);}}};RemoteAnimationAdapter wrappedAdapter new RemoteAnimationAdapter(wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());if (mainOptions null) {mainOptions ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();} else {ActivityOptions mainActivityOptions ActivityOptions.fromBundle(mainOptions);mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));mainOptions mainActivityOptions.toBundle();}sideOptions sideOptions ! null ? sideOptions : new Bundle();setSideStagePosition(sidePosition, wct);mSplitLayout.setDivideRatio(splitRatio);if (mMainStage.isActive()) {mMainStage.moveToTop(getMainStageBounds(), wct);} else {// Build a request WCT that will launch both apps such that task 0 is on the main stage// while task 1 is on the side stage.// 设置mMainStage对应的RootTask的Bounds并将其移动到最前面mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);}// 设置mSideStage对应的RootTask的Bounds并将其移动到最前面mSideStage.moveToTop(getSideStageBounds(), wct);// Make sure the launch options will put tasks in the corresponding split roots// 配置launch task的option让分屏应用的task启动到RootTask节点之下addActivityOptions(mainOptions, mMainStage);addActivityOptions(sideOptions, mSideStage);// Add task launch requests// 启动分屏应用启动方式和从任务管理器启动是一样的startActivityFromRecentswct.startTask(mainTaskId, mainOptions);wct.startTask(sideTaskId, sideOptions);// Using legacy transitions, so we cant use blast sync since it conflicts.// 所有修改封装到WindowContainerTransaction中然后通过WindowOrganizer框架完成上面的变化mTaskOrganizer.applyTransaction(wct);}}显示分屏中间的View设置mMainStage对应的RootTask的Bounds并移动到最前面设置mSideStage对应的RootTask的Bounds并移动到最前面启动分屏应用让分屏应用的task启动到RootTask节点之下启动方式和从任务管理器启动是一样的Framework侧对应的就是startActivityFromRecents方法 2、继续来看下ShellTaskOrganizer的applyTransaction方法。 frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java public class ShellTaskOrganizer extends TaskOrganizer implementsCompatUIController.CompatUICallback { }frameworks/base/core/java/android/window/TaskOrganizer.java public class TaskOrganizer extends WindowOrganizer {}frameworks/base/core/java/android/window/TaskOrganizer.java public class WindowOrganizer {//applyTransaction是ShellTaskOrganizer的父类方法public void applyTransaction(NonNull WindowContainerTransaction t) {try {if (!t.isEmpty()) {//调用IWindowOrganizerController的applyTransaction方法getWindowOrganizerController().applyTransaction(t);}} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}static IWindowOrganizerController getWindowOrganizerController() {return IWindowOrganizerControllerSingleton.get();}private static final SingletonIWindowOrganizerController IWindowOrganizerControllerSingleton new SingletonIWindowOrganizerController() {Overrideprotected IWindowOrganizerController create() {try {return ActivityTaskManager.getService().getWindowOrganizerController();} catch (RemoteException e) {return null;}}}; }applyTransaction方法最终是其父类WindowOrganizer的方法该方法先是获取到WindowOrganizerController的实例对象然后调用该对象的applySyncTransaction方法。 3、IWindowOrganizerController是一个aidl该接口的具体实现类是WindowOrganizerController。 frameworks/base/core/java/android/window/IWindowOrganizerController.aidl interface IWindowOrganizerController {int applySyncTransaction(in WindowContainerTransaction t,in IWindowContainerTransactionCallback callback);}frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java class WindowOrganizerController extends IWindowOrganizerController.Stubimplements BLASTSyncEngine.TransactionReadyListener {Overridepublic int applySyncTransaction(WindowContainerTransaction t,IWindowContainerTransactionCallback callback) {if (t null) {throw new IllegalArgumentException(Null transaction passed to applySyncTransaction);}enforceTaskPermission(applySyncTransaction());final CallerInfo caller new CallerInfo();final long ident Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {int syncId -1;if (callback ! null) {syncId startSyncWithOrganizer(callback);}applyTransaction(t, syncId, null /*transition*/, caller);if (syncId 0) {setSyncReady(syncId);}return syncId;}} finally {Binder.restoreCallingIdentity(ident);}}}这里还是运用了WindowOrganizer框架的能力把所有修改点封装到 WindowContainerTransaction中然后通过mTaskOrganizer.applyTransaction(wct); 转交给FrameworkFramework解析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.w-s-a.com/news/501279/

相关文章:

  • 网站降权查询wordpress更换文章背景色
  • 大型电商网站开发金融企业网站建设公司
  • 成都营销型网站建设价格化妆品品牌推广方案
  • 深圳公司手机网站制作苏州网站推广哪家好
  • 网站建设开发方式包括购买学校网站建设费计入什么科目
  • 做简单网站的框架图中小微企业查询平台
  • 哪些网站可以免费做产品推广建设建设部网站
  • 网站开发销售怎么做django做网站
  • 淘宝客网站做百度竞价万网域名怎么绑定网站
  • 建设网站找哪个公司北京知名大公司有哪些
  • 专业彩票网站开发网站流量在哪设置
  • 网站建设对应的岗位榆林做网站公司
  • 网站建设公司怎么算专业js网站分页怎么做
  • 网和网站的区别phpcms和帝国cms哪个好
  • wordpress改网站名字长沙网络营销外包
  • 宝塔怎么做第二个网站网站内容设计遵循的原则有
  • 网站违反了 google 质量指南免费ppt模版网站
  • 郑州网站建设郑州网站建设成都那家网站建设好
  • 温州网站排名优化公司如何招聘软件网站开发人员
  • 成都 网站建设公司哪家好襄阳行业网站建设
  • wordpress 调用时间做网站seo的公司哪家好
  • 手机上网站搭建网站账户系统
  • 西乡网站的建设柳州建站
  • 宁夏网站建设怎么样互联网 网站设计
  • 成都关键词seo推广平台手机端关键词排名优化软件
  • 学做软件的网站卡盟平台
  • 网站构建建设案例展示关于做服饰网站的首页
  • 如何建设网站论坛凡科建站手机版登录
  • 建设银行门户网站惠州公司网站建设价格
  • 用python开发网站网站如何取消验证码