Android 7.0 SystemUI(2)--Multi-Window多窗口模式
16lz
2021-01-23
从Android7.0开始,Google原生加入了应用多窗口支持,相关特性可查看
多窗口支持
本文涉及到的代码
frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.javaframeworks/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.javaframeworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java frameworks/base/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.javaframeworks/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;
主要有几个比较重要的对象
DividerWindowManager--主要是封装了 WindowManager
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); } } }
dockTopTask方法第二个参数是标示应用进入分屏后显示的区域
主要有两种模式
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;
进入到dockTopTask方法中
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); } } }
首先会检查调用者是否有android.permission.MANAGE_ACTIVITY_STACKS权限,如果有权限这会调用mStackSupervisor.moveTaskToStackLocked开始移动task
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; }
这里返回一个DOCKED_STACK_ID的ActivityStack。
我们知道一个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(); } }
移动完task后,最后performSurfacePlacement开始去显示出来。
更多相关文章
- 【Android 设计】:模式_ Android新特性
- android1.6新增SD卡写权限WRITE_EXTERNAL_STORAGE
- android系统权限SET_PREFERRED_APPLICATIONS怎么获取
- Android 随时随地键值对存储对象解决方案
- android对象池之Message
- android 飞行模式分析
- Android的绘制文本对象FontMetrics的介绍及绘制文本
- Android访问权限大全
- Android JNI和NDK学习(09)--JNI实例二 传递类对象