Android V app 冷启动(4) 启动窗口的绘制

579 阅读14分钟

上一篇文章,分析了如何添加启动窗口 View,本文来研究启动窗口的绘制,但是侧重点在 WMS 侧。

add window

在 WMShell 中,启动窗口 View 是直接通过 WindowManagerGlobal 添加的,WindowManagerGlobal 会创建 ViewRootImpl ,并调用 ViewRootImpl#setView() 保存启动窗口 View,如下

// ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
            // 保存 DecorView
            mView = view;

            // ...

            // 向 Choreographer 注册下一帧的回调
            // 最终执行 performTraversal()
            requestLayout();

            // ...

            try {
                // ...
                
                // 向 WMS 发起 add window
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                        mTempControls, attachedFrame, compatScale);

                //...

            } 
            
            // ...
        }
    }
}

来看下 WMS 的 add window 流程

// WindowManagerService.java

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
        int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
        float[] outSizeCompatScale) {
    // ...

    final int type = attrs.type;
    synchronized (mGlobalLock) {
        // ...
        ActivityRecord activity = null;
        // false
        final boolean hasParent = parentWindow != null;
        // 这个 WindowToken 就是 ActivityRecord
        WindowToken token = displayContent.getWindowToken(
                hasParent ? parentWindow.mAttrs.token : attrs.token);
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;

        // ...

        if (token == null) {
            // ...
        } else if (rootType >= FIRST_APPLICATION_WINDOW
                && rootType <= LAST_APPLICATION_WINDOW) {
            // WindowToken 转化为 ActivityRecord
            activity = token.asActivityRecord();
            // ...
        } // ..

        // 1.创建WindowState
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], attrs, viewVisibility, session.mUid, userId,
                session.mCanAddInternalSystemWindow);
        
        // ...

        // mWindowMap 保存 app 端到 WMS 端的窗口映射
        mWindowMap.put(client.asBinder(), win);
        
        // ...

        // 2.ActivityRecord 保存 WindowState
        win.mToken.addWindow(win);
        
        if (type == TYPE_APPLICATION_STARTING && activity != null) {
            // 3. ActivityRecord#mStartingWindow 保存启动窗口的 WindowState
            // WindowState#mStartingData 保存启动窗口数据
            activity.attachStartingWindow(win);
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
                    activity, win);
        } // ...


        // ...

        if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) {
            
        } else {
            // 4. 通过 parent 更新 WindowState layer
            // 由于启动窗口 WindowState 有了 mStartingData,此时它的 layer 被设置为 Integer.MAX_VALUE
            win.getParent().assignChildLayers();
        }
        // ...
        
        // 5. 如果 ActivityRecord 可见,那么执行 update orientation
        boolean needToSendNewConfiguration =
                win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
        // ...
    }
    return res;
}

在 WMS 端,WindowState 代表窗口,它会被保存到 ActivityRecord 下。

另外,如果第二阶段启动,发生在 add window 之前,那么 WMS add window 还会执行 update orientation 来更新方向,因为第二阶段启动,会把待启动的 ActivityRecord 的可见性(visible-requested)更新为 true。

其实,update orientation 还会在 WMS relayout window 中执行,为了方便文章的排版,我们留到 relayout window 时再分析。

relayout window

在 ViewRootImpl#setView() 中, 向 Choreographer 注册了下一帧的回调,这个回调最终会在 主线程 执行 performTraversal()

private void performTraversals() {
    // ...

    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    if (layoutRequested) {
        if (!mFirst) {
            
        }

        // measure
        // 可以获得 measured width 和 measured height
        windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
                desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
    }

    // ...


    if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
            || mForceNextWindowRelayout) {
        // ...

        try {
            // ...

            // relayout window
            // 使用 measured width 和 measured height 向 WMS 发起 relayout
            // 获取用于绘制的 surface,以及窗口的 frame
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);


            // ...
        } 
        
        // ...
    } else {

    }

    // ...

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        // 使用 relayout 后获得的窗口的大小,进行 layout
        performLayout(lp, mWidth, mHeight);

        // ...

    }

    // ...

    if (!isViewVisible) {
        
    } else if (cancelAndRedraw) {
        
    } else {
        // ...

        // draw
        if (!performDraw(mActiveSurfaceSyncGroup)) {

        }
    }

    // ...
}

在一次 traversal 中,会完成窗口的绘制,但是前提是通过 relayout window 得到绘制的 surface,如下

// ViewRootImpl.java

// mRelayoutResult 就是包装几个参数
private final WindowRelayoutResult mRelayoutResult = windowSessionRelayoutInfo()
        ? new WindowRelayoutResult(mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
                mTempInsets, mTempControls)
        : null;
        
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
        boolean insetsPending) throws RemoteException {
    // ...
    
    // 测量后的宽高
    final int measuredWidth = mMeasuredWidth;
    final int measuredHeight = mMeasuredHeight;
    final boolean relayoutAsync;
    if ((mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
            && mWindowAttributes.type != TYPE_APPLICATION_STARTING
            && mSyncSeqId <= mLastSyncSeqId
            && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
        // ..
    } else {
        relayoutAsync = false;
    }

    // ...

    // 根据测量后的宽高,计算向 WMS relayout 时,需要请求的宽高
    // appScale 此时为 1
    final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
    final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
    
    int relayoutResult = 0;
    mRelayoutSeq++;
    if (relayoutAsync) {
        
    } else {
        if (windowSessionRelayoutInfo()) {
            // 向 WMS 发起 relayout
            // requestedWidth 和 requestedHeight 可以简单认为是 measured width 和 measured height
            // viewVisibility 为 VISIBLE
            // relayout window 返回的数据保存到 mRelayoutResult
            relayoutResult = mWindowSession.relayout(mWindow, params,
                    requestedWidth, requestedHeight, viewVisibility,
                    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                    mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
        } else {
            
        }
        
        // ...
    }

    // ...

    // mWinFrame 保存 ralayout 后得到的窗口大小
    setFrame(mTmpFrames.frame, true /* withinRelayout */);
    return relayoutResult;
}

来看下 WMS relayout window 的实现

// WindowManagerService.java

private int relayoutWindowInner(Session session, IWindow client, LayoutParams attrs,
        int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
        int lastSyncSeqId, ClientWindowFrames outFrames,
        MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
        InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
        Bundle outBundle, WindowRelayoutResult outRelayoutResult) {
    // ...
    
    synchronized (mGlobalLock) {
        final WindowState win = windowForClientLocked(session, client, false);
        
        // ...
        
        if (viewVisibility != View.GONE) {
            // WindowState 的 mRequestedWidth 和 mRequestedHeight 保存请求的宽高
            win.setRequestedSize(requestedWidth, requestedHeight);
        }

        // ...

        // 标记 DisplayContent 需要 layout
        win.setDisplayLayoutNeeded();
        
        // ...
        
        // viewVisibility 是 VISIBLE ,并且启动窗口类型为 TYPE_APPLICATION_STARTING
        // shouldRelayout 为 true
        final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
                (win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
                        || win.mActivityRecord.isClientVisible());

        // ...

        if (shouldRelayout && outSurfaceControl != null) {
            try {
                // 1.创建 surface
                result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
            } catch (Exception e) {
                
            }
        }

        // 2.强制执行一次窗口刷新
        mWindowPlacerLocked.performSurfacePlacement(true /* force */);

        // ...

        // 3.更新方向
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: updateOrientation");
        configChanged |= displayContent.updateOrientation();
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

        // ...

        // 4.填充需要返回的数据
        if (outFrames != null && outMergedConfiguration != null) {
            final boolean shouldReportActivityWindowInfo;
            if (Flags.windowSessionRelayoutInfo()) {
                shouldReportActivityWindowInfo = outRelayoutResult != null
                        && win.mLastReportedActivityWindowInfo != null;
            } else {

            }
            
            final ActivityWindowInfo outActivityWindowInfo = shouldReportActivityWindowInfo
                    ? new ActivityWindowInfo()
                    : null;
            
            // 主要从窗口frame填充到outFrames
            win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
                    outActivityWindowInfo, false /* useLatestConfig */, shouldRelayout);

            if (shouldReportActivityWindowInfo) {
                if (Flags.windowSessionRelayoutInfo()) {
                    outRelayoutResult.activityWindowInfo = outActivityWindowInfo;
                } else {

                }
            }

            // ...
        }

        // ...

    }

    Binder.restoreCallingIdentity(origId);
    return result;
}

WMS relayout window 主要做了如下事情

  1. 创建 surface ,并挂载到 WindowState surface 之下,这个新建的 surface 才是真正的窗口 surface,它被 app 端用于绘制。
  2. 强制执行一次窗口刷新,主要是为了得到窗口的 frame。
  3. update orientation,尝试更新系统 rotation。对于本文分析的案例而言,这里会执行著名的 Fixed Rotation。
  4. 填充需要返回的数据,包括窗口 frame,config,等等。

在后面的分析中,我会使用 窗口surface,它代表 app 端绘制用的 surface。

创建 surface

// WindowManagerService.java

private int createSurfaceControl(SurfaceControl outSurfaceControl, int result,
        WindowState win, WindowStateAnimator winAnimator) {
    if (!win.mHasSurface) {
        // 返回给 app 的结果中,标记 surface change
        result |= RELAYOUT_RES_SURFACE_CHANGED;
    }

    WindowSurfaceController surfaceController;
    try {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
        // WindowStateAnimator 创建的是 WindowSurfaceController
        // WindowSurfaceController 在构造的时候,会创建用于 app 绘制的 surface
        surfaceController = winAnimator.createSurfaceLocked();
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
    
    
    if (surfaceController != null) {
        // WindowSurfaceController 把创建的 surface 复制给 app 端
        surfaceController.getSurfaceControl(outSurfaceControl);
        ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);

    } else {
        
    }

    return result;
}
// WindowStateAnimator.java

WindowSurfaceController createSurfaceLocked() {
    final WindowState w = mWin;

    if (mSurfaceController != null) {
        return mSurfaceController;
    }

    w.setHasSurface(false);

    ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this);

    // 重置绘制状态为 mDrawState 为 DRAW_PENDING
    // 绘制状态,初始为 NO_SURFACE
    resetDrawState();

    mService.makeWindowFreezingScreenIfNeededLocked(w);

    // 创建 surface 时,默认为 hidden 状态,即不可见
    int flags = SurfaceControl.HIDDEN;
    final WindowManager.LayoutParams attrs = w.mAttrs;

    // ...
    
    try {
        
        // 默认是开启硬件加速
        final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
        // TRANSLUCENT
        final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;

        // 创建 WindowSurfaceController
        // 构造函数中会创建 surface
        mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
                flags, this, attrs.type);
                
        if (!setScPropertiesInClient()) {
            mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
                    (attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
        }
        
        // WindowState#mHasSurface 更新为 true,标记已经有了 surface
        w.setHasSurface(true);

        w.mInputWindowHandle.forceChange();

        ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
                    "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
                    mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
                    flags, this);
    } 
    
    // ...
    
  
    // 标记 surface 处于 hidden 状态
    mLastHidden = true;

    if (DEBUG) Slog.v(TAG, "Created surface " + this);
    return mSurfaceController;
}
// WindowSurfaceController.java

WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
        int windowType) {
    mAnimator = animator;

    title = name;

    mService = animator.mService;
    final WindowState win = animator.mWin;
    mWindowType = windowType;
    mWindowSession = win.mSession;

    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
    // 构建 surface,这才是 app 用来绘制的 surface
    mSurfaceControl = win.makeSurface()
            // 注意,surface 挂载到 WindowState surface 之下
            .setParent(win.getSurfaceControl())
            .setName(name)
            .setFormat(format)
            .setFlags(flags)
            .setMetadata(METADATA_WINDOW_TYPE, windowType)
            .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
            .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
            .setCallsite("WindowSurfaceController")
            .setBLASTLayer()
            .build();

    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

WindowSurfaceController 控制了窗口 surface 的创建,复制,等等。窗口 surface 在创建的时候,指定了其 parent 就是 WindowState surface。

窗口刷新

窗口刷新的流程比较长,但是此次窗口刷新,主要是为了得到启动窗口的 frame,因此,主要看 layout 流程

// DisplayContent.java

private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
    // relayout 时,标记了 DisplayContent 需要 layout
    // 因此,这里能对 DisplayContent 下的所有 surface 进行 layout
    if (!isLayoutNeeded()) {
        return;
    }
    
    clearLayoutNeeded();

    // ...
    
    // from top to bottom,对所有非子窗口进行 layout
    forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

    // ...
}


private final Consumer<WindowState> mPerformLayout = w -> {
    // ...

    // ...
    
    // 启动窗口,此时还没有 frame,即 w.mHaveFrame 为 false
    // 因此,它需要 layout 来确定 frame
    if (!gone || !w.mHaveFrame || w.mLayoutNeeded) {
        // ...
        
        getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
        
        // ...
    }
};
// DisplayPolicy.java

public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
    if (win.skipLayout()) {
        return;
    }

    // This window might be in the simulated environment.
    // We invoke this to get the proper DisplayFrames.
    displayFrames = win.getDisplayFrames(displayFrames);

    // 启动窗口的 atrrs ,没有根据旋转方向进行区分
    final WindowManager.LayoutParams attrs = win.mAttrs.forRotation(displayFrames.mRotation);
    // null
    sTmpClientFrames.attachedFrame = attached != null ? attached.getFrame() : null;

    // If this window has different LayoutParams for rotations, we cannot trust its requested
    // size. Because it might have not sent its requested size for the new rotation.
    // true
    final boolean trustedSize = attrs == win.mAttrs;
    // 就是 app 请求的宽高
    final int requestedWidth = trustedSize ? win.mRequestedWidth : UNSPECIFIED_LENGTH;
    final int requestedHeight = trustedSize ? win.mRequestedHeight : UNSPECIFIED_LENGTH;

    // 由 WindowLayout 计算窗口 frame
    // 计算的结果保存到参数 sTmpClientFrames
    mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
            win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
            win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames);

    // WindowState 保存窗口的所有 frame
    win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}
// WindowState.java

void setFrames(ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
    final WindowFrames windowFrames = mWindowFrames;
    mTmpRect.set(windowFrames.mParentFrame);
    
    // 保存窗口的 frame
    windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
    windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
    windowFrames.mFrame.set(clientWindowFrames.frame);

    windowFrames.mCompatFrame.set(windowFrames.mFrame);
    
    // ...
    
    
    // 目前只是得到了窗口的 frame,但是还没有在 surface 上生效
    mSurfacePlacementNeeded = true;
    
    // 此时,窗口才算真正有 frame 数据了
    mHaveFrame = true;
}

这里,我并没有展示如何计算窗口 frame 的代码。因为这需要详细的数据来支撑计算。

update orientation

// DisplayContent.java

boolean updateOrientation() {
    return updateOrientation(false /* forceUpdate */);
}

private boolean updateOrientation(boolean forceUpdate) {
    final WindowContainer prevOrientationSource = mLastOrientationSource;
    
    // 获取 orientation
    // 对于本文分析的案例,就是待启动 Activity 的声明的横屏方向
    // 并且还会更新 orientation source
    final int orientation = getOrientation();
    
    // The last orientation source is valid only after getOrientation.
    // 获取最新的 orientation source
    // 对于本文分析的案例,它就是待启动的 ActivityRecord
    final WindowContainer orientationSource = getLastOrientationSource();
    
    // ...
    
    // 就是要启动的 ActvityRecord
    final ActivityRecord r =
            orientationSource != null ? orientationSource.asActivityRecord() : null;
            
    if (r != null) {
        // ...
        
        // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
        // 对于本文分析的案例,topCandidate 就是要启动的 ActivityRecord
        final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
        
        // 处理要启动的 activity 的方向与系统方向不同的情况
        if (topCandidate != null && handleTopActivityLaunchingInDifferentOrientation(
                topCandidate, r, true /* checkOpening */)) {
            // 走了 fixed rotation,就暂时不走 update orientation
            // Display orientation should be deferred until the top fixed rotation is finished.
            return false;
        }
    }
    
    // 不会走到这里...
    
    // 继续执行 update orientation
    return mDisplayRotation.updateOrientation(orientation, forceUpdate);
}


private boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
        @NonNull ActivityRecord orientationSrc, boolean checkOpening) {
    // ...
    
    // 根据 orientation 计算 rotation
    final int rotation = rotationForActivityInDifferentOrientation(orientationSrc);
    
    // 这里表示,计算出的旋转方向,与系统方向一致,那么不走 fixed rotation
    if (rotation == ROTATION_UNDEFINED) {
        return false;
    }
        
    // ...

    // 开始 fixed rotation 流程
    setFixedRotationLaunchingApp(r, rotation);
    return true;
}

待启动 app 的 MainActivity ,它声明的 orientation 是 LANDSCAPE,根据 orientation 计算出的 rotation 是 ROTATION_90,而当前系统的 rotation 是 ROTATION_0,因此触发 Fixed Rotation 机制,从而暂停执行 update orientation。

来看下如何根据 orientation 计算 rotation,如下

// DisplayContent.java

int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) {
    // ...
    
    // 获取系统当前的旋转方向
    final int currentRotation = getRotation();
    
    // 根据 activity 的 orientation,计算新的旋转方向
    final int rotation = mDisplayRotation.rotationForOrientation(r.getRequestedOrientation(),
            currentRotation);

    // 如果方向没有改变,返回 ROTATION_UNDEFINED
    if (rotation == currentRotation) {
        return ROTATION_UNDEFINED;
    }
    
    // 如果方向有改变,返回新的方向
    return rotation;
}
// DisplayRotation.java

int rotationForOrientation(@ScreenOrientation int orientation,
        @Surface.Rotation int lastRotation) {
    // ...

    switch (orientation) {
        // ...

        case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
            // Return landscape unless overridden.
            if (isLandscapeOrSeascape(preferredRotation)) {
                return preferredRotation;
            }
            return mLandscapeRotation;

        // ...
    }
}

填充返回的信息

// 前三个参数就是要填充的数据,最后一个参数 shouldRelayout 为 true
win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
        outActivityWindowInfo, false /* useLatestConfig */, shouldRelayout);
// WindowState.java

void fillClientWindowFramesAndConfiguration(@NonNull ClientWindowFrames outFrames,
        @NonNull MergedConfiguration outMergedConfiguration,
        @Nullable ActivityWindowInfo outActivityWindowInfo,
        boolean useLatestConfig, boolean relayoutVisible) {
    
    // 填充窗口 frame
    outFrames.frame.set(mWindowFrames.mCompatFrame);
    outFrames.displayFrame.set(mWindowFrames.mDisplayFrame);
    
    if (mInvGlobalScale != 1f) {
        
    }
    if (mLayoutAttached) {
        
    }
    
    outFrames.compatScale = getCompatScaleForClient();
    
    if (mLastReportedFrames != outFrames) {
        // 保存最新发送的 frame
        mLastReportedFrames.setTo(outFrames);
    }

    
    // 填充 config
    // 参数 useLatestConfig 为 false,参数 relayoutVisible 为 true
    if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
            || mActivityRecord.isVisibleRequested()))) {
            
        final Configuration globalConfig = getProcessGlobalConfiguration();
        
        final Configuration overrideConfig = getMergedOverrideConfiguration();
        
        outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
        
        if (outMergedConfiguration != mLastReportedConfiguration) {
            mLastReportedConfiguration.setTo(outMergedConfiguration);
        }
        
        if (outActivityWindowInfo != null && mLastReportedActivityWindowInfo != null) {
            outActivityWindowInfo.set(mActivityRecord.getActivityWindowInfo());
            mLastReportedActivityWindowInfo.set(outActivityWindowInfo);
        }        
    } else {
        outMergedConfiguration.setTo(mLastReportedConfiguration);
        if (outActivityWindowInfo != null && mLastReportedActivityWindowInfo != null) {
            outActivityWindowInfo.set(mLastReportedActivityWindowInfo);
        }       
    }
    
    // 注意,此时代表配置已经发送给 app 端
    // 之后窗口刷新的 resize window,不会检测到窗口配置改变了
    mLastConfigReportedToClient = true;
}

Fixed Rotation

理论上讲,从竖屏的 Launcher,启动一个横屏的 app,系统要发生旋转,才能显示横屏的 app。

但是,AOSP 另辟蹊径,它先让 app 端绘制一个横屏界面,然后 WMS 对 surface 应用一个 transform ,例如,旋转、位置,使得横屏的界面,完整的的显示在竖屏的屏幕下,看起来像是系统已经旋转到横屏。这就是 Fixed Rotation 机制。

// DisplayContent.java

private boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
        @NonNull ActivityRecord orientationSrc, boolean checkOpening) {
    // ...
    
    
    // 根据 Activity 提供的 orientation,计算 rotation
    final int rotation = rotationForActivityInDifferentOrientation(orientationSrc);
    
    // ...

    setFixedRotationLaunchingApp(r, rotation);
    return true;
}


void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Rotation int rotation) {
    // ...

    if (!r.hasFixedRotationTransform()) {
        // start fixed rotation
        startFixedRotationTransform(r, rotation);
    }
    
    // set fixed rotation app
    setFixedRotationLaunchingAppUnchecked(r, rotation);
    
    // ...
}


// rotation 此时为计算出的旋转方向
private void startFixedRotationTransform(WindowToken token, int rotation) {
    mTmpConfiguration.unset();
    
    // 根据 rotation,计算出新的 config,并保存到参数 mTmpConfiguration 中
    // 另外也会返回新的 display info
    final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
    
    // 下面这些数据,也是根据 rotation 计算出来的
    final DisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
    final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
    final PrivacyIndicatorBounds indicatorBounds =
            calculatePrivacyIndicatorBoundsForRotation(rotation);
    final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
    
    // 根据 rotation 计算出的数据,重新生成一个 display frame
    final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
            cutout, roundedCorners, indicatorBounds, displayShape);
            
    // 由 ActivityRecord 来 apply fixed rotation 数据
    token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}


void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
    if (mFixedRotationLaunchingApp == null && r != null) {
        mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
        final boolean shouldDebounce = r == mFixedRotationTransitionListener.mAnimatingRecents
                || mTransitionController.isTransientLaunch(r);
        // 启动异步旋转
        startAsyncRotation(shouldDebounce);
    } else if (mFixedRotationLaunchingApp != null && r == null) {
        
    }
    
    // 保存 fixed rotation app
    mFixedRotationLaunchingApp = r;
}

根据 Activity 提供的 orientation 计算出新的 rotation,它与系统当前的 rotation 不一致时,就会启动 Fixed Rotation 机制

  1. 根据新的 rotation,计算相关数据,例如,config,display info,display frames,等等。然后 WindowToken 应用这些数据。
  2. 启动异步旋转。
  3. WindowToken#mFixedRotationLaunchingApp 保存正在执行 Fixed Rotation 的 ActivityRecord。

主要来看下 ActivityRecord 如何 apply fixed rotation 数据

// ActivityRecord.java

void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
        Configuration config) {
    // 通过父类方法来处理
    super.applyFixedRotationTransform(info, displayFrames, config);
    
    // apply fixed rotation transform 会更新 ActivityRecord 的配置
    // 因此,这里要确认 ActivityRecord 是否能正确处理配置改变
    // 但是,此时 ActivityRecord 还处于 INITIALING 状态,无法处理配置
    ensureActivityConfiguration();
}
// WindowToken.java

void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
        Configuration config) {
    if (mFixedRotationTransformState != null) {
        mFixedRotationTransformState.disassociate(this);
    }
    
    config = new Configuration(config);
    // 1.创建 FixedRotationTransformState,包装所有 fixed rotation 数据
    mFixedRotationTransformState = mTransitionController.isShellTransitionsEnabled()
            ? new FixedRotationTransformState(info, displayFrames, config)
            : new FixedRotationTransformStateLegacy(info, displayFrames, config,
                    mDisplayContent.getRotation());
    mFixedRotationTransformState.mAssociatedTokens.add(this);
    mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
    
    // 根据 FixedRotationTransformState 更新配置
    onFixedRotationStatePrepared();
}

private void onFixedRotationStatePrepared() {
    // Resolve the rotated configuration.
    onConfigurationChanged(getParent().getConfiguration());
    
    final ActivityRecord r = asActivityRecord();
    // 此时,ActivityRecord 还没有关联进程
    if (r != null && r.hasProcess()) {

    }
}

ActivityRecord apply fixed rotation

  1. 创建 FixedRotationTransformState ,保存 Fixed Rotation 数据,display info,display frames,config。
  2. mAssociatedTokens 保存与 Fixed Rotation 相关的 WindowToken。
  3. 利用 FixedRotationTransformState,重新更新 ActivityRecord 配置。

ActivityRecord 使用基类方法来更新配置,如下

// WindowContainer.java

public void onConfigurationChanged(Configuration newParentConfig) {
    // 1. update config
    super.onConfigurationChanged(newParentConfig);
    
    // 2. update surface position
    updateSurfacePositionNonOrganized();
    
    // ...
}

update config 是利用 FixedRotationTransformState 的 config 来更新 ActivityRecord 自己的配置。新的 config 造成了 surface position 的改变,因此需要 update surface position。

更新 ActivityRecord 配置

我在屏幕旋转系列文章中,已经分析过配置更新的流程。因此,这里只挑与 Fixed Rotation 相关的配置更新来讲解。

由于 Fixed Rotation config 的存在,ActivityRecord 的 resolved override config 会被它更新,如下

// WindowToken.java

void resolveOverrideConfiguration(Configuration newParentConfig) {
    super.resolveOverrideConfiguration(newParentConfig);
    if (isFixedRotationTransforming()) {
        // resolved override config 从 fixed rotration config 中更新
        getResolvedOverrideConfiguration().updateFrom(
                mFixedRotationTransformState.mRotatedOverrideConfiguration);
    }
}

resolved override config 更新后,会导致 full config,merged override config 也被更新,如下

// WindowContainer.java

public void onConfigurationChanged(Configuration newParentConfig) {
    mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
    
    // 解析 resolved override config
    resolveOverrideConfiguration(newParentConfig);
    
    // 更新 full config
    mFullConfiguration.setTo(newParentConfig);
    mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
    mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
    
    // 更新 merged override config
    onMergedOverrideConfigurationChanged();
    
    if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
            // 通知监听者 resolved override config change
            mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
                    mResolvedOverrideConfiguration);
        }
    }
    
    for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
        // 通知监听者,mergd override config change
        mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                mMergedOverrideConfiguration);
    }
    
    for (int i = getChildCount() - 1; i >= 0; --i) {
        // 把 ActivityRecord 的 full config,分发给 WindowState,让他们也更新配置
        dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
    }
}

WindowState 作为 ActivityRecord 的 child,它会根据 ActivityRecord 的 full config 来更新自己的配置。

// WindowState.java

public void onConfigurationChanged(Configuration newParentConfig) {
    // 保存当前的配置
    final Configuration selfConfiguration = super.getConfiguration();
    mTempConfiguration.setTo(selfConfiguration);
    
    // 利用基类更新配置
    super.onConfigurationChanged(newParentConfig);
    
    // 配置改变后,重置 mLastConfigReportedToClient 为 false,表示最新的配置还没有发送给 app
    final int diff = selfConfiguration.diff(mTempConfiguration);
    if (diff != 0) {
        mLastConfigReportedToClient = false;
    }

    // ...
}

对于 Activity 来说,它的 WindowState 配置更新非常简单

  1. WindowState 没有自己的 override config 逻辑,因此它的配置全都根据 ActivityRecord 进行更新。
  2. Activity 的窗口是全屏的,并且 WindowState 的 config 与 ActivityRecord config 无差异,因此 WindowState surface position 不需要更新。

更新 ActivityRecord surface position

// WindowContainer.java

final void updateSurfacePositionNonOrganized() {
    if (isOrganized()) return;

    // 虽然 WindowToken 有复写这个方法,但是仍然是通过基类实现
    // 注意,ActivityRecord 被收集到 Transition 中,因此这里使用的 sync transaction
    updateSurfacePosition(getSyncTransaction());
}
// WindowContianer.java

void updateSurfacePosition(Transaction t) {
    if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
        return;
    }

    if (isClosingWhenResizing()) {

    } else {
        // 计算当前 WC 在 parent 坐标系下的 x,y 坐标
        getRelativePosition(mTmpPos);
    }
    
    // 计算旋转角度差
    final int deltaRotation = getRelativeDisplayRotation();
    if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
        return;
    }

    //更新 surface 相对于 parent 的x,y 坐标
    t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
    // set first, since we don't want rotation included in this (for now).
    mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);

    if (mTransitionController.isShellTransitionsEnabled()
            && !mTransitionController.useShellTransitionsRotation()) {
        if (deltaRotation != Surface.ROTATION_0) {
            // 根据旋转角度差,旋转 surface
            // 虽然 WindowToken 有复写,但是还是通过基类实现
            updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
            
            getPendingTransaction().setFixedTransformHint(mSurfaceControl,
                    getWindowConfiguration().getDisplayRotation());
        } else if (deltaRotation != mLastDeltaRotation) {
           
        }
    }
    mLastDeltaRotation = deltaRotation;
}

由于 ActivityRecord 的 config 从 Fixed Rotation config 更新了,导致它与其 parent 产生了旋转角度差,因此,需要对 ActivityRecord surface 进行旋转。

由于需要把横屏的 ActivityRecord 窗口,显示在竖屏的屏幕上,因此,需要把 ActivityRecord surface 从横屏旋转到竖屏。旋转角度差的计算,如下

// WindowContainer.java

int getRelativeDisplayRotation() {
    final WindowContainer parent = getParent();
    if (parent == null) return Surface.ROTATION_0;
    // 获取 ActivityRecord 自己的 Rotation
    final int rotation = getWindowConfiguration().getDisplayRotation();
    
    // 获取 Task rotation
    final int parentRotation = parent.getWindowConfiguration().getDisplayRotation();
    
    // 计算从 ActivityRecord rotation 到 Task rotation 的旋转角度差
    return RotationUtils.deltaRotation(rotation, parentRotation);
}
// RotationUtils.java

// oldRotation 是 ActivityRecord rotation,值为 1
// newRotation 是 Task rotation,值为 0
public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) {
    int delta = newRotation - oldRotation;
    if (delta < 0) delta += 4;
    
    // 计算出旋转角度差为 4
    return delta;
}

计算 ActivityRecord 与其 parent 的旋转角度差后,现在可以依据它来对 ActivityRecord 进行旋转和偏移,让横屏的 Activity 横屏,完整显示在竖屏屏幕下,如下

// WindowContainer.java

protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation,
        @Nullable SurfaceControl positionLeash) {
    // 根据旋转角度差,给 ActivityRecord surface 设置 matrix,也就是旋转 surface
    RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation);
    
    mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y);
    final Rect parentBounds = getParent().getBounds();
    final boolean flipped = (deltaRotation % 2) != 0;
    // 根据 ActivityRecord 与 parent 的偏移坐标,以及旋转角度差
    // 对 ActivityRecord surface 进行偏移
    RotationUtils.rotatePoint(mTmpPos, deltaRotation,
            flipped ? parentBounds.height() : parentBounds.width(),
            flipped ? parentBounds.width() : parentBounds.height());
    t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl,
            mTmpPos.x, mTmpPos.y);
}
// RotationUtils.java

/**
 * Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the
 * bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left
 * corner appropriately.
 */
public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc,
        @Rotation int rotation) {
    // Note: the matrix values look inverted, but they aren't because our coordinate-space
    // is actually left-handed.
    // Note: setMatrix expects values in column-major order.
    switch (rotation) {
        case ROTATION_0:
            // ...
            break;
        case ROTATION_90:
            // ...
            break;
        case ROTATION_180:
            // ...
            break;
        case ROTATION_270:
            t.setMatrix(sc, 0.f, 1.f, -1.f, 0.f);
            break;
    }
}


// inOutPoint 是 ActivityRecord surface 相对于 Task 的偏移坐标,都是 0
public static void rotatePoint(Point inOutPoint, @Rotation int rotation,
        int parentW, int parentH) {
    int origX = inOutPoint.x;
    switch (rotation) {
        case ROTATION_0:
            // ...
            return;
        case ROTATION_90:
            // ...
            return;
        case ROTATION_180:
            // ...
            return;
        case ROTATION_270:
            inOutPoint.x = parentH - inOutPoint.y;
            inOutPoint.y = origX;
    }
}

横屏的 Activity 窗口,其实只需要在竖屏的坐标系下,按照顺时针方向旋转 90 度,然后向右偏移一个屏幕宽度即可。然而,这里计算出的旋转角度差是 270 度,为何?根据注释所说,坐标空间是左旋的(left-handed),因此需要执行逆时针旋转(CCW),所以需要旋转270度。

我用 winscope 查看了下 ActivityRecord 的 transform ,如下

image.png

很显然,这个 transform 做的是顺时针 90 度旋转,然后偏移了一个屏幕宽度。

那么, WMS 利用 270 度做的旋转和偏移,到底是基于什么原理呢?如果有读者知道,欢迎留言告知。