Android N 支持多窗口模式,或者叫分屏模式,即在屏幕上可以同时显示多个窗口。
在手机模式下,两个应用可以并排或者上下同时显示,如图 1 所示,屏幕上半部分的窗口是系统的 CLOCK 应用,下半部分是系统设置功能。用户可以拖动两个应用之间的分界线改变两个窗口的大小,放大其中一个应用,同时缩小另一个应用。
图 1 分屏模式
在分屏模式下,各个窗口的应用都可以正常运行,但是只能有一个窗口获得焦点,而另外的窗口则属于暂停状态。
在某个应用界面进入分屏,这个应用会显示在上半部分或者左半部分,在android9之后会显示在下半部分或者右半部分。公司的车机项目从android8升级到android9,客户想要显示的和之前的一样,显示在左半部分。
分屏模式相关代码在SystemUI中,进入分屏的代码如下
private boolean onLongPressRecents() {
if (mRecents == null || !ActivityManager.supportsMultiWindow(getContext())
|| !mDivider.getView().getSnapAlgorithm().isSplitScreenFeasible()) {
return false;
}
return mStatusBar.toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
}
走到src/com/android/systemui/statusbar/phone/StatusBar.java中
protected boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
return false;
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition();
if (navbarPos == NAV_BAR_POS_INVALID) {
return false;
}
int createMode = navbarPos == NAV_BAR_POS_LEFT
? ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT
: ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE, createMode, null, metricsDockAction);
} else {
Divider divider = getComponent(Divider.class);
if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
// Undocking from the minimized state is not supported
return false;
} else {
EventBus.getDefault().send(new UndockingTaskEvent());
if (metricsUndockAction != -1) {
mMetricsLogger.action(metricsUndockAction);
}
}
}
return true;
}
进入分屏时,dockSide = WindowManager.DOCKED_INVALID,所以执行mRecents.splitPrimaryTask()
在src/com/android/systemui/recents/Recents.java的方法splitPrimaryTask中,最终执行的是
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
}
在src/com/android/systemui/recents/RecentsImpl.java
public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode,
Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Make sure we inform DividerView before we actually start the activity so we can change
// the resize mode already.
if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
}
}
EventBus是一种用于Android的事件发布-订阅框架,通过解耦发布者和订阅者简化Android事件传递,因此接收DockedTopTaskEvent事件是具体执行方法。
在src/com/android/systemui/stackdivider/DividerView.java接收EventBus发送的事件
public final void onBusEvent(DockedTopTaskEvent event) {
if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
mState.growAfterRecentsDrawn = false;
mState.animateAfterRecentsDrawn = true;
startDragging(false /* animate */, false /* touching */);
}
updateDockSide();
mEntranceAnimationRunning = true;
resizeStack(calculatePositionForInsetBounds(), mSnapAlgorithm.getMiddleTarget().position,
mSnapAlgorithm.getMiddleTarget());
}
mState.animateAfterRecentsDrawn置为true,执行分屏动画
public void onRecentsDrawn() {
updateDockSide();
final int position = calculatePositionForInsetBounds();
if (mState.animateAfterRecentsDrawn) {
mState.animateAfterRecentsDrawn = false;
mHandler.post(() -> {
// Delay switching resizing mode because this might cause jank in recents animation
// that's longer than this animation.
stopDragging(position, getSnapAlgorithm().getMiddleTarget(),
mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
200 /* endDelay */);
});
}
if (mState.growAfterRecentsDrawn) {
mState.growAfterRecentsDrawn = false;
updateDockSide();
EventBus.getDefault().send(new RecentsGrowingEvent());
stopDragging(position, getSnapAlgorithm().getMiddleTarget(), 336,
Interpolators.FAST_OUT_SLOW_IN);
}
}
此方法根据animateAfterRecentsDrawn是否为true而执行stopDragging()
public void stopDragging(int position, SnapTarget target, long duration, long startDelay,
long endDelay, Interpolator interpolator) {
mHandle.setTouching(false, true /* animate */);
flingTo(position, target, duration, startDelay, endDelay, interpolator);
mWindowManager.setSlippery(true);
releaseBackground();
}
private void flingTo(int position, SnapTarget target, long duration, long startDelay,
long endDelay, Interpolator interpolator) {
ValueAnimator anim = getFlingAnimator(position, target, endDelay);
anim.setDuration(duration);
anim.setStartDelay(startDelay);
anim.setInterpolator(interpolator);
anim.start();
}
通过getFlingAnimator获取动画,在getFlingAnimator中
ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
anim.addUpdateListener(animation -> resizeStackDelayed((int) animation.getAnimatedValue(),
taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
? TASK_POSITION_SAME
: snapTarget.taskPosition,
snapTarget));
resizeStackDelayed是动画执行的步骤
public void resizeStackDelayed(int position, int taskPosition, SnapTarget taskSnapTarget) {
Message message = mHandler.obtainMessage(MSG_RESIZE_STACK, position, taskPosition,
taskSnapTarget);
message.setAsynchronous(true);
mSfChoreographer.scheduleAtSfVsync(mHandler, message);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESIZE_STACK:
resizeStack(msg.arg1, msg.arg2, (SnapTarget) msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
if (mRemoved) {
// This divider view has been removed so shouldn't have any additional influence.
return;
}
calculateBoundsForPosition(position, mDockSide, mDockedRect);
if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
return;
}
// Make sure shadows are updated
if (mBackground.getZ() > 0f) {
mBackground.invalidate();
}
mLastResizeRect.set(mDockedRect);
if (mHomeStackResizable && mIsInMinimizeInteraction) {
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
mDockedTaskRect);
calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
mWindowManagerProxy.resizeDockedStack(mDockedRect, mDockedTaskRect, mDockedTaskRect,
mOtherTaskRect, null);
return;
}
... ...
... ...
}
mDockSide是影响显示左右的参数,
private void updateDockSide() {
mDockSide = mWindowManagerProxy.getDockSide();
mMinimizedShadow.setDockSide(mDockSide);
}
mDockSide是通过mWindowManagerProxy.getDockSide(),最终是通过WindowManagerService获取,我最终没找到WMS在哪里设置的,只能在updateDockSide方法中将mDockSide置为WindowManager.DOCKED_LEFT。