从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

Android 7.0 SystemUI(2)--Multi-Window多窗口模式_第1张图片

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来一一对应。如下图

Android 7.0 SystemUI(2)--Multi-Window多窗口模式_第2张图片

所以分屏时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开始去显示出来。



   

更多相关文章

  1. 【Android 设计】:模式_ Android新特性
  2. android1.6新增SD卡写权限WRITE_EXTERNAL_STORAGE
  3. android系统权限SET_PREFERRED_APPLICATIONS怎么获取
  4. Android 随时随地键值对存储对象解决方案
  5. android对象池之Message
  6. android 飞行模式分析
  7. Android的绘制文本对象FontMetrics的介绍及绘制文本
  8. Android访问权限大全
  9. Android JNI和NDK学习(09)--JNI实例二 传递类对象

随机推荐

  1. How can clear screen in php cli (like
  2. PHP / MySQL - 有时会将空白条目添加到
  3. Laravel手把手系列教程之一环安装和环境
  4. php和django位于同一个lighttpd服务器上
  5. 如何加载json文件?
  6. 将node.js服务器更改为Apache服务器
  7. 纯真ip数据库查询的php实现(补充分组查询)
  8. thinkPHP5下扩展encryptedData解密算法文
  9. 用户GROUP BY ERROR之间的SQL查询private
  10. JSON解析错误:无法识别的标记'<'处于角度