从Android7.0开始,Google原生加入了应用多窗口支持,相关特性可查看
多窗口支持
本文涉及到的代码
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
frameworks/base/core/java/android/app/ActivityManager.java
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java.java
SystemUI在启动过程中会启动Divider,主要是管理分屏分割线的
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
private final Class>[] SERVICES = new Class[] {
com.android.systemui.tuner.TunerService.class,
com.android.systemui.keyguard.KeyguardViewMediator.class,
com.android.systemui.recents.Recents.class,
com.android.systemui.volume.VolumeUI.class,
Divider.class,
com.android.systemui.statusbar.SystemBars.class,
com.android.systemui.usb.StorageNotification.class,
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
com.android.systemui.tv.pip.PipUI.class,
com.android.systemui.shortcut.ShortcutKeyDispatcher.class
};
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
/**
* Controls the docked stack divider.
*/
public class Divider extends SystemUI {
private DividerWindowManager mWindowManager;
private DividerView mView;
...
private DockDividerVisibilityListener mDockDividerVisibilityListener;
...
private ForcedResizableInfoActivityController mForcedResizableController;
frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
/**
* Manages the window parameters of the docked stack divider.
*/
public class DividerWindowManager {
private static final String WINDOW_TITLE = "DockedStackDivider";
private final WindowManager mWindowManager;
private WindowManager.LayoutParams mLp;
private View mView;
public DividerWindowManager(Context ctx) {
mWindowManager = ctx.getSystemService(WindowManager.class);
}
public void add(View view, int width, int height) {
mLp = new WindowManager.LayoutParams(
width, height, TYPE_DOCK_DIVIDER,
FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
| FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mLp.setTitle(WINDOW_TITLE);
mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
mWindowManager.addView(view, mLp);
mView = view;
}
public void remove() {
if (mView != null) {
mWindowManager.removeView(mView);
}
mView = null;
}
DividerView就是分屏分割线的view
DockDividerVisibilityListener是用来回调控制DividerView状态的(包括)
通过长按多任务按键可触发多窗口模式进入分屏状态
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mRecents == null || !ActivityManager.supportsMultiWindow()
|| !getComponent(Divider.class).getView().getSnapAlgorithm()
.isSplitScreenFeasible()) {
return false;
}
toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
return true;
}
};
@Override
protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
return;
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
//进入分屏
mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
//退出分屏
EventBus.getDefault().send(new UndockingTaskEvent());
if (metricsUndockAction != -1) {
MetricsLogger.action(mContext, metricsUndockAction);
}
}
}
主要有两种模式
frameworks/base/core/java/android/app/ActivityManager.java
//横屏时在上方显示或者竖屏时在左边显示
public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;
//横屏时在下方显示或者竖屏时在右边显示
public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java
public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return false;
}
//获取屏幕显示像素
Point realSize = new Point();
if (initialBounds == null) {
mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
.getRealSize(realSize);
initialBounds = new Rect(0, 0, realSize.x, realSize.y);
}
int currentUser = sSystemServicesProxy.getCurrentUser();
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
boolean screenPinningActive = ssp.isScreenPinningActive();
boolean isRunningTaskInHomeStack = runningTask != null &&
SystemServicesProxy.isHomeStack(runningTask.stackId);
if (runningTask != null && !isRunningTaskInHomeStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
if (runningTask.isDockable) {
if (metricsDockAction != -1) {
MetricsLogger.action(mContext, metricsDockAction,
runningTask.topActivity.flattenToShortString());
}
if (sSystemServicesProxy.isSystemUser(currentUser)) {
//分屏
mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
initialBounds);
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
} else {
Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
}
}
mDraggingInRecentsCurrentUser = currentUser;
return true;
} else {
//应用不支持分屏
Toast.makeText(mContext, R.string.recents_incompatible_app_message,
Toast.LENGTH_SHORT).show();
return false;
}
} else {
return false;
}
}
从这里可以看到需要满足以下条件才能进入分屏
当前正在显示的界面不是launcher或者recentsactivity(launcher和recentsactivity都是在HOME_STACK_ID上),即在launcher或者recentsactivity界面长按多任务键是不会进入分屏模式的,另外还需要满足当前不在画中画模式。
应用支持分屏时调用
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
public void dockTopTask(int topTaskId, 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.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
showRecents(
false /* triggeredFromAltTab */,
dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
false /* animate */,
true /* launchedWhileDockingTask*/,
false /* fromHome */,
DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
if (mIam == null) {
return false;
}
try {
return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
false /* animate */, initialBounds, true /* moveHomeStackFront */ );
} catch (RemoteException e) {
e.printStackTrace();
}
return false;
}
最终会进入到ams的moveTaskToDockedStack中。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
Rect initialBounds, boolean moveHomeStackFront) {
//权限检查
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
//移动task
final boolean moved = mStackSupervisor.moveTaskToStackLocked(
taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
animate, DEFER_RESUME);
if (moved) {
if (moveHomeStackFront) {
mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
return moved;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
String reason, boolean animate, boolean deferResume) {
//taskId要移动的task的id,先根据id找到TaskRecord
final TaskRecord task = anyTaskForIdLocked(taskId);
...
//stackId为DOCKED_STACK_ID,如果TaskRecord的id已经是DOCKED_STACK_ID,说明该task已经在DOCKED_STACK_ID上,就不需要再去移动了
if (task.stack != null && task.stack.mStackId == stackId) {
// You are already in the right stack silly...
Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
return true;
}
...
try {
final ActivityStack stack = moveTaskToStackUncheckedLocked(
task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
...
//这里stackId = DOCKED_STACK_ID
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
kept = resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,
!mightReplaceWindow, deferResume);
} else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
Rect bounds = task.getLaunchBounds();
if (bounds == null) {
stack.layoutTaskInStack(task, null);
bounds = task.mBounds;
}
kept = resizeTaskLocked(task, bounds, RESIZE_MODE_FORCED, !mightReplaceWindow,
deferResume);
} else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
//走这里
kept = resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,
!mightReplaceWindow, deferResume);
}
} finally {
mWindowManager.continueSurfaceLayout();
}
...
handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);
return (preferredLaunchStackId == stackId);
}
先根据taskid找到taskrecord
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
ActivityStack moveTaskToStackUncheckedLocked(
TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
...
//创建DOCKED_STACK_ID的ActivityStack
final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
task.mTemporarilyUnresizable = false;
//wm端也要做相应的移动
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
//将要移动的taskrecord放到DOCKED_STACK_ID的ActivityStack
stack.addTask(task, toTop, reason);
// If the task had focus before (or we're requested to move focus),
// move focus to the new stack by moving the stack to the front.
stack.moveToFrontAndResumeStateIfNeeded(
r, forceFocus || wasFocused || wasFront, wasResumed, reason);
return stack;
}
我们知道一个Activity在Ams会有一个ActivityRecord的对象,对应在WMS会有一个WindowState来保存该activity的显示窗口显示状态,通过一个IApplicationToken对象进行关联。
在ams中一组有关联的ActivityRecord组成了TaskRecord,对应的在WMS中有Task,他们通过taskid来一一对应。如下图
所以分屏时wms端也要做相应的移动。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java.java
public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
synchronized (mWindowMap) {
...
Task task = mTaskIdToTask.get(taskId);
...
TaskStack stack = mStackIdToStack.get(stackId);
...
task.moveTaskToStack(stack, toTop);
final DisplayContent displayContent = stack.getDisplayContent();
displayContent.layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
}
}