android T 分屏流程之systemui部分/android framework车载车机手机实战开发

hi,上一节已经讲解清楚了分屏在Launcher端的一个启动情况,本节课开始将进行systemui部分的详细讲解:
b站免费视频教程讲解:
https://www.bilibili.com/video/BV1wj411o7A9/
在这里插入图片描述

1、systemui部分触发分屏的调用流程

回忆上一节launcher端会调用以下方法来调用到systemui端

//packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
  pendingAnimation.addEndListener(aBoolean -> {
    
    
            mSplitSelectStateController.launchSplitTasks(
                    aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
        });

    public void launchSplitTasks(Consumer<Boolean> callback) {
    
    
     //省略
        launchTasks(mInitialTaskId, pendingIntent, fillInIntent, mSecondTaskId, mStagePosition,
                callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
    }
    
public void launchTasks(int taskId1, @Nullable PendingIntent taskPendingIntent,
            @Nullable Intent fillInIntent, int taskId2, @StagePosition int stagePosition,
            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
    
    
       //省略
   //跨进程调用到systemui进程的startIntentAndTaskWithLegacyTransition
                 mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
                        fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
                        stagePosition, splitRatio, adapter);
    }
//packages/apps/Launcher3/quickstep/src/com/android/quickstep/SystemUiProxy.java
  public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
            Intent fillInIntent, int taskId, Bundle mainOptions, Bundle sideOptions,
            @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
            RemoteAnimationAdapter adapter) {
    
    
        if (mSystemUiProxy != null) {
    
    
            try {
    
    
            //这里有调用到了SplitScreen的startIntentAndTaskWithLegacyTransition
                if (taskPendingIntent == null) {
    
    
                mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
                        taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                        splitRatio, adapter);
            }
            }
        }
    }

这里注意最后桌面调用了mSplitScreen.startIntentAndTaskWithLegacyTransition,这里的mSplitScreen就是一个binder代理,会调用到systemui的服务端
这里对应systemui端的代码如下:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java

 private static class ISplitScreenImpl extends ISplitScreen.Stub {
    
    
//省略
        @Override
        public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
                int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
                float splitRatio, RemoteAnimationAdapter adapter) {
    
    
                //注意这里有线程切换哈,binder线程到main线程
            executeRemoteCallWithTaskPermission(mController, "startTasks",
                    (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
                            mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
                            splitRatio, adapter));
        }

  

线程切换后调用到了最关键方法:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

  private void startWithLegacyTransition(int mainTaskId, int sideTaskId,
            @Nullable PendingIntent pendingIntent, @Nullable Intent fillInIntent,
            @Nullable Bundle mainOptions, @Nullable Bundle sideOptions,
            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) {
    
    
     //初始化分屏的分割线的布局
        mSplitLayout.init();
     //省略
        final WindowContainerTransaction wct = new WindowContainerTransaction();//初始化用来跨进程传递的WindowContainerTransaction
    
   //初始化一个远程动画的运行回调binder对象
        IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
    
    
            @Override
            public void onAnimationStart(@WindowManager.TransitionOldType int transit,
                    RemoteAnimationTarget[] apps,
                    RemoteAnimationTarget[] wallpapers,
                    RemoteAnimationTarget[] nonApps,
                    final IRemoteAnimationFinishedCallback finishedCallback) {
    
    
            //暂时省略这里后续分析
        };
        RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
                wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());//包装一下成为RemoteAnimationAdapter类型
//省略
//构建出对应的mainOptions,及上分屏的启动option
            mainOptions = mainActivityOptions.toBundle();
 //准备好对应的sideOptions,下分屏的option
        sideOptions = sideOptions != null ? sideOptions : new Bundle();
        setSideStagePosition(sidePosition, wct);

        mSplitLayout.setDivideRatio(splitRatio);//设置分界线比例,一般大小上下屏幕都一样大,为0.5,这里就把对应的上下分屏的bound计算出来了
        if (!mMainStage.isActive()) {
    
    //设置为active
            mMainStage.activate(wct, false /* reparent */);
        }
        updateWindowBounds(mSplitLayout, wct);//这里把计算出来的上下分屏的bound都设置给对应的configration,传递到systemserver端,然后方便systemserver更新task的bound
        wct.reorder(mRootTaskInfo.token, true);//需要让装载分屏的roottask进行reorder,主要就是为了把分屏移到最前面, 注意这里的mRootTaskInfo其实就是一开始systemui负责创建的mutilwindow的task这里默认id一般为4

//准备好对应的option参数
        // Make sure the launch options will put tasks in the corresponding split roots
        addActivityOptions(mainOptions, mMainStage);
        addActivityOptions(sideOptions, mSideStage);

			//分别准备好对应上下分屏启动task的transition
        // Add task launch requests
        wct.startTask(mainTaskId, mainOptions);
        if (withIntent) {
    
    
            wct.sendPendingIntent(pendingIntent, fillInIntent, sideOptions);
        } else {
    
    
            wct.startTask(sideTaskId, sideOptions);
        }
        //最后把前面准备好的WindowContainerTranstion统一进行apply到systemserver
        // Using legacy transitions, so we can't use blast sync since it conflicts.
        mTaskOrganizer.applyTransaction(wct);
        mSyncQueue.runInSync(t -> {
    
    
        //这里主要进行相关的divider分界线显示
            setDividerVisibility(true, t);
            updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
        });
    }

ps:mMainStage和mSideStage属于和RootTask一样,分屏的RootTask一般会带两个子task,他们就分别是mMainStage和mSideStage的RootTaskInfo
在这里插入图片描述在这里插入图片描述

#0 Task=4 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #1 Task=6 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]
         #0 Task=5 type=undefined mode=multi-window override-mode=multi-window requested-bounds=[0,0][0,0] bounds=[0,0][1440,2960]

执行堆栈情况:


startWithLegacyTransition:503, StageCoordinator (com.android.wm.shell.splitscreen)
startTasksWithLegacyTransition:392, StageCoordinator (com.android.wm.shell.splitscreen)
lambda$startTasksWithLegacyTransition$8:684, SplitScreenController$ISplitScreenImpl (com.android.wm.shell.splitscreen)
$r8$lambda$YwxG8mhKer2INb6KsZqR3kn2_Zg:-1, SplitScreenController$ISplitScreenImpl (com.android.wm.shell.splitscreen)
accept:-1, SplitScreenController$ISplitScreenImpl$$ExternalSyntheticLambda6 (com.android.wm.shell.splitscreen)
lambda$executeRemoteCallWithTaskPermission$1:60, ExecutorUtils (com.android.wm.shell.common)
$r8$lambda$s8eUOdyrqpqzzyFwAMGxO-MaCg4:-1, ExecutorUtils (com.android.wm.shell.common)
run:-1, ExecutorUtils$$ExternalSyntheticLambda1 (com.android.wm.shell.common)
handleCallback:942, Handler (android.os)
dispatchMessage:99, Handler (android.os)
loopOnce:201, Looper (android.os)
loop:288, Looper (android.os)
main:7897, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:548, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:936, ZygoteInit (com.android.internal.os)

时序图:
在这里插入图片描述

2、systemui开启分屏干的关键事情

在这里插入图片描述

2.1 SplitLayout创建分割线区域部分

刚开始是SplitLayout的init

    public void init() {
    
    
        if (mInitialized) return;
        mInitialized = true;
        //调用mSplitWindowManager初始化,主要是创建对应window出来,注意这里的window创建和pip那个window创建类是,不是用普通的windowmanager,所以dumpsys window windows是看不到的,得通过dumpsys SurfaceFlinger才可以看到
        mSplitWindowManager.init(this, mInsetsState);
        mDisplayImeController.addPositionProcessor(mImePositionProcessor);
    }
    /** Inflates {@link DividerView} on to the root surface. */
    void init(SplitLayout splitLayout, InsetsState insetsState) {
    
    
        if (mDividerView != null || mViewHost != null) {
    
    
            throw new UnsupportedOperationException(
                    "Try to inflate divider view again without release first");
        }

        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
        mDividerView = (DividerView) LayoutInflater.from(mContext)
                .inflate(R.layout.split_divider, null /* root */);

        final Rect dividerBounds = splitLayout.getDividerBounds();
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH
                        | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle(mWindowName);
        lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
        mViewHost.setView(mDividerView, lp);
        mDividerView.setup(splitLayout, this, mViewHost, insetsState);
    }

再来是区域的计算确定部分,setDivideRatio方法:

public void setDivideRatio(float ratio) {
    
    
//注意这里的position是非常关键的,这个为roottask的0.5一般情况,上下屏幕相等情况
    final int position = isLandscape()
            ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
            : mRootBounds.top + (int) (mRootBounds.height() * ratio);
    final DividerSnapAlgorithm.SnapTarget snapTarget =
            mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
            //这里会设置对应的位置position,非常关键会触发bound的计算
    setDividePosition(snapTarget.position, false /* applyLayoutChange */);
}
   void setDividePosition(int position, boolean applyLayoutChange) {
    
    
        mDividePosition = position;
        updateBounds(mDividePosition);//根据新新的mDividePosition计算新的bound
        if (applyLayoutChange) {
    
    
            mSplitLayoutHandler.onLayoutSizeChanged(this);
        }
    }
       /** Updates recording bounds of divider window and both of the splits. */
    private void updateBounds(int position) {
    
    
        mDividerBounds.set(mRootBounds);
        mBounds1.set(mRootBounds);
        mBounds2.set(mRootBounds);
        final boolean isLandscape = isLandscape(mRootBounds);
        if (isLandscape) {
    
    
            position += mRootBounds.left;
            mDividerBounds.left = position - mDividerInsets;
            mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth;
            mBounds1.right = position;
            mBounds2.left = mBounds1.right + mDividerSize;
        } else {
    
    
            position += mRootBounds.top;
            mDividerBounds.top = position - mDividerInsets;
            mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth;
            mBounds1.bottom = position;
            mBounds2.top = mBounds1.bottom + mDividerSize;
        }
        DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */);
        DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */);
        mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
    }

已经计算好了分屏的bound后,就需要把bound设置到WindowContainerTransition中进行传递,到了关键的:updateWindowBounds(mSplitLayout, wct);

    private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
    
    
        final StageTaskListener topLeftStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
        final StageTaskListener bottomRightStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
        //分别有了上下屏task信息后,要对这些task的bound进行修改,applyTaskChanges就是关键的方法(注意这里的task还不是直接app的task,还是分屏的mMainStage及mSideStage对应的容器task)
        layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
    }
      /** Apply recorded task layout to the {@link WindowContainerTransaction}. */
    public void applyTaskChanges(WindowContainerTransaction wct,
            ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
    
    
        if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
    
    
            wct.setBounds(task1.token, mBounds1);//WindowContainerTransaction设置好对应的task的bound数据
            wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
            mWinBounds1.set(mBounds1);
            mWinToken1 = task1.token;
        }
        if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
    
    
            wct.setBounds(task2.token, mBounds2);
            wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
            mWinBounds2.set(mBounds2);
            mWinToken2 = task2.token;
        }
    }
    /**
     * Resize a container.
     */
    @NonNull
    public WindowContainerTransaction setBounds(
            @NonNull WindowContainerToken container,@NonNull Rect bounds) {
    
    
        Change chg = getOrCreateChange(container.asBinder());//注意这里进行了Change的构造,即bounds变化靠这个Change变量进行传递
        
        chg.mConfiguration.windowConfiguration.setBounds(bounds);
        chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
        chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
        return this;
    }

在这里插入图片描述

2.2 对分屏的RootTask进行reorder

  @NonNull
    public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
    
    
        mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop));
        return this;
    }
    //创建对应的HIERARCHY_OP_TYPE_REORDER的HierarchyOp进行传递
         public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {
    
    
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER)
                    .setContainer(container)
                    .setReparentContainer(container)
                    .setToTop(toTop)
                    .build();
        }

2.3 对分屏Task进行startTask

这个和reorder没啥区别


public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
    
    
        mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
        return this;
    }
     /** Create a hierarchy op for launching a task. */
        public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) {
    
    
            final Bundle fullOptions = options == null ? new Bundle() : options;
            fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);
            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK)
                    .setToTop(true)
                    .setLaunchOptions(fullOptions)
                    .build();
        }

猜你喜欢

转载自blog.csdn.net/learnframework/article/details/130944230
今日推荐