Android Debug

Android 7.1.2(Android N) Multi-w

2017-07-28  本文已影响992人  izhoujinjian

Android 7.1.2(Android N) Multi-window-mode--多窗口加载显示流程、退出流程

高清原文

(一)Multi-window-mode加载显示

首先贴一张总体流程图:

start-multi-window-mode.png

(1)RecentsButton(虚拟键)触发事件

长按RecentsButton(虚拟键)会触发KeyButtonView.java的onTouchEvent()方法
按下(ACTION_DOWN)后开始计时,如果一段时间ViewConfiguration.getLongPressTimeout()后,没有释放(ACTION_UP)
说明用户想长按,于是我们的postDelayed扔出了一个Runnable来进行长按处理。如果在ViewConfiguration.getLongPressTimeout()之内,用户释放(ACTION_UP)了,那就是个短按事件了,就会进入Recents Task 加载显示流程。
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java

    private final Runnable mCheckLongPress = new Runnable() {
    public void run() {
        if (isPressed()) {
            // Log.d("KeyButtonView", "longpressed: " + this);
            if (isLongClickable()) {
                // Just an old-fashioned ImageView
                performLongClick();
                mLongClicked = true;
            }
            ......
    };
    public boolean onTouchEvent(MotionEvent ev) {
    ......
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            ......
            //postDelayed长按事件
            postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
            break;
        ......
        case MotionEvent.ACTION_UP:
        ......
    }

进入performLongClick()->performLongClickInternal()方法

    /frameworks/base/core/java/android/view/View.java
    private boolean performLongClickInternal(float x, float y) {
    ......
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLongClickListener != null) {
        handled = li.mOnLongClickListener.onLongClick(View.this);
    }
    ......
   }

SystemUI启动的SystemBars的时候会注册RecentsButton长按事件(由于是分析多窗口显示加载流程,这里暂且不分析SystemBars启动流程),recentsButton.setOnLongClickListener(mRecentsLongClickListener)
最终会在PhoneStatusBar.java 里面触发分屏toggleSplitScreenMode()

/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

    //recentsButton长按事件具体实现
    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;
      }
    };
    private void prepareNavigationBarView() {
    ......
    ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
    ......
    recentsButton.setOnLongClickListener(mRecentsLongClickListener);
    ......
    }

这里我们看到,长按RecentsButton,代码逻辑为:

mRecents为空

不支持分屏

这里 1、upportsMultiWindow()方法判断了一个系统属性config_supportsMultiWindow为真 以及非低内存版本,则认为系统可以支持分屏 2、isSplitScreenFeasible( )方法判断当前分屏的大小,是否是满足系统要求的最小的分屏像素值。

/frameworks/base/core/java/android/app/ActivityManager.java
/**
 * Returns true if the system supports at least one form of multi-window.
 * E.g. freeform, split-screen, picture-in-picture.
 * @hide
 */
static public boolean supportsMultiWindow() {
    return !isLowRamDeviceStatic()
            && Resources.getSystem().getBoolean(
                com.android.internal.R.bool.config_supportsMultiWindow);
}

//isSplitScreenFeasible 判断当前分屏的大小,是否是满足系统要求的最小的分屏像素值。
/frameworks/base/core/java/com/android/internal/policy/DividerSnapAlgorithm.java

   //其中mMinimalSizeResizableTask 值为 <dimen name="default_minimal_size_resizable_task">220dp</dimen>
   mMinimalSizeResizableTask = res.getDimensionPixelSize(
            com.android.internal.R.dimen.default_minimal_size_resizable_task);
/**
 * @return whether it's feasible to enable split screen in the current configuration, i.e. when
 *         snapping in the middle both tasks are larger than the minimal task size.
 */
public boolean isSplitScreenFeasible() {
    int statusBarSize = mInsets.top;
    int navBarSize = mIsHorizontalDivision ? mInsets.bottom : mInsets.right;
    int size = mIsHorizontalDivision
            ? mDisplayHeight
            : mDisplayWidth;
    int availableSpace = size - navBarSize - statusBarSize - mDividerSize;
    return availableSpace / 2 >= mMinimalSizeResizableTask;
}

来到分屏的代码位置,这里有一个判断

/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

@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);
        }
    }
}

dockSide == WindowManager.DOCKED_INVALID 此状态表示当前没有处在分屏模式下,因此我们需要进入分屏

我们看下这里的WindowManagerProxy.getInstance().getDockSide()如何处理的
这里可以看到,来到了WMS(WindowManagerServer.java)位置,看下getDockedStackSide方法

/frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java

public int getDockSide() {
    try {
        return WindowManagerGlobal.getWindowManagerService().getDockedStackSide();
    } catch (RemoteException e) {
        Log.w(TAG, "Failed to get dock side: " + e);
    }
    return DOCKED_INVALID;
}
//这里如何判断的呢?找下当前的默认显示屏幕,然后判断下DockStack是否存在,如果存在,则在多窗口模式,如果不存在,则当前不是
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public int getDockedStackSide() {
    synchronized (mWindowMap) {
        final TaskStack dockedStack = getDefaultDisplayContentLocked()
                .getDockedStackVisibleForUserLocked();
        return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
    }
}

我们这里在启动分屏,所以此时不在分屏模式模式,于是乎,我们来到代码:千里之行,启程。

/frameworks/base/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
public interface RecentsComponent {
......
/**
 * Docks the top-most task and opens recents.
 */
boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
        int metricsDockAction);
......
}

进入Recents.java的dockTopTask()方法
/frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java

    @Override
public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
        int metricsDockAction) {
    ......
    if (runningTask != null && !isRunningTaskInHomeStack && !screenPinningActive) {
        ......
            if (sSystemServicesProxy.isSystemUser(currentUser)) {
                //代码走到mImpl.dockTopTask,我们直接过来(mImpl==RecentsImpl.java)
                mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
            } 
        ......
  }

/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);
    }
}

我们看到了代码走入了moveTaskToDockedStack,这里继续跟进,我们看到了:

    /frameworks/base/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
    /** Docks an already resumed task to the side of the screen. */
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;
}

这里mIam就是ActivityManagerService的代理端。此时,此方法moveTaskToDockedStack则会通过binder,进入到ActivityManagerService的对应方法里面。

(2)AMS 与 WMS 交互

image.png

我们来看下ActivityManagerService.java里面moveTaskToDockedStack方法的注释:
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    /**
 * Moves the input task to the docked stack.
 *
 * @param taskId Id of task to move.
 * @param createMode The mode the docked stack should be created in if it doesn't exist
 *                   already. See
 *                   {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
 *                   and
 *                   {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
 * @param toTop If the task and stack should be moved to the top.
 * @param animate Whether we should play an animation for the moving the task
 * @param initialBounds If the docked stack gets created, it will use these bounds for the
 *                      docked stack. Pass {@code null} to use default bounds.
 */
@Override
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);
            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);
        }
    }
}

参数为:

需要移动到docked stack的task id

createMode 创建横屏的还是竖屏的分屏

toTop 是否将这个task 和stack移动到最上面

animate 是否需要一个动画

initialBounds 初始化docked stack的边界值

我们看下这里的实际传递的参数:

taskId 这个不用管,只需要知道当前正在运行的TASK的id值即可。

dragMode = NavigationBarGestureHelper.DRAG_MODE_NONE

stackCreateMode=ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT

initialBounds = 屏幕大小信息,这里为 0 0 720 1280

moveHomeStackFront = true

animate=false

onTop = true

/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) {
    ......
    //停止surface更新,我们需要更新数据,随后使用continueSurfaceLayout继续。我们可以理解成一个锁,锁住画布。
    mWindowManager.deferSurfaceLayout();
    final int preferredLaunchStackId = stackId;
    boolean kept = true;
    try {
        final ActivityStack stack = moveTaskToStackUncheckedLocked(
                task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
        stackId = stack.mStackId;

        if (!animate) {
            stack.mNoAnimActivities.add(topActivity);
        }

        // We might trigger a configuration change. Save the current task bounds for freezing.
        mWindowManager.prepareFreezingTaskBounds(stack.mStackId);

        // Make sure the task has the appropriate bounds/size for the stack it is in.
        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();
    }

    if (mightReplaceWindow) {
        // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
        // window), we need to clear the replace window settings. Otherwise, we schedule a
        // timeout to remove the old window if the replacing window is not coming in time.
        mWindowManager.scheduleClearReplacingWindowIfNeeded(topActivity.appToken, !kept);
    }

    if (!deferResume) {

        // The task might have already been running and its visibility needs to be synchronized with
        // the visibility of the stack / windows.
        ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
        resumeFocusedStackTopActivityLocked();
    }

    handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);

    return (preferredLaunchStackId == stackId);
}

来到moveTaskToStackUncheckedLocked方法处.
移动特定的任务到传入的stack id(我们传入的为DOCKED_STACK_ID,移动的是当前最上面的那个TASK)

task 需要移入的task

stackId 移入的stackid (DOCKED_STACK_ID)

toTop = true

forceFocus ,默认取得反值

forceFocus =false(不需要强制Focus)

reason 就是个注释,我们不管

返回我们最终移入 的stack信息
/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

    /**
 * Moves the specified task record to the input stack id.
 * WARNING: This method performs an unchecked/raw move of the task and
 * can leave the system in an unstable state if used incorrectly.
 * Use {@link #moveTaskToStackLocked} to perform safe task movement to a stack.
 * @param task Task to move.
 * @param stackId Id of stack to move task to.
 * @param toTop True if the task should be placed at the top of the stack.
 * @param forceFocus if focus should be moved to the new stack
 * @param reason Reason the task is been moved.
 * @return The stack the task was moved to.
 */
ActivityStack moveTaskToStackUncheckedLocked(
        TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {

    ......
    // Temporarily disable resizeablility of task we are moving. We don't want it to be resized
    // if a docked stack is created below which will lead to the stack we are moving from and
    // its resizeable tasks being resized.
    task.mTemporarilyUnresizable = true;
    //获取这个栈,如果需要创建,创建它
    final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
    task.mTemporarilyUnresizable = false;
    //移动task到对应的stack上面
    mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
    //然后我们当前的stack,存储下task
    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;
}


   //getStack()法
    ActivityStack getStack(int stackId, boolean createStaticStackIfNeeded, boolean createOnTop) {
    ActivityContainer activityContainer = mActivityContainers.get(stackId);
    if (activityContainer != null) {
        return activityContainer.mStack;
    }
    if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
        return null;
    }
    return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
}


    ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
    //这里我们看到的ActivityDisplay 为获取对应displayId的一个实例,所以我们系统是支持多种显示设备的。
    ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
    if (activityDisplay == null) {
        return null;
    }
   //创建一个ActivityContainer(stackId),用来实现stack栈信息,然后存储下来。
    ActivityContainer activityContainer = new ActivityContainer(stackId);
    mActivityContainers.put(stackId, activityContainer);
    activityContainer.attachToDisplayLocked(activityDisplay, onTop);
    return activityContainer.mStack;
}
        void attachToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
        if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this
                + " to display=" + activityDisplay + " onTop=" + onTop);
        mActivityDisplay = activityDisplay;
        //这里便是将这个stack挂在对应显示屏的列表上面(一般我们默认显示屏是手机)
        mStack.attachDisplay(activityDisplay, onTop);
        activityDisplay.attachActivities(mStack, onTop);
    }

/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

我们看到了attachDisplay方法

    void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
    mDisplayId = activityDisplay.mDisplayId;
    mStacks = activityDisplay.mStacks;
    mBounds = mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
    mFullscreen = mBounds == null;
    if (mTaskPositioner != null) {
        mTaskPositioner.setDisplay(activityDisplay.mDisplay);
        mTaskPositioner.configure(mBounds);
    }

    if (mStackId == DOCKED_STACK_ID) {
        // If we created a docked stack we want to resize it so it resizes all other stacks
        // in the system.
        mStackSupervisor.resizeDockedStackLocked(
                mBounds, null, null, null, null, PRESERVE_WINDOWS);
    }
}

关键方法attachStack,我们跟入看下:(首先看注释)

创建一个taskstack放置在对应的显示容器内

stackId ==栈Id,我们这里认为为DOCKED_STACK_ID

displayId =我们认为为默认屏,手机即可

onTop = true

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    /**
 * Create a new TaskStack and place it on a DisplayContent.
 * @param stackId The unique identifier of the new stack.
 * @param displayId The unique identifier of the DisplayContent.
 * @param onTop If true the stack will be place at the top of the display,
 *              else at the bottom
 * @return The initial bounds the stack was created with. null means fullscreen.
 */
public Rect attachStack(int stackId, int displayId, boolean onTop) {
    final long origId = Binder.clearCallingIdentity();
    try {
        synchronized (mWindowMap) {
            final DisplayContent displayContent = mDisplayContents.get(displayId);
            boolean attachedToDisplay = false;
            if (displayContent != null) {
                TaskStack stack = mStackIdToStack.get(stackId);
                if (stack == null) {
                    if (DEBUG_STACK) Slog.d(TAG_WM, "attachStack: stackId=" + stackId);

                    stack = displayContent.getStackById(stackId);
                    if (stack != null) {
                        // It's already attached to the display. Detach and re-attach
                        // because onTop might change, and be sure to clear mDeferDetach!
                        displayContent.detachStack(stack);
                        stack.mDeferDetach = false;
                        attachedToDisplay = true;
                    } else {
                    //创建了TaskStack
                        stack = new TaskStack(this, stackId);
                    }
                    //我们创建了TaskStack,于是系统通知systemui,显示divider线。
                    mStackIdToStack.put(stackId, stack);
                    if (stackId == DOCKED_STACK_ID) {
                        getDefaultDisplayContentLocked().mDividerControllerLocked
                                .notifyDockedStackExistsChanged(true);
                    }
                }
                if (!attachedToDisplay) {
                //这里又出现一个方法,stack.attachDisplayContent(displayContent);我们仔细看下它
                    stack.attachDisplayContent(displayContent);
                }
                displayContent.attachStack(stack, onTop);
                if (stack.getRawFullscreen()) {
                    return null;
                }
                Rect bounds = new Rect();
                stack.getRawBounds(bounds);
                return bounds;
            }
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
    return null;
}

/frameworks/base/services/core/java/com/android/server/wm/TaskStack.java

这里直接有值了,我们直接赋值返回了,于是我们说下这个值从哪赋值的mDockedStackCreateBounds
我们之前看到的,moveTaskToDockedStack --> 方法里面有个 mWindowManager.setDockedStackCreateState(createMode, initialBounds); 这里给了赋值。(需要看的可以向上重新回头阅读下这个信息)

    void attachDisplayContent(DisplayContent displayContent) {
    ...... final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
                == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
        getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
                mDisplayContent.mDividerControllerLocked.getContentWidth(),
                dockedOnTopOrLeft);
    }

    updateDisplayInfo(bounds);
}

回到ActivityStack.java,attachDisplay方法,看到如下代码:

        if (mStackId == DOCKED_STACK_ID) {
        // If we created a docked stack we want to resize it so it resizes all other stacks
        // in the system.
        mStackSupervisor.resizeDockedStackLocked(
                mBounds, null, null, null, null, PRESERVE_WINDOWS);
    }

我们需要调整task的大小信息。
/frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

    void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
        Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
        boolean preserveWindows, boolean deferResume) {

    ......
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
    mWindowManager.deferSurfaceLayout();
    try {
        // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
        mAllowDockedStackResize = false;
        ActivityRecord r = stack.topRunningActivityLocked();
        resizeStackUncheckedLocked(stack, dockedBounds, tempDockedTaskBounds,
                tempDockedTaskInsetBounds);

        ......
        if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) {
            ......
        } else {
            // Docked stacks occupy a dedicated region on screen so the size of all other
            // static stacks need to be adjusted so they don't overlap with the docked stack.
            // We get the bounds to use from window manager which has been adjusted for any
            // screen controls and is also the same for all stacks.
            mWindowManager.getStackDockedModeBounds(
                    HOME_STACK_ID, tempRect, true /* ignoreVisibility */);
            for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
                if (StackId.isResizeableByDockedStack(i) && getStack(i) != null) {
                    resizeStackLocked(i, tempRect, tempOtherTaskBounds,
                            tempOtherTaskInsetBounds, preserveWindows,
                            true /* allowResizeInDockedMode */, deferResume);
                }
            }
        }
        if (!deferResume) {
            stack.ensureVisibleActivitiesConfigurationLocked(r, preserveWindows);
        }
    } finally {
        mAllowDockedStackResize = true;
        mWindowManager.continueSurfaceLayout();
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }
    ......
}

-->deferSurfaceLayout() [WMS]
resizeDockedStackLocked() [ActivityStackSupervisor.java]
---->resizeStackUncheckedLocked() [ActivityStackSupervisor.java]
---->resizeStackLocked() [ActivityStackSupervisor.java]
-->continueSurfaceLayout() [WMS]
-->continueLayout) [WindowSurfacePlacer.java]
-->performSurfacePlacement() [WindowSurfacePlacer.java]
-->performSurfacePlacementLoop() [WindowSurfacePlacer.java]
-->performSurfacePlacementInner() [WindowSurfacePlacer.java]
-->applySurfaceChangesTransaction() [WindowSurfacePlacer.java]
-->performLayoutLockedInner() [WindowSurfacePlacer.java]
-->beginLayoutLw() && layoutWindowLw() [PhoneWindowManager.java]
-->computeFrameLw() [WindowState.java]
我们这里不再深入细化,因为这里逻辑太多,以后再做研究,我们当前需要了解的是:

系统这个时候,重新将所有的task大小计算,我们一般应用所在的FULL_SCREEN_STACK 会重新调整,然后给当前app通知进入分屏。

为什么讲这个呢?因为这里是系统向activity发出的回调,告知系统进入分屏模式,需要activity作出响应的地方。

image.png

系统在此时发送了REPORT_MULTI_WINDOW_MODE_CHANGED_MSG消息出去。最终会到activity的onMultiWindowModeChanged 生命周期中。

Task和Stack

Android系统中的每一个Activity都位于一个Task中。一个Task可以包含多个Activity,同一个Activity也可能有多个实例。 在AndroidManifest.xml中,我们可以通过android:launchMode来控制Activity在Task中的实例。

另外,在startActivity的时候,我们也可以通过setFlag 来控制启动的Activity在Task中的实例。

Task管理的意义还在于近期任务列表以及Back栈。 当你通过多任务键(有些设备上是长按Home键,有些设备上是专门提供的多任务键)调出多任务时,其实就是从ActivityManagerService获取了最近启动的Task列表。

Back栈管理了当你在Activity上点击Back键,当前Activity销毁后应该跳转到哪一个Activity的逻辑。关于Task和Back栈,请参见这里:Tasks and Back Stack。

其实在ActivityManagerService与WindowManagerService内部管理中,在Task之外,还有一层容器,这个容器应用开发者和用户可能都不会感觉到或者用到,但它却非常重要,那就是Stack。 下文中,我们将看到,Android系统中的多窗口管理,就是建立在Stack的数据结构上的。 一个Stack中包含了多个Task,一个Task中包含了多个Activity(Window),下图描述了它们的关系:

image.png

另外还有一点需要注意的是,ActivityManagerService和WindowManagerService中的Task和Stack结构是一一对应的,对应关系对于如下:

ActivityStack <–> TaskStack
TaskRecord <–> Task
即,ActivityManagerService中的每一个ActivityStack或者TaskRecord在WindowManagerService中都有对应的TaskStack和Task,这两类对象都有唯一的id(id是int类型),它们通过id进行关联。
总结:
如此一来,我们就创建出来DOCKED_STACK_ID的一个栈了,其中stack是维护task任务的,task是维护activity的。它们就是如此的关系。然后我们系统将创建好的stack关联到WMS,调整task的大小,然后通知当前的activity,我们当前进入分屏模式下了,你要在你的onMultiWindowModeChanged 里面做出响应。(看到了吗?我们分屏在activity的一个生命周期方法,在此处出现了)

moveTaskToStackUncheckedLocked 里面主要做了几件事情

final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop); 获取DOCK_STACK,如果没有,就创建它

task.mTemporarilyUnresizable = false;

mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop); 移动当前的task进入DOCK_STACK里面,更新状态,传递divider显示与否的消息到systemui,传递给activity onMultiWindowModeChanged,来告知消息。

stack.addTask(task, toTop, reason); 加入当前的AMS的管理里面即可。

后面触发了mWindowPlacerLocked.performSurfacePlacement();方法,引发绘制动作,我们的分屏启动完成了。

(3)Divider位置

我们再来看一个问题,就是我们的分屏,会在屏幕上画出一个分割线,这个线的位置如何定义出来的呢?

我们回到DividerWindowManager.java ,我们之前讲过,我们的分割线是在分屏开启后进行显示,加入到WMS里面去,我们可以看到一个关键信息

/frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java

    public void add(View view, int width, int height) {

    Log.i(TAG, "zhoujinjian DividerWindowManager add()");
    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;
}

这里我们看到 TYPE_DOCK_DIVIDER,是这个View的类型,非常特殊。

我们搜索这个关键字,可以看到很多内容,我们简单说下里面一些:

WindowManagerService.java 里 addWindow,

            if (type == TYPE_DOCK_DIVIDER) {
            getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
        }

这里为如果当前View的类型为TYPE_DOCK_DIVIDER 我们要加入到DockedDividerController里面,按照上一节的说法,这个DockedDividerController会在系统的Vsync里面,实时触发,这里则会判断是否有divider之类的状态。
/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
PhoneWindowManager.java 里面的 layoutWindowLw 方法:

else if (canHideNavigationBar()
                    && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                    && (attrs.type == TYPE_STATUS_BAR
                        || attrs.type == TYPE_TOAST
                        || attrs.type == TYPE_DOCK_DIVIDER
                        || attrs.type == TYPE_VOICE_INTERACTION_STARTING
                        || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
                        && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
                // Asking for layout as if the nav bar is hidden, lets the
                // application extend into the unrestricted screen area.  We
                // only do this for application windows (or toasts) to ensure no window that
                // can be above the nav bar can do this.
                // XXX This assumes that an app asking for this will also
                // ask for layout in only content.  We can't currently figure out
                // what the screen would be if only laying out to hide the nav bar.
                pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
                pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
                pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
                        + mUnrestrictedScreenWidth;
                pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
                        + mUnrestrictedScreenHeight;
            } 

给TYPE_DOCK_DIVIDER 赋值绘制区域,系统边界值的信息。
/frameworks/base/services/core/java/com/android/server/wm/WindowState.java
我们再看一个类WindowState.java,里面的关键方法computeFrameLw

有段内容:

else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
        mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
        mContentFrame.set(mFrame);
        if (!mFrame.equals(mLastFrame)) {
            mMovedByResize = true;
        }
    }

我们打个断点在这里:


EM截图_2017121910554.png

我们惊奇的发现,我们的栈里面有performSurfacePlacementLoop,还有moveTaskToStackLocked-->mWindowManager.continueSurfaceLayout(); 这里触发了启动绘制。看到这条线路,我们可以找到整个窗体的计算过程路径。
这里我们关心的是这个分割线的位置:(这里mFrame便是计算好的位置信息了,我们当前值为 Rect(0, 568 - 720, 664),高96,看这里是不是在屏幕中间)

/frameworks/base/services/core/java/com/android/server/wm/DockedStackDividerController.java

    void positionDockedStackedDivider(Rect frame) {
    TaskStack stack = mDisplayContent.getDockedStackLocked();
    if (stack == null) {
        // Unfortunately we might end up with still having a divider, even though the underlying
        // stack was already removed. This is because we are on AM thread and the removal of the
        // divider was deferred to WM thread and hasn't happened yet. In that case let's just
        // keep putting it in the same place it was before the stack was removed to have
        // continuity and prevent it from jumping to the center. It will get hidden soon.
        frame.set(mLastRect);
        return;
    } else {
        stack.getDimBounds(mTmpRect);
    }
    int side = stack.getDockSide();
    switch (side) {
        case DOCKED_LEFT:
            frame.set(mTmpRect.right - mDividerInsets, frame.top,
                    mTmpRect.right + frame.width() - mDividerInsets, frame.bottom);
            break;
        case DOCKED_TOP:
            frame.set(frame.left, mTmpRect.bottom - mDividerInsets,
                    mTmpRect.right, mTmpRect.bottom + frame.height() - mDividerInsets);
            break;
        case DOCKED_RIGHT:
            frame.set(mTmpRect.left - frame.width() + mDividerInsets, frame.top,
                    mTmpRect.left + mDividerInsets, frame.bottom);
            break;
        case DOCKED_BOTTOM:
            frame.set(frame.left, mTmpRect.top - frame.height() + mDividerInsets,
                    frame.right, mTmpRect.top + mDividerInsets);
            break;
    }
    mLastRect.set(frame);
}

依据我们的DOCKED_STACK的位置,去计算frame,这里我们为TOP

而这里的96如何来的呢?我们知道创建分割线的地方为 Divider.java的addDivider,里面有个信息:
/frameworks/base/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java

    private void addDivider(Configuration configuration) {
        Log.i(TAG, "zhoujinjian Divider addDivider()");
    mView = (DividerView)
            LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
    mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
    //我们这里的dp2px=2,所以会是96高。
    final int size = mContext.getResources().getDimensionPixelSize(
            com.android.internal.R.dimen.docked_stack_divider_thickness);
    final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
    final int width = landscape ? size : MATCH_PARENT;
    final int height = landscape ? MATCH_PARENT : size;
    mWindowManager.add(mView, width, height);
    mView.injectDependencies(mWindowManager, mDividerState);
}

如上,我们发现我们穿过层层阻碍,走完了分屏的创建过程的大半过程。分屏过程错复杂,我们还有个触发最近列表的过程需要讲解。
我们看到了这里,经历了dock的整个创建过程,我们再回到我们出发的起点位置,看个内容:

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);
    }
}

RecentsImpl.java的dockTopTask方法,我们启动分屏的开始部分。
我们看到了,如果创建成功,我们进入里面的方法EventBus的send我们不去关注了,想要了解的,去看看EventBus的总线方式,以及如何解耦的。核心便是通过注解,系统将需要接收的通过方法的参数类型进行匹配。
我们需要看下面的:showRecents ,这个便是我们进入分屏,下方出现的最近列表界面启动的地方。此方法我们不详细扩展了,本质就是启动了一个activity即可(startRecentsActivity)。

(二)Multi-window-mode 退出流程

exit-multi-window-mode.png

参考文档:

浅析Android的窗口
Android 7.0中的多窗口实现解析
google 进入分屏后在横屏模式按 home 键界面错乱 (二)

上一篇下一篇

猜你喜欢

热点阅读