AOSP15 WMS/AMS系统开发 - WindowManagerService relayout调用流程详解

基于 AOSP 15 (Android 15) 源码分析

摘要: relayout是WMS中仅次于addWindow的高频操作,窗口每次measure/layout/draw前都会触发。本文从Framework开发视角,梳理relayout从客户端performTraversals到服务端WMS.relayoutWindow再到WindowLayout.computeFrames帧计算的全链路。


目录

  • [1. 整体架构概览](#1. 整体架构概览 "#1-%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84%E6%A6%82%E8%A7%88")
  • [2. relayout是什么?什么时候触发?](#2. relayout是什么?什么时候触发? "#2-relayout%E6%98%AF%E4%BB%80%E4%B9%88%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E8%A7%A6%E5%8F%91")
  • [3. 完整调用链路](#3. 完整调用链路 "#3-%E5%AE%8C%E6%95%B4%E8%B0%83%E7%94%A8%E9%93%BE%E8%B7%AF")
  • [4. 客户端侧: performTraversals → relayoutWindow](#4. 客户端侧: performTraversals → relayoutWindow "#4-%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BE%A7-performtraversals--relayoutwindow")
    • [4.1 relayout触发判断逻辑](#4.1 relayout触发判断逻辑 "#41-relayout%E8%A7%A6%E5%8F%91%E5%88%A4%E6%96%AD%E9%80%BB%E8%BE%91")
    • [4.2 windowShouldResize判定](#4.2 windowShouldResize判定 "#42-windowshouldresize%E5%88%A4%E5%AE%9A")
    • [4.3 ViewRootImpl.relayoutWindow()](#4.3 ViewRootImpl.relayoutWindow() "#43-viewrootimplrelayoutwindow")
    • [4.4 relayoutAsync优化机制](#4.4 relayoutAsync优化机制 "#44-relayoutasync%E4%BC%98%E5%8C%96%E6%9C%BA%E5%88%B6")
  • [5. Binder IPC: IWindowSession接口](#5. Binder IPC: IWindowSession接口 "#5-binder-ipc-iwindowsession%E6%8E%A5%E5%8F%A3")
  • [6. 服务端核心: WMS.relayoutWindow()](#6. 服务端核心: WMS.relayoutWindow() "#6-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%A0%B8%E5%BF%83-wmsrelayoutwindow")
    • [6.1 阶段一: 入口校验](#6.1 阶段一: 入口校验 "#61-%E9%98%B6%E6%AE%B5%E4%B8%80-%E5%85%A5%E5%8F%A3%E6%A0%A1%E9%AA%8C-line-2310-2325")
    • [6.2 阶段二: 窗口属性变更处理](#6.2 阶段二: 窗口属性变更处理 "#62-%E9%98%B6%E6%AE%B5%E4%BA%8C-%E7%AA%97%E5%8F%A3%E5%B1%9E%E6%80%A7%E5%8F%98%E6%9B%B4%E5%A4%84%E7%90%86-line-2338-2442")
    • [6.3 阶段三: shouldRelayout决策与Surface管理](#6.3 阶段三: shouldRelayout决策与Surface管理 "#63-%E9%98%B6%E6%AE%B5%E4%B8%89-shouldrelayout%E5%86%B3%E7%AD%96%E4%B8%8Esurface%E7%AE%A1%E7%90%86")
    • [6.4 阶段四: performSurfacePlacement → computeFrames 完整链路](#6.4 阶段四: performSurfacePlacement → computeFrames 完整链路 "#64-%E9%98%B6%E6%AE%B5%E5%9B%9B-performsurfaceplacement--computeframes-%E5%AE%8C%E6%95%B4%E9%93%BE%E8%B7%AF")
  • [7. 调试方法](#7. 调试方法 "#7-%E8%B0%83%E8%AF%95%E6%96%B9%E6%B3%95")
  • [8. Framework开发常见场景](#8. Framework开发常见场景 "#8-framework%E5%BC%80%E5%8F%91%E5%B8%B8%E8%A7%81%E5%9C%BA%E6%99%AF")
  • [9. 完整时序图](#9. 完整时序图 "#9-%E5%AE%8C%E6%95%B4%E6%97%B6%E5%BA%8F%E5%9B%BE")
  • [10. 源码文件索引](#10. 源码文件索引 "#10-%E6%BA%90%E7%A0%81%E6%96%87%E4%BB%B6%E7%B4%A2%E5%BC%95")

1. 整体架构概览

scss 复制代码
┌────────────────────────────────────────────────────────────────────────┐
│                            应用进程 (App Process)                       │
│                                                                        │
│  触发源 (三条路径):                                                      │
│  ├─ 首次窗口: ViewRootImpl.setView() → ViewRootImpl.requestLayout()     │
│  ├─ 运行时:   View.requestLayout() → mParent递归向上                     │
│  │              → ViewRootImpl.requestLayout()                         │
│  └─ 其他:     Window属性变更 / 可见性变更 / WMS回调                        │
│                              │                                         │
│                              ▼                                         │
│          ViewRootImpl.scheduleTraversals()                             │
│                              │                                         │
│                              ▼                                         │
│                    performTraversals()                                 │
│                      │                                                 │
│                      ├── relayoutWindow()  ── 与WMS通信获取帧/Surface    │
│                      ├── performMeasure()                              │
│                      ├── performLayout()                               │
│                      └── performDraw()     ── 将内容写入Surface的Buffer  │
│                              │                                         │
│              IWindowSession.relayout() (Binder IPC)                    │
└──────────────────────────────┼─────────────────────────────────────────┘
                               │
┌──────────────────────────────┼─────────────────────────────────────────┐
│                     system_server 进程                                  │
│                              │                                         │
│  ┌─────────────┐     ┌──────▼──────────┐     ┌────────────────────┐    │
│  │   Session   │────▶│      WMS        │────▶│    WindowState     │    │
│  │(per-process)│     │ .relayoutWindow │     │ .relayoutVisible   │    │
│  └─────────────┘     └──────┬──────────┘     │  Window()          │    │
│                             │                └────────────────────┘    │
│       ┌─────────────────────┼──────────────────────┐                   │
│       │                     │                      │                   │
│  ┌────▼──────────┐  ┌──────▼────────┐  ┌──────────▼─────────┐          │
│  │DisplayPolicy  │  │WindowState    │  │WindowSurfacePlacer │          │
│  │.adjustWindow  │  │Animator       │  │.performSurface     │          │
│  │ ParamsLw()    │  │.createSurface │  │ Placement()        │          │
│  │ 策略调整       │  │ Locked()      │  │ 强制布局             │          │
│  └───────────────┘  └───────────────┘  └────────────────────┘          │
└────────────────────────────────────────────────────────────────────────┘

核心类职责

类名 路径 Framework开发关注点
ViewRootImpl core/.../view/ViewRootImpl.java relayout触发入口,处理返回的帧/Surface
IWindowSession AIDL接口 Binder IPC通道,同步/异步两种模式
Session services/.../wm/Session.java per-process,透传到WMS
WindowManagerService services/.../wm/WindowManagerService.java relayout核心:属性更新、Surface管理、焦点分配
WindowState services/.../wm/WindowState.java 窗口状态机,可见性转换、帧填充
WindowStateAnimator services/.../wm/WindowStateAnimator.java Surface创建和动画控制
WindowSurfacePlacer services/.../wm/WindowSurfacePlacer.java 触发全局布局循环
DisplayPolicy services/.../wm/DisplayPolicy.java 窗口参数策略调整,调用computeFrames的入口
WindowLayout core/.../view/WindowLayout.java 帧计算引擎: displayFrame/parentFrame/frame

2. relayout是什么?什么时候触发?

relayout的核心作用

relayout是客户端与WMS之间的同步协商过程:

  • 客户端告诉WMS: 我想要的尺寸、属性、可见性
  • WMS告诉客户端: 你实际的帧位置、Surface、配置、Insets

每次 performTraversals() 在执行 measure/layout/draw 之前,都要先通过relayout向WMS申请资源。

触发时机

场景 触发条件 Framework开发中的典型case
首次布局 mFirst == true addView()后第一次traversal
尺寸变化 windowShouldResize 旋转屏幕、分屏调整、输入法弹出
可见性变更 viewVisibilityChanged setVisibility(VISIBLE/INVISIBLE/GONE)
属性变更 params != null 修改WindowManager.LayoutParams中任一字段
强制relayout mForceNextWindowRelayout 配置变更、兼容模式切换

3. 完整调用链路

scss 复制代码
[应用进程]
触发源 (三条路径):
  ├─ 路径1: ViewRootImpl.setView() → requestLayout()           ← 首次addView
  ├─ 路径2: View.requestLayout() → mParent递归向上
  │          → ViewRootImpl.requestLayout()                     ← 运行时View变更
  └─ 路径3: Window属性变更 / 可见性变更 / WMS回调(resizeClient等)
  │
  ▼
ViewRootImpl.scheduleTraversals()           ← Choreographer下一帧执行
  │
  ▼
ViewRootImpl.doTraversal()
  │
  ▼
ViewRootImpl.performTraversals()            ← View树绘制总入口
  │
  ├── 判断 relayout 条件
  │     (mFirst || windowShouldResize || visibilityChanged || params变更)
  │
  ▼
ViewRootImpl.relayoutWindow(params, viewVisibility, insetsPending)
  │
  ├── 计算请求尺寸 (measuredWidth * appScale)
  ├── 判断 relayoutAsync 快捷路径
  ├── IWindowSession.relayout() 或 relayoutAsync()
  │
  ═══════════════════ Binder IPC ═══════════════════
  │
[system_server]
Session.relayout()
  │
  ▼
WMS.relayoutWindow()                        ← 核心方法 (~420行)
  │
  ├── 1. 获取WindowState,序列号校验
  ├── 2. DisplayPolicy.adjustWindowParamsLw()  策略调整
  ├── 3. 属性差异计算 (flagChanges/attrChanges)
  ├── 4. shouldRelayout决策 → Surface创建/销毁
  ├── 5. performSurfacePlacement(true)        强制全局布局
  │       │
  │       ▼
  │     WindowSurfacePlacer.performSurfacePlacement()
  │       → RootWindowContainer.performSurfacePlacementNoTrace()
  │       → DisplayContent.applySurfaceChangesTransaction()
  │       → DisplayContent.performLayout()
  │       → DisplayPolicy.layoutWindowLw()
  │       → WindowLayout.computeFrames()     ★ 帧计算
  │
  ├── 6. 焦点/IME/方向/帧填充/Insets传递
  │
  ▼
返回 result + WindowRelayoutResult
  │
  ═══════════════════ Binder IPC ═══════════════════
  │
[应用进程]
ViewRootImpl.relayoutWindow() 处理返回
  │
  ▼
performMeasure → performLayout → performDraw

4. 客户端侧: performTraversals → relayoutWindow

4.1 relayout触发判断逻辑

java 复制代码
// ViewRootImpl.performTraversals() line 3731-3771
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
        || mForceNextWindowRelayout) {
    mForceNextWindowRelayout = false;

    // insetsPending: 窗口给WMS提供内部insets时,先标记pending
    insetsPending = computesInternalInsets
            && mWindowAttributes.providedInsets == null;

    // 核心: 调用relayout
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

    // 处理取消绘制
    cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
            == RELAYOUT_RES_CANCEL_AND_REDRAW;

    // 处理BLAST同步
    if (mSyncSeqId > mLastSyncSeqId) {
        mLastSyncSeqId = mSyncSeqId;
        reportNextDraw("relayout");
        mSyncBuffer = true;
    }
}

4.2 windowShouldResize判定

java 复制代码
// ViewRootImpl.performTraversals() (约 line 3620)
boolean windowShouldResize = layoutRequested && windowSizeMayChange
    && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
        || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                && frame.width() < desiredWindowWidth && frame.width() != mWidth)
        || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT
                && frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mPendingDragResizing;

Framework开发注意 : windowSizeMayChange由多因素决定------配置变更、drag resize、WRAP_CONTENT场景下desired尺寸变化等。调试时关注 mWidth != host.getMeasuredWidth() 这个条件。

4.3 ViewRootImpl.relayoutWindow()

java 复制代码
// ViewRootImpl.java line 9367
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
        boolean insetsPending) throws RemoteException {

    // 1. 获取配置和测量尺寸
    final WindowConfiguration winConfig = getCompatWindowConfiguration();
    final int measuredWidth = mMeasuredWidth;
    final int measuredHeight = mMeasuredHeight;

    // 2. relayoutAsync优化判断(详见4.4节)

    // 3. 计算请求尺寸(兼容性缩放)
    float appScale = mAttachInfo.mApplicationScale;
    final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
    final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);

    mRelayoutSeq++;

    if (relayoutAsync) {
        // 异步: 只发送属性,不等待返回
        mWindowSession.relayoutAsync(mWindow, params,
                requestedWidth, requestedHeight, viewVisibility,
                insetsPending ? RELAYOUT_INSETS_PENDING : 0,
                mRelayoutSeq, mLastSyncSeqId);
    } else {
        // 同步: 等待WMS返回完整结果
        relayoutResult = mWindowSession.relayout(mWindow, params,
                requestedWidth, requestedHeight, viewVisibility,
                insetsPending ? RELAYOUT_INSETS_PENDING : 0,
                mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
        mRelayoutRequested = true;

        // 4. 处理返回的帧信息(带序列号防乱序)
        onClientWindowFramesChanged(mTmpFrames);

        // 5. BLAST Sync SeqId
        if (mRelayoutResult.syncSeqId > 0) {
            mSyncSeqId = mRelayoutResult.syncSeqId;
        }

        // 6. 兼容性缩放
        mWinFrameInScreen.set(mTmpFrames.frame);
        mInvCompatScale = 1f / mTmpFrames.compatScale;

        // 7. Insets控制权
        handleInsetsControlChanged(mTempInsets, mTempControls);
    }

    // 8. Surface相关更新
    //    - Transform hint: Buffer旋转与设备方向的映射
    //    - Surface尺寸: WindowLayout.computeSurfaceSize()
    //    - BlastBufferQueue: updateBlastSurfaceIfNeeded()
    //    - ThreadedRenderer: setSurfaceControl()

    return relayoutResult;
}

onClientWindowFramesChanged --- 帧信息防乱序:

java 复制代码
// ViewRootImpl.java
private void onClientWindowFramesChanged(@NonNull ClientWindowFrames inOutFrames) {
    if (isIncomingSeqStale(mLastReportedFrames.seq, inOutFrames.seq)) {
        // 收到旧的帧信息,忽略,使用上次报告的值
        inOutFrames.setTo(mLastReportedFrames);
    } else {
        mLastReportedFrames.setTo(inOutFrames);
    }
}

Framework开发注意: 帧信息的seq机制保证异步更新时不会用旧数据覆盖新数据。如果调试时发现窗口帧"回退",检查seq是否有问题。

4.4 relayoutAsync优化机制

为减少Binder同步调用开销,relayoutAsync在仅属性变更、帧信息不变时走oneway异步路径:

java 复制代码
// ViewRootImpl.relayoutWindow() 中的判断
// 条件全部满足才考虑走异步:
// 1. 可见性未变化 (FLAG_WINDOW_VISIBILITY_CHANGED == 0)
// 2. 非StartingWindow
// 3. BLAST Sync已完成 (mSyncSeqId <= mLastSyncSeqId)
// 4. AM和WM的WindowConfiguration无差异
if (全部满足) {
    // 本地预计算帧
    mWindowLayout.computeFrames(attrs.forRotation(winConfig.getRotation()),
            state, displayCutoutSafe, winConfig.getBounds(), ...);

    // 关键: 位置和尺寸不能同时变化
    // 只位置变 或 只尺寸变 → 异步
    // 位置和尺寸都变 → 同步(需要BLAST sync)
    relayoutAsync = !positionChanged || !sizeChanged;
} else {
    relayoutAsync = false;
}
对比 relayout (同步) relayoutAsync (异步)
Binder 双向同步调用 oneway单向
返回值 WindowRelayoutResult完整返回 无返回值
场景 首次/配置变更/Surface变更/位置+尺寸同时变 仅属性变更
实现 Session.relayout() 内部 outRelayoutResult != null relayoutAsync() 传 null 给 relayout()

5. Binder IPC: IWindowSession接口

java 复制代码
// IWindowSession.aidl
// 同步relayout
int relayout(IWindow window, in WindowManager.LayoutParams attrs,
    int requestedWidth, int requestedHeight, int viewVisibility,
    int flags, int seq, int lastSyncSeqId,
    out @nullable WindowRelayoutResult outRelayoutResult);

// 异步relayout (oneway)
oneway void relayoutAsync(IWindow window, in WindowManager.LayoutParams attrs,
    int requestedWidth, int requestedHeight, int viewVisibility,
    int flags, int seq, int lastSyncSeqId);

参数详解

参数 类型 说明 Framework关注点
window IWindow 客户端窗口Binder标识 WMS通过它找到对应的WindowState
attrs LayoutParams 窗口属性,null表示不变 属性变更会触发策略调整
requestedWidth/Height int 客户端请求尺寸 经过appScale缩放后的值
viewVisibility int VISIBLE/INVISIBLE/GONE 决定shouldRelayout的关键输入
flags int RELAYOUT_INSETS_PENDING等 标记insets尚未就绪
seq int 序列号 去重: 旧seq被忽略直接return 0
lastSyncSeqId int 客户端已完成的BLAST同步ID 用于BLAST cancelAndRedraw判断

6. 服务端核心: WMS.relayoutWindow()

源码 : WindowManagerService.java

整体分为4个阶段,按执行顺序排列:

6.1 阶段一: 入口校验

java 复制代码
synchronized (mGlobalLock) {
    // ① 获取WindowState,找不到直接返回
    final WindowState win = windowForClientLocked(session, client, false);
    if (win == null) return 0;

    // ② 序列号去重: 旧请求直接忽略
    if (win.mRelayoutSeq < seq) {
        win.mRelayoutSeq = seq;
    } else if (win.mRelayoutSeq > seq) {
        return 0;
    }

    // ③ BLAST同步取消: WMS正在等sync buffer,但客户端要非sync绘制
    if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= lastSyncSeqId) {
        result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
    }

Framework注意: 客户端收到 RELAYOUT_RES_CANCEL_AND_REDRAW → cancelDraw = true,跳过本帧绘制,等下一帧重试。

6.2 阶段二: 窗口属性变更处理

当 attrs != null 时处理窗口属性变更:

java 复制代码
if (attrs != null) {
    // ① DisplayPolicy策略调整
    displayPolicy.adjustWindowParamsLw(win, attrs);

    // ② 安全过滤
    attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
    attrs.inputFeatures = sanitizeInputFeatures(attrs.inputFeatures, ...);

    // ③ 权限检查: 状态栏禁用标志需要STATUS_BAR权限
    int disableFlags = (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility)
            & DISABLE_MASK;
    if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) {
        disableFlags = 0;
    }
    win.mDisableFlags = disableFlags;

    // ④ 不可变属性校验: 窗口类型不可变更
    if (win.mAttrs.type != attrs.type) {
        throw new IllegalArgumentException(
                "Window type can not be changed after the window is added.");
    }

    // ⑤ 计算属性差异 (XOR)
    flagChanges = win.mAttrs.flags ^ attrs.flags;
    privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;
    attrChanges = win.mAttrs.copyFrom(attrs);

    // ⑥ 布局变更标记
    if ((attrChanges & LAYOUT_CHANGED) != 0
            || (attrChanges & SYSTEM_UI_VISIBILITY_CHANGED) != 0) {
        win.mLayoutNeeded = true;
    }

    // ⑦ 锁屏flag变更通知ActivityRecord
    if (win.mActivityRecord != null
            && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
                || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
        win.mActivityRecord.checkKeyguardFlagsChanged();
    }

    // ⑧ TrustedOverlay更新(影响Input系统的触摸信任链)
    if (wasTrustedOverlay != win.isWindowTrustedOverlay()) {
        win.updateTrustedOverlay();
    }

    // ⑨ DWPC检查: 虚拟Display的窗口flag不满足策略 → 重定位到默认Display
    if (displayContent.mDwpcHelper.hasController()
            && win.mActivityRecord != null
            && (!win.mRelayoutCalled || flagChanges != 0 || privateFlagChanges != 0)) {
        if (!displayContent.mDwpcHelper.keepActivityOnWindowFlagsChanged(...)) {
            mH.sendMessage(mH.obtainMessage(H.REPARENT_TASK_TO_DEFAULT_DISPLAY, ...));
            return 0;
        }
    }
}

Framework注意: attrChanges通过LayoutParams.copyFrom()计算,用XOR比较每个字段。调试"窗口行为突然变了"时先看flagChanges。

6.3 阶段三: shouldRelayout决策与Surface管理

shouldRelayout决策:

java 复制代码
win.mRelayoutCalled = true;
win.mInRelayout = true;
win.setViewVisibility(viewVisibility);

final boolean shouldRelayout = viewVisibility == View.VISIBLE
        && (win.mActivityRecord == null
            || win.mAttrs.type == TYPE_APPLICATION_STARTING
            || win.mActivityRecord.isClientVisible());

决策树与分支处理:

scss 复制代码
shouldRelayout?
  │
  ├── true (应显示)
  │     │
  │     ├── createSurfaceControl()                     ← 创建Surface
  │     │     └── WindowStateAnimator.createSurfaceLocked()
  │     │           → SurfaceControl.Builder.setBLASTLayer().build()
  │     │
  │     └── (performSurfacePlacement在两个分支之后统一执行)
  │
  └── false (不应显示)
        │
        └── tryStartExitingAnimation()                 ← 退出动画
              │
              ├── 有匹配动画 → mAnimatingExit = true (等动画结束再销毁)
              └── 无匹配 → win.destroySurface()        ← 直接销毁Surface

注意: performSurfacePlacement(true) 在shouldRelayout的两个分支之后统一执行(line 2541),不在分支内部。

Surface创建细节 --- WindowStateAnimator.createSurfaceLocked():

java 复制代码
// WindowStateAnimator.java (约 line 294)
SurfaceControl createSurfaceLocked() {
    if (mSurfaceControl != null) {
        return mSurfaceControl;  // 已存在直接返回
    }

    w.setHasSurface(false);
    resetDrawState();  // → DRAW_PENDING

    int flags = SurfaceControl.HIDDEN;

    mSurfaceControl = mWin.makeSurface()
            .setParent(mWin.mSurfaceControl)       // 父Surface (WindowContainer的SC)
            .setName(mTitle)                        // 窗口名 (调试可见)
            .setFormat(format)
            .setFlags(flags)
            .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
            .setMetadata(METADATA_OWNER_UID, mSession.mUid)
            .setMetadata(METADATA_OWNER_PID, mSession.mPid)
            .setCallsite("WindowSurfaceController")
            .setBLASTLayer()                        // BLAST BufferQueue模式
            .build();

    w.setHasSurface(true);
    w.mInputWindowHandle.forceChange();  // 通知Input系统
    mLastHidden = true;  // 新建的Surface默认隐藏
    return mSurfaceControl;
}

6.4 阶段四: performSurfacePlacement → computeFrames 完整链路

relayout在Surface创建/处理后,强制执行一次全局布局 (line 2541),这条链路最终调用 WindowLayout.computeFrames() 为每个窗口计算帧:

java 复制代码
// WMS.java
mWindowPlacerLocked.performSurfacePlacement(true /* force */);

总览调用链:

scss 复制代码
WMS.relayoutWindow() line 2541
  │
  ▼
① WindowSurfacePlacer.performSurfacePlacement(force)       // line 118
  ▼
② WindowSurfacePlacer.performSurfacePlacementLoop()        // line 133
  ▼
③ RootWindowContainer.performSurfacePlacement()            // line 755
  ▼
④ RootWindowContainer.performSurfacePlacementNoTrace()     // line 766
  ▼
⑤ RootWindowContainer.applySurfaceChangesTransaction()    // line 792
  ▼
⑥ DisplayContent.applySurfaceChangesTransaction()         // line 5036
  ▼
⑦ DisplayContent.performLayout()                           // line 5153
  ▼
⑧ DisplayContent.performLayoutNoTrace()                    // line 5162
  │
  ├── 第一轮: forAllWindows(mPerformLayout)         根窗口  // line 5182
  │     └── DisplayPolicy.layoutWindowLw(w, null)           // line 945
  │           └── WindowLayout.computeFrames()  ★
  │
  └── 第二轮: forAllWindows(mPerformLayoutAttached) 子窗口  // line 5187
        └── DisplayPolicy.layoutWindowLw(w, parent)         // line 984
              └── WindowLayout.computeFrames()  ★

① WindowSurfacePlacer.performSurfacePlacement(force)

java 复制代码
// WindowSurfacePlacer.java line 114-131
final void performSurfacePlacement(boolean force) {
    // force=true(来自relayout)时忽略延迟计数,直接执行
    if (mDeferDepth > 0 && !force) {
        mDeferredRequests++;
        return;
    }
    int loopCount = 6;  // 最多循环6次
    do {
        mTraversalScheduled = false;
        performSurfacePlacementLoop();          // ← 核心
        mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
        loopCount--;
    } while (mTraversalScheduled && loopCount > 0);
    // 循环结束: 壁纸action标记清除
    mService.mRoot.mWallpaperActionPending = false;
}

Framework开发关注:

  • loopCount = 6 --- 如果一次布局循环后又有新的布局请求(mTraversalScheduled重新被设置),会继续循环,最多6次
  • 超过6次日志: "Performed 6 layouts in a row. Skipping"
  • force=true来自relayout,确保不被deferLayout延迟

② WindowSurfacePlacer.performSurfacePlacementLoop()

java 复制代码
// WindowSurfacePlacer.java line 133-179
private void performSurfacePlacementLoop() {
    // 递归保护: 如果已在布局中,直接返回
    if (mInLayout) {
        Slog.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout.");
        return;
    }

    // 等待默认Display的配置就绪
    final DisplayContent defaultDisplay = mService.getDefaultDisplayContentLocked();
    if (defaultDisplay.mWaitingForConfig) {
        return;  // 配置未就绪,等下次
    }
    if (!mService.mDisplayReady) {
        return;  // Display未初始化
    }

    mInLayout = true;

    // 清理因内存不足而强制移除的窗口
    if (!mService.mForceRemoves.isEmpty()) {
        while (!mService.mForceRemoves.isEmpty()) {
            final WindowState ws = mService.mForceRemoves.remove(0);
            Slog.i(TAG, "Force removing: " + ws);
            ws.removeImmediately();
        }
        // 等待250ms让状态稳定
        synchronized (tmp) { tmp.wait(250); }
    }

    try {
        mService.mRoot.performSurfacePlacement();  // ← 进入RootWindowContainer
        mInLayout = false;
        // ...
    } catch ...
}

Framework开发关注:

  • mInLayout递归保护 --- 如果布局过程中又触发布局会被拒绝
  • mForceRemoves --- 内存压力时窗口被强制移除的场景

③ RootWindowContainer.performSurfacePlacement()

java 复制代码
// RootWindowContainer.java
void performSurfacePlacement() {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
    try {
        performSurfacePlacementNoTrace();
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
}

简单包装,加systrace tag,perfetto中可见 performSurfacePlacement。


④ RootWindowContainer.performSurfacePlacementNoTrace()

java 复制代码
// RootWindowContainer.java
void performSurfacePlacementNoTrace() {
    // ① 焦点预分配: 如果之前有焦点变更标记
    if (mWmService.mFocusMayChange) {
        mWmService.mFocusMayChange = false;
        mWmService.updateFocusedWindowLocked(
                UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
    }

    // ② 清理临时状态
    mDisplayBrightnessOverrides.clear();
    mUserActivityTimeout = -1;

    // ③ 事务序列号递增 --- 每次布局唯一标识
    mWmService.mTransactionSequence++;

    // ④ 核心: 应用Surface变更事务
    try {
        applySurfaceChangesTransaction();           // ← 进入⑤
    } catch (RuntimeException e) {
        Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
    }

    // ⑤ 后续处理 (布局之后)
    handleResizingWindows();                        // 通知客户端窗口resize
    clearFrameChangingWindows();
    mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
    mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
    mWmService.mSyncEngine.onSurfacePlacement();    // BLAST同步引擎
    checkAppTransitionReady(surfacePlacer);         // App转场动画就绪检查
    // ...
}

Framework开发关注:

  • mTransactionSequence++ --- 调试时可通过dumpsys看到当前transaction序列号
  • applySurfaceChangesTransaction() 是帧计算的真正入口
  • 后续的 handleResizingWindows() 会把计算好的帧通过Binder返回给客户端

⑤ RootWindowContainer.applySurfaceChangesTransaction()

java 复制代码
// RootWindowContainer.java(在performSurfacePlacementNoTrace中被调用)
applySurfaceChangesTransaction();

// 实际实现: 遍历所有DisplayContent
// RootWindowContainer.java
void applySurfaceChangesTransaction() {
    // 遍历每个Display
    for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
        final DisplayContent dc = mChildren.get(displayNdx);
        dc.applySurfaceChangesTransaction();        // ← 进入⑥
    }
}

多Display架构: 每个DisplayContent独立执行自己的布局。


⑥ DisplayContent.applySurfaceChangesTransaction()

java 复制代码
// DisplayContent.java
void applySurfaceChangesTransaction() {
    final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
    beginHoldScreenUpdate();
    mTmpUpdateAllDrawn.clear();

    // ① 壁纸窗口调整
    if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
        mWallpaperController.adjustWallpaperWindows();
    }

    // ② 配置变更 → 重新计算方向
    if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
        if (updateOrientation()) {    // 屏幕旋转
            setLayoutNeeded();        // 标记需要重新布局
            sendNewConfiguration();
        }
    }

    // ③ 布局重做标记
    if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
        setLayoutNeeded();            // 标记需要重新布局
    }

    // ④ ★ 执行布局 --- 核心入口
    performLayout(true /* initial */, false /* updateInputWindows */);  // ← 进入⑦
    pendingLayoutChanges = 0;

    // ⑤ 布局后策略
    mDisplayPolicy.beginPostLayoutPolicyLw();
    forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
    mDisplayPolicy.finishPostLayoutPolicyLw();

    // ⑥ Insets分发 (布局后)
    mInsetsStateController.onPostLayout();

    // ⑦ Surface显示/隐藏
    forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);

    // ⑧ IME显示检查
    mInsetsStateController.getImeSourceProvider().checkAndStartShowImePostLayout();

    // ⑨ Display属性更新 (刷新率/HDR等)
    mWmService.mDisplayManagerInternal.setDisplayProperties(...);

    // ⑩ 壁纸可见性通知
    updateRecording();
    // ...
}

Framework开发关注:

  • pendingLayoutChanges 的三个 FINISH_LAYOUT_REDO_* 标志控制是否重新布局
  • setLayoutNeeded() 是触发 performLayout 的前提条件,没有这个标记布局会跳过
  • 布局顺序: performLayout → postLayoutPolicy → insets分发 → Surface变更

⑦ DisplayContent.performLayout()

java 复制代码
// DisplayContent.java
void performLayout(boolean initial, boolean updateInputWindows) {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");
    try {
        performLayoutNoTrace(initial, updateInputWindows);
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
}

⑧ DisplayContent.performLayoutNoTrace() --- 两轮布局

java 复制代码
// DisplayContent.java line 5162-5194
private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
    // ★ 前置检查: 没有layoutNeeded标记直接返回
    if (!isLayoutNeeded()) {
        return;
    }
    clearLayoutNeeded();

    if (DEBUG_LAYOUT) {
        Slog.v(TAG, "-------------------------------------");
        Slog.v(TAG, "performLayout: dw=" + mDisplayInfo.logicalWidth
                + " dh=" + mDisplayInfo.logicalHeight);
    }

    int seq = mLayoutSeq + 1;
    if (seq < 0) seq = 0;
    mLayoutSeq = seq;               // 布局序列号递增
    mTmpInitial = initial;

    // ★★★ 第一轮: 根窗口布局 (非attached窗口) ★★★
    forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

    // ★★★ 第二轮: 子窗口布局 (attached窗口) ★★★
    // 子窗口依赖父窗口的帧,所以必须先完成根窗口布局
    forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);

    // 通知InputDispatcher窗口帧可能变化
    mInputMonitor.setUpdateInputWindowsNeededLw();
    if (updateInputWindows) {
        mInputMonitor.updateInputWindowsLw(false /*force*/);
    }
}

第一轮回调 mPerformLayout --- 根窗口 (line 912-965):

java 复制代码
// DisplayContent.java line 912
private final Consumer<WindowState> mPerformLayout = w -> {
    // 跳过子窗口 (子窗口在第二轮处理)
    if (w.mLayoutAttached) {
        return;
    }

    // 跳过条件: 窗口已gone且不需要重新布局
    final boolean gone = w.isGoneForLayout();
    if (!gone || !w.mHaveFrame || w.mLayoutNeeded) {
        if (mTmpInitial) {
            w.resetContentChanged();
        }
        w.mSurfacePlacementNeeded = true;
        w.mLayoutNeeded = false;

        // ★ 调用DisplayPolicy执行布局
        getDisplayPolicy().layoutWindowLw(w, null /* attached */, mDisplayFrames);
        w.mLayoutSeq = mLayoutSeq;

        // 首次布局: 初始化lastFrames避免不必要的resize
        final boolean firstLayout = !w.isLaidOut();
        if (firstLayout) {
            if (!w.getFrame().isEmpty()) {
                w.updateLastFrames();
                mWmService.mFrameChangingWindows.remove(w);
            }
            w.updateSurfacePositionNonOrganized();
            w.onResizeHandled();
        }
    }
};

第二轮回调 mPerformLayoutAttached --- 子窗口

java 复制代码
// DisplayContent.java line 967
private final Consumer<WindowState> mPerformLayoutAttached = w -> {
    // 跳过非子窗口
    if (!w.mLayoutAttached) {
        return;
    }

    // 条件: 可见 且 已调用relayout,或者没有帧,或者需要重新布局
    if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
            || w.mLayoutNeeded) {
        if (mTmpInitial) {
            w.resetContentChanged();
        }
        w.mSurfacePlacementNeeded = true;
        w.mLayoutNeeded = false;

        // ★ 和第一轮相同,但传入父窗口引用
        getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
        w.mLayoutSeq = mLayoutSeq;
    }
};

Framework开发关注:

  • 两轮布局 : 根窗口先布局,子窗口后布局。子窗口的 attachedFrame 来自父窗口的 getFrame(),所以必须等第一轮完成
  • isLayoutNeeded() 是关键门槛 --- setLayoutNeeded() 在多处被调用: relayout属性变更、配置变更、方向变更等
  • mLayoutSeq 用于判断窗口是否在最近一次布局中被处理过 (w.mLayoutSeq == mLayoutSeq)
  • isGoneForLayout() 包含多个条件: mViewVisibility == GONE || !mRelayoutCalled || !mToken.isVisible() 等

⑨ DisplayPolicy.layoutWindowLw --- computeFrames的直接调用者

java 复制代码
// DisplayPolicy.java line 1385-1408
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
    // ① 跳过标记
    if (win.skipLayout()) {
        return;
    }

    // ② 获取窗口所在Display的帧信息
    displayFrames = win.getDisplayFrames(displayFrames);

    // ③ 根据屏幕旋转生成对应方向的LayoutParams
    //    如果当前rotation和窗口不匹配,会生成旋转后的副本
    final WindowManager.LayoutParams attrs = win.mAttrs.forRotation(displayFrames.mRotation);
    sTmpClientFrames.attachedFrame = attached != null ? attached.getFrame() : null;

    // ④ trustedSize: attrs因旋转而变化时,不能用客户端请求的尺寸
    //    因为客户端还没来得及为新rotation发送尺寸
    final boolean trustedSize = attrs == win.mAttrs;
    final int requestedWidth = trustedSize ? win.mRequestedWidth : UNSPECIFIED_LENGTH;
    final int requestedHeight = trustedSize ? win.mRequestedHeight : UNSPECIFIED_LENGTH;

    // ⑤ ★ 核心: 调用computeFrames计算帧
    mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
            win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
            win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames);

    // ⑥ 应用计算结果到WindowState
    win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}

Framework开发关注:

  • attrs.forRotation(rotation) --- 屏幕旋转时,窗口的LayoutParams可能因方向不同而变化。返回自身引用表示无需旋转,返回新对象表示需要旋转
  • trustedSize --- 如果发生了旋转(attrs对象变了),requestedWidth/Height被设为UNSPECIFIED_LENGTH(-1),computeFrames会改用LayoutParams中的值
  • attached != null 时 attachedFrame 传入父窗口的frame,供子窗口计算parentFrame
  • win.setFrames() 将计算好的displayFrame/parentFrame/frame写入WindowState

⑩ WindowLayout.computeFrames --- 帧计算核心

java 复制代码
// WindowLayout.java line 65
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
        Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
        int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
        float compatScale, ClientWindowFrames frames) {

    final int type = attrs.type;
    final int fl = attrs.flags;
    final int pfl = attrs.privateFlags;
    final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
    final Rect attachedWindowFrame = frames.attachedFrame;
    final Rect outDisplayFrame = frames.displayFrame;
    final Rect outParentFrame = frames.parentFrame;
    final Rect outFrame = frames.frame;

    // ━━━━━━ Step 1: 计算 displayFrame (从windowBounds中扣除Insets) ━━━━━━
    final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
            attrs.isFitInsetsIgnoringVisibility());
    final @WindowInsets.Side.InsetsSide int sides = attrs.getFitInsetsSides();
    final int left   = (sides & WindowInsets.Side.LEFT)   != 0 ? insets.left   : 0;
    final int top    = (sides & WindowInsets.Side.TOP)    != 0 ? insets.top    : 0;
    final int right  = (sides & WindowInsets.Side.RIGHT)  != 0 ? insets.right  : 0;
    final int bottom = (sides & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
    outDisplayFrame.set(windowBounds.left + left, windowBounds.top + top,
            windowBounds.right - right, windowBounds.bottom - bottom);

    // ━━━━━━ Step 2: 计算 parentFrame ━━━━━━
    if (attachedWindowFrame == null) {
        // 非子窗口: parentFrame = displayFrame (可能被IME裁剪)
        outParentFrame.set(outDisplayFrame);
        if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
            final InsetsSource source = state.peekSource(ID_IME);
            if (source != null) {
                outParentFrame.inset(source.calculateInsets(outParentFrame, false));
            }
        }
    } else {
        // 子窗口: layoutInScreen=false时取attachedWindowFrame,否则取displayFrame
        outParentFrame.set(!layoutInScreen ? attachedWindowFrame : outDisplayFrame);
    }

    // ━━━━━━ Step 3: 刘海屏裁剪 ━━━━━━
    final int cutoutMode = attrs.layoutInDisplayCutoutMode;
    final DisplayCutout cutout = state.getDisplayCutout();
    if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
        // SHORT_EDGES: 短边方向放开限制
        // DEFAULT: 遵守displayCutoutSafe
        // 子窗口和floating IN_SCREEN窗口不需要额外裁剪
        if (!attachedInParent && !floatingInScreenWindow) {
            intersectOrClamp(outParentFrame, displayCutoutSafeExceptMaybeBars);
        }
        intersectOrClamp(outDisplayFrame, displayCutoutSafeExceptMaybeBars);
    }

    // FLAG_LAYOUT_NO_LIMITS: 全屏窗口可以超出display边界
    if (noLimits && type != TYPE_SYSTEM_ERROR && !inMultiWindowMode) {
        outDisplayFrame.set(MIN_X, MIN_Y, MAX_X, MAX_Y);
    }

    // ━━━━━━ Step 4: 计算窗口尺寸 w/h ━━━━━━
    final int pw = outParentFrame.width();
    final int ph = outParentFrame.height();
    int rw = requestedWidth;
    int rh = requestedHeight;

    // 未测量过或extendedByCutout时,用LayoutParams中的值
    if (rw == UNSPECIFIED_LENGTH || extendedByCutout) {
        rw = attrs.width >= 0 ? attrs.width : pw;
    }
    if (rh == UNSPECIFIED_LENGTH || extendedByCutout) {
        rh = attrs.height >= 0 ? attrs.height : ph;
    }

    if ((attrs.flags & FLAG_SCALED) != 0) {
        // SCALED模式: 用LayoutParams指定值
        w = attrs.width < 0 ? pw : (hasCompatScale ? (int)(attrs.width * compatScale + .5f) : attrs.width);
        h = attrs.height < 0 ? ph : (hasCompatScale ? (int)(attrs.height * compatScale + .5f) : attrs.height);
    } else {
        // 正常模式: MATCH_PARENT→parentFrame尺寸,否则→requested
        w = attrs.width == MATCH_PARENT ? pw : (hasCompatScale ? (int)(rw * compatScale + .5f) : rw);
        h = attrs.height == MATCH_PARENT ? ph : (hasCompatScale ? (int)(rh * compatScale + .5f) : rh);
    }

    // 多窗口模式: 尺寸不超过parentFrame
    if (inMultiWindowMode && !hasLayoutChildFlag) {
        w = Math.min(w, pw);
        h = Math.min(h, ph);
    }

    // ━━━━━━ Step 5: Gravity应用 → 最终frame ━━━━━━
    // 根据gravity将(w,h)放置到parentFrame中
    Gravity.apply(attrs.gravity, w, h, outParentFrame,
            (int)(attrs.x + attrs.horizontalMargin * pw),
            (int)(attrs.y + attrs.verticalMargin * ph), outFrame);

    // 确保窗口不超出displayFrame
    if (fitToDisplay) {
        Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
    }
}

Framework开发关注 --- 三个核心帧的含义:

帧 含义 计算方式
displayFrame 扣除Insets(状态栏/导航栏)后的可用区域 windowBounds - fitInsets
parentFrame 窗口尺寸参考基准(子窗口=父窗口frame) 根窗口=displayFrame,子窗口=attachedFrame
frame 最终窗口位置和尺寸 Gravity.apply(parentFrame, w, h)
  • 布局循环最多6次 。超过后日志 "Performed 6 layouts in a row. Skipping"
  • 两轮布局: 根窗口先,子窗口后。子窗口帧依赖父窗口
  • attrs.forRotation(rotation) --- 旋转时生成对应方向LayoutParams
  • fitToDisplay --- 全屏窗口和Popup/Dialog会被displayFrame约束,SurfaceView不会
  • compatScale --- 兼容模式下帧尺寸会被缩放

7. 调试方法

ProtoLog开关

bash 复制代码
adb shell dumpsys activity logging enable WM_DEBUG_FOCUS
adb shell dumpsys activity logging enable WM_DEBUG_ANIM
adb shell dumpsys activity logging enable WM_DEBUG_SCREEN_ON
adb shell dumpsys activity logging enable WM_SHOW_TRANSACTIONS
adb shell dumpsys activity logging enable WM_SHOW_SURFACE_ALLOC
adb shell dumpsys activity logging enable WM_DEBUG_LAYOUT
adb shell dumpsys activity logging enable WM_DEBUG_VISIBILITY
adb shell dumpsys activity logging enable WM_DEBUG_SYNC_ENGINE

dumpsys查看

bash 复制代码
adb dumpsys window windows | grep -A 30 "Window #"
adb dumpsys window frames
adb dumpsys window windows | grep "mCurrentFocus"
adb dumpsys window windows | grep "mInputMethodTarget"
adb dumpsys window insets
adb dumpsys SurfaceFlinger --list

systrace/perfetto

bash 复制代码
atrace -b 32768 wm view am gfx input sched freq --async_time > trace.bin
# perfetto.dev中查看: relayoutWindow / createSurfaceControl / performSurfacePlacement / wmUpdateFocus

8. Framework开发常见场景

场景1: 修改窗口属性后窗口行为没变

  1. adjustWindowParamsLw() 是否过滤了flag?
  2. sanitizeFlagSlippery() / sanitizeInputFeatures() 是否覆盖?
  3. win.mDisableFlags 是否被权限检查清零?
  4. attrChanges是否包含期望的bit?

场景2: 窗口显示黑屏/闪烁

  1. createSurfaceLocked()返回值是否为null?
  2. RELAYOUT_RES_FIRST_TIME设置但未调用finishDrawing()?
  3. BLAST同步卡住?检查mSyncSeqId和mPrepareSyncSeqId
  4. RELAYOUT_RES_CANCEL_AND_REDRAW持续返回?

场景3: 窗口resize后内容未更新

  1. performSurfacePlacement循环超过6次?
  2. BLAST sync: 客户端是否正确处理syncSeqId?
  3. fillClientWindowFramesAndConfiguration返回的帧是否正确?
  4. onClientWindowFramesChanged的seq是否乱序?

场景4: 虚拟Display上窗口被踢回默认Display

  1. DisplayWindowPolicyController.keepActivityOnWindowFlagsChanged()返回值
  2. 日志: "Activity XXX window flag changed, can't remain on display N"

场景5: 输入法弹出/收起时窗口resize异常

  1. SOFT_INPUT_ADJUST_RESIZE → mLayoutNeeded = true
  2. 检查InsetsState中IME source的frame变化
  3. computeImeTarget()是否正确识别IME目标

9. 完整时序图

css 复制代码
App Process                         system_server
──────────                          ─────────────
    │
    │ [触发源] 三条路径:
    │ ① setView() → requestLayout()  (首次addView)
    │ ② View.requestLayout() → mParent递归 → ViewRootImpl.requestLayout()
    │ ③ Window属性变更 / 可见性变更 / WMS回调
    ▼
ViewRootImpl.scheduleTraversals()
    │
    ▼
performTraversals()
    │ 判断 relayout 条件
    ▼
relayoutWindow()
    │ 计算尺寸, 判断async
    │
    │  IWindowSession.relayout()
    │──────────────────────────────▶│
    │                               │
    │                        Session.relayout()
    │                               │
    │                               ▼
    │                        WMS.relayoutWindow()
    │                               │
    │                        [阶段1] 序列号校验 + cancelAndRedraw
    │                               │
    │                        [阶段2] attrs? → adjustWindowParamsLw
    │                        → sanitizeFlags → attrChanges
    │                        → checkKeyguard → DWPC
    │                               │
    │                        [阶段3] shouldRelayout?
    │                  ┌──Yes────────┤────No──────────┐
    │           createSurfaceControl │      tryStartExiting
    │           createSurfaceLocked()│      Animation()
    │           → BLAST Surface      │      或 destroySurface
    │                  │              │                │
    │                  ▼◀─────────────┘                │
    │                  │                               │
    │                        [阶段4] performSurfacePlacement(true)
    │                               │
    │                        WindowSurfacePlacer → RootWindowContainer
    │                        → DisplayContent.applySurfaceChangesTransaction
    │                        → DisplayContent.performLayout()
    │                        → DisplayPolicy.layoutWindowLw()
    │                        → WindowLayout.computeFrames()  ★
    │                               │
    │                        后续: 焦点/IME/方向/帧填充/Insets
    │                               │
    │  result + WindowRelayoutResult                   │
    │◀────────────────────────────────────────────────│
    │
    ▼
处理返回结果 → performMeasure → performLayout → performDraw
    │
    │  finishDrawing(buffer, syncSeqId)
    │──────────────────────────────▶│  WMS完成BLAST同步

10. 源码文件索引

文件 路径 关键方法/行号
ViewRootImpl.java core/java/android/view/ performTraversals() ~3454, relayoutWindow() ~9367
IWindowSession.aidl core/java/android/view/ relayout() ~94, relayoutAsync() ~110
Session.java services/.../wm/ relayout() ~278, relayoutAsync() ~289
WindowManagerService.java services/.../wm/ relayoutWindow() ~2280, createSurfaceControl() ~2760, tryStartExitingAnimation() ~2712
WindowState.java services/.../wm/ relayoutVisibleWindow() ~4979, prepareWindowToDisplayDuringRelayout() ~2851, fillClientWindowFramesAndConfiguration() ~3694, cancelAndRedraw() ~6175
WindowStateAnimator.java services/.../wm/ createSurfaceLocked() ~294, applyEnterAnimationLocked() ~548
WindowSurfacePlacer.java services/.../wm/ performSurfacePlacement() ~114, performSurfacePlacementLoop() ~133
RootWindowContainer.java services/.../wm/ performSurfacePlacementNoTrace() ~764, applySurfaceChangesTransaction() ~792
DisplayPolicy.java services/.../wm/ adjustWindowParamsLw() ~950, layoutWindowLw() ~1385
DisplayContent.java services/.../wm/ applySurfaceChangesTransaction() ~5036, performLayout() ~5153, performLayoutNoTrace() ~5162, mPerformLayout ~912
WindowLayout.java core/java/android/view/ computeFrames() ~65, computeSurfaceSize() ~314
WindowRelayoutResult.java core/java/android/view/ 完整类 ~1-140
ClientWindowFrames.java core/java/android/window/ 完整类 ~1-156
WindowManagerGlobal.java core/java/android/view/ RELAYOUT_RES_* 常量 ~80-113
InputMonitor.java services/.../wm/ updateInputWindowsLw() ~328
UnknownAppVisibilityController.java services/.../wm/ notifyRelayouted() ~141
相关推荐
三少爷的鞋3 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)6 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码8 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程9 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓13 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer