WMS核心机制:窗口管理与层级控制深度解析

引言

在前面的文章中,我们深入了解了PMS如何管理应用的安装与权限。但应用安装后,它的界面如何显示在屏幕上?多个应用窗口如何叠加?窗口的层级关系如何决定?动画效果如何实现?

想象你同时打开了微信、浏览器和视频播放器:

  • 🎬 视频播放器以画中画模式浮在最上层
  • 💬 微信的悬浮球显示在所有应用之上
  • 🌐 浏览器窗口被部分遮挡,但仍然可见
  • ⌨️ 你点击微信,输入法弹出,遮挡了部分内容
  • 🎨 切换应用时,出现精美的过渡动画

这一切的背后,都是**WindowManagerService(WMS)**的精妙设计。

css 复制代码
用户点击应用图标
    ↓
Activity创建并请求显示
    ↓
WMS创建窗口并分配Z-order
    ↓
窗口动画播放
    ↓
SurfaceFlinger合成显示
    ↓
用户看到应用界面!

如果把Android系统比作一个舞台:

  • WMS是"舞台导演",管理所有演员(窗口)的位置和出场顺序
  • SurfaceFlinger是"灯光师",负责最终的视觉呈现
  • InputManagerService是"传声筒",将观众(用户)的反应传递给演员

📖 系列前置阅读:建议先阅读第16-17篇(PMS核心与进阶),理解应用的安装与启动机制。

本篇将揭开WMS的神秘面纱,探索Android窗口管理的核心技术。


WMS是什么?

WMS的核心职责

WindowManagerService是Android系统中负责窗口管理的核心服务,在SystemServer中随第二批"核心服务(Core Services)"一起启动。它的职责可以概括为"窗口全生命周期管理":

阶段 核心功能 关键操作
创建时 窗口注册与分配 分配WindowToken、确定Z-order层级
显示时 布局计算与渲染 计算窗口位置/大小、触发动画
交互时 输入事件分发 确定焦点窗口、分发触摸事件
切换时 动画与过渡 播放切换动画、更新层级关系
销毁时 资源清理 释放WindowToken、移除窗口

WMS管理的窗口类型

Android中存在多种类型的窗口,WMS需要统一管理:

java 复制代码
// frameworks/base/core/java/android/view/WindowManager.java
// Android 15: 窗口类型定义

public interface WindowManager extends ViewManager {
    // 应用窗口 (Z-order: 1-99)
    int TYPE_BASE_APPLICATION = 1;         // 普通Activity窗口
    int TYPE_APPLICATION = 2;              // 应用窗口
    int TYPE_APPLICATION_STARTING = 3;     // 启动窗口(闪屏)

    // 子窗口 (Z-order: 1000-1999)
    int TYPE_APPLICATION_PANEL = 1000;     // 面板窗口(如菜单)
    int TYPE_APPLICATION_MEDIA = 1001;     // 媒体窗口(如视频)
    int TYPE_APPLICATION_SUB_PANEL = 1002; // 子面板窗口

    // 系统窗口 (Z-order: 2000-2999)
    int TYPE_STATUS_BAR = 2000;            // 状态栏
    int TYPE_SEARCH_BAR = 2001;            // 搜索栏
    int TYPE_PHONE = 2002;                 // 电话窗口
    int TYPE_SYSTEM_ALERT = 2003;          // 系统警告窗口
    int TYPE_KEYGUARD = 2004;              // 锁屏窗口
    int TYPE_TOAST = 2005;                 // Toast提示
    int TYPE_SYSTEM_OVERLAY = 2006;        // 系统覆盖层
    int TYPE_INPUT_METHOD = 2011;          // 输入法窗口
    int TYPE_NAVIGATION_BAR = 2019;        // 导航栏
    int TYPE_APPLICATION_OVERLAY = 2038;   // 应用悬浮窗(需权限)
}

窗口类型的层级规则

  • 应用窗口 (1-99):普通Activity,层级最低
  • 子窗口 (1000-1999):依附于父窗口,如Dialog、PopupWindow
  • 系统窗口 (2000-2999):系统级窗口,层级最高

Z-order示例

scss 复制代码
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 导航栏 (TYPE_NAVIGATION_BAR, 2019)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 输入法 (TYPE_INPUT_METHOD, 2011)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Toast (TYPE_TOAST, 2005)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Dialog (TYPE_APPLICATION_PANEL, 1000)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 Activity窗口 (TYPE_APPLICATION, 2)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

WMS的核心数据结构

WMS维护着Android系统中所有窗口的状态:

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
// Android 15: WMS核心数据结构

public class WindowManagerService extends IWindowManager.Stub {

    // 所有窗口的映射表 (IBinder → WindowState)
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

    // 窗口Token映射表 (IBinder → WindowToken)
    final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();

    // 显示内容管理 (每个物理/虚拟显示一个DisplayContent)
    // DisplayId → DisplayContent
    final SparseArray<DisplayContent> mDisplayContents = new SparseArray<>();

    // 根窗口容器 (整个窗口树的根节点)
    RootWindowContainer mRoot;

    // 窗口动画管理器
    final WindowAnimator mAnimator;

    // 输入管理器 (与InputManagerService交互)
    InputManagerService mInputManager;

    // 策略管理器 (窗口策略,如状态栏/导航栏行为)
    WindowManagerPolicy mPolicy;
}

关键数据结构说明

  1. WindowState:代表一个窗口的完整状态

    • 包含窗口位置、大小、可见性、Z-order等信息
    • 对应应用侧的Window对象
  2. WindowToken:窗口令牌,标识窗口的归属

    • 同一个Activity的所有窗口共享一个WindowToken
    • 用于权限校验和生命周期管理
  3. DisplayContent:显示内容管理

    • 每个物理/虚拟显示屏对应一个DisplayContent
    • 管理该显示屏上的所有窗口
  4. RootWindowContainer:根容器

    • 整个窗口树的根节点
    • 包含所有DisplayContent

WMS架构深度解析

WMS的层次结构

图:WMS架构展示了从顶层WindowManagerService到底层WindowState的完整层次结构,以及与SurfaceFlinger、InputManagerService的交互关系

架构层次说明

  1. Layer 1 - 核心服务层

    • WindowManagerService:整个窗口系统的核心控制器
    • 单例模式,运行在SystemServer进程
  2. Layer 2 - 根容器层

    • RootWindowContainer:管理所有显示屏的根容器
    • 负责跨显示屏的窗口管理和任务协调
  3. Layer 3 - 显示内容层

    • DisplayContent:每个物理/虚拟显示屏一个实例
    • 主屏幕(Display 1):手机/平板的主显示
    • 副屏幕(Display 2):外接显示器、投影等
  4. Layer 4 - 窗口容器层次 DisplayContent └── DisplayArea (显示区域) └── Task (任务栈) └── ActivityRecord (Activity记录) └── WindowToken (窗口令牌) └── WindowState (窗口状态)

辅助组件

  • WindowAnimator:管理所有窗口的动画效果
  • InputManagerService:处理输入事件分发到正确的窗口
  • WindowManagerPolicy:定义窗口策略(如状态栏/导航栏行为)
  • SurfaceFlinger:底层渲染服务(通过Binder IPC通信)

窗口创建与显示流程

当一个Activity启动时,WMS如何创建并显示窗口?让我们追踪完整的流程。

Activity启动触发窗口创建

java 复制代码
// frameworks/base/core/java/android/app/Activity.java
// Android 15: Activity创建窗口

public class Activity extends ContextThemeWrapper {

    /**
     * Activity.attach()中创建PhoneWindow
     */
    final void attach(Context context, ActivityThread aThread, ...) {
        // 1. 创建PhoneWindow对象(应用侧的Window实现)
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this); // Activity作为Window的回调

        // 2. 设置WindowManager
        mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, // ActivityRecord的token
            mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0
        );

        // 3. 获取WindowManager实例
        mWindowManager = mWindow.getWindowManager();
    }

    /**
     * setContentView()触发窗口添加到WMS
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);

        // 首次setContentView时,会通知WindowManager添加窗口
        initWindowDecorActionBar();
    }
}

WindowManager添加窗口到WMS

java 复制代码
// frameworks/base/core/java/android/view/WindowManagerImpl.java
// Android 15: WindowManager添加窗口

public final class WindowManagerImpl implements WindowManager {

    /**
     * 添加View到窗口系统
     */
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params); // 应用WindowToken

        // 委托给WindowManagerGlobal处理
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
}

// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {

    /**
     * 真正的添加窗口逻辑
     */
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {

        // 1. 参数校验
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }

        // 2. 确保WindowManager.LayoutParams类型
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

        // 3. 如果是子窗口,调整参数
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // 4. 创建ViewRootImpl (View树的根,连接View和WMS)
            root = new ViewRootImpl(view.getContext(), display);

            // 5. 设置View的布局参数
            view.setLayoutParams(wparams);

            // 6. 记录到全局列表
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // 7. 通过ViewRootImpl添加窗口到WMS
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // 添加失败,回滚
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
}

ViewRootImpl与WMS通信

java 复制代码
// frameworks/base/core/java/android/view/ViewRootImpl.java
// Android 15: ViewRootImpl添加窗口到WMS

public final class ViewRootImpl implements ViewParent {

    /**
     * 设置View并添加到WMS
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                // 1. 触发首次布局测量
                requestLayout();

                // 2. 通过Binder调用WMS添加窗口
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;

                    InputChannel inputChannel = null;
                    if ((mWindowAttributes.inputFeatures
                            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                        inputChannel = new InputChannel();
                    }

                    // 3. 调用WMS.addWindow()
                    int res = mWindowSession.addToDisplay(
                        mWindow,                    // IWindow代理
                        mWindowAttributes,           // 窗口属性
                        getHostVisibility(),         // 可见性
                        mDisplay.getDisplayId(),     // 显示屏ID
                        userId,                      // 用户ID
                        mInsetsController.getRequestedVisibilities(), // 插入区域
                        inputChannel,                // 输入通道
                        mTempInsets,                 // 临时插入区域
                        mTempControls,               // 插入控制
                        attachedFrame,               // 附加帧
                        compatScale                  // 兼容性缩放
                    );

                    // 4. 处理WMS返回结果
                    if (res < WindowManagerGlobal.ADD_OKAY) {
                        // 添加失败,根据错误码抛出异常
                        switch (res) {
                            case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                                throw new WindowManager.BadTokenException(...);
                            case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                                throw new WindowManager.InvalidDisplayException(...);
                            // ... 其他错误处理
                        }
                    }

                    // 5. 设置输入事件接收器
                    if (inputChannel != null) {
                        mInputEventReceiver = new WindowInputEventReceiver(
                            inputChannel, Looper.myLooper()
                        );
                    }

                } catch (RemoteException e) {
                    throw new RuntimeException("Adding window failed", e);
                }
            }
        }
    }
}

WMS添加窗口核心逻辑

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
// Android 15: WMS添加窗口

public class WindowManagerService extends IWindowManager.Stub {

    /**
     * WMS添加窗口的核心方法
     */
    public int addWindow(Session session, IWindow client,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {

        // 1. 权限检查
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs.type, appOp);
        if (res != ADD_OKAY) {
            return res; // 没有权限添加该类型的窗口
        }

        synchronized (mGlobalLock) {
            // 2. 获取DisplayContent
            final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
            if (displayContent == null) {
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            // 3. 验证WindowToken
            WindowToken token = displayContent.getWindowToken(
                attrs.hasParent() ? attrs.token : attrs.token);

            // 首次添加应用窗口时,可能还没有WindowToken
            if (token == null) {
                if (attrs.type >= FIRST_APPLICATION_WINDOW
                        && attrs.type <= LAST_APPLICATION_WINDOW) {
                    // 应用窗口必须有有效的Token
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }

                // 系统窗口,创建隐式Token
                token = new WindowToken(this, attrs.token, attrs.type, false, displayContent);
            }

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

            // 5. 检查窗口是否可以添加
            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != ADD_OKAY) {
                return res;
            }

            // 6. 打开输入通道 (用于接收触摸事件)
            final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }

            // 7. 将WindowState添加到WindowToken
            token.addWindow(win);

            // 8. 添加到全局窗口映射表
            mWindowMap.put(client.asBinder(), win);

            // 9. 更新焦点窗口
            boolean focusChanged = false;
            if (win.canReceiveKeys()) {
                focusChanged = updateFocusedWindowLocked(
                    UPDATE_FOCUS_WILL_ASSIGN_LAYERS, false /*updateInputWindows*/);
                if (focusChanged) {
                    mInputManagerCallback.setFocusedApplicationForDisplay(
                        displayId, mFocusedApp.get(displayId));
                }
            }

            // 10. 触发布局计算
            displayContent.assignWindowLayers(false /* setLayoutNeeded */);

            return ADD_OKAY;
        }
    }
}

窗口创建流程总结

scss 复制代码
Activity.attach()
    ↓ 创建PhoneWindow
Activity.setContentView()
    ↓ 触发窗口添加
WindowManager.addView()
    ↓ 委托给WindowManagerGlobal
WindowManagerGlobal.addView()
    ↓ 创建ViewRootImpl
ViewRootImpl.setView()
    ↓ Binder IPC调用
WMS.addWindow()
    ↓ 验证权限和Token
    ↓ 创建WindowState
    ↓ 添加到窗口树
    ↓ 计算层级(Z-order)
    ↓ 打开输入通道
    ↓ 触发布局计算
窗口显示完成!

窗口层级管理(Z-order)

Android使用Z-order来管理窗口的层叠顺序,Z值越大,窗口越靠上。

Z-order计算规则

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
// Android 15: Z-order层级计算

public class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> {

    /**
     * 为所有窗口分配Z-order层级
     */
    void assignWindowLayers(boolean setLayoutNeeded) {
        assignChildLayers(getSyncTransaction());
        if (setLayoutNeeded) {
            setLayoutNeeded();
        }
    }

    /**
     * 递归为子窗口分配层级
     */
    private void assignChildLayers(SurfaceControl.Transaction t) {
        int layer = 0;

        // 从最底层开始分配
        for (int i = 0; i < mChildren.size(); i++) {
            final DisplayChildWindowContainer child = mChildren.get(i);

            // 每个子容器的基础层级递增
            child.assignChildLayers(t, layer);

            // 计算下一个容器的基础层级
            layer += child.getPrefixOrderIndex();
        }
    }
}

WindowState的Z-order计算

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java
// Android 15: 窗口Z-order

public class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {

    /**
     * 计算窗口的最终Z-order
     */
    void assignLayer(SurfaceControl.Transaction t, int layer) {
        // 1. 基础层级(根据窗口类型)
        final int baseLayer = mBaseLayer;

        // 2. 子层级偏移(Dialog、PopupWindow等)
        final int subLayer = mSubLayer;

        // 3. 最终Z-order = 基础层级 + 子层级偏移
        final int z = baseLayer + subLayer;

        // 4. 应用到SurfaceControl
        t.setLayer(mSurfaceControl, z);

        // 5. 递归处理子窗口
        for (int i = 0; i < mChildren.size(); i++) {
            mChildren.get(i).assignLayer(t, z);
        }
    }

    /**
     * 计算基础层级(根据窗口类型)
     */
    private int calculateBaseLayer() {
        final int type = mAttrs.type;

        // 应用窗口 (1-99)
        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
            return APPLICATION_LAYER; // 2
        }

        // 子窗口 (1000-1999)
        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            // 子窗口层级 = 父窗口层级 + 子窗口类型偏移
            return mAttachedWindow.mBaseLayer
                + (type - FIRST_SUB_WINDOW) * TYPE_LAYER_MULTIPLIER;
        }

        // 系统窗口 (2000-2999)
        switch (type) {
            case TYPE_STATUS_BAR:
                return STATUS_BAR_LAYER; // 高层级
            case TYPE_NAVIGATION_BAR:
                return NAVIGATION_BAR_LAYER; // 最高层级
            case TYPE_INPUT_METHOD:
                return INPUT_METHOD_LAYER; // 次高层级
            case TYPE_TOAST:
                return TOAST_LAYER;
            // ... 其他系统窗口类型
        }

        return APPLICATION_LAYER; // 默认应用层级
    }
}

Z-order实际案例

让我们看一个真实的例子,展示多个窗口的层级关系:

java 复制代码
// 场景:用户打开微信,弹出Dialog,输入法弹起

窗口层级 (从上到下):

Z=2019  Navigation Bar (导航栏)
        ├── TYPE_NAVIGATION_BAR
        └── 始终在最顶层

Z=2011  Input Method (输入法)
        ├── TYPE_INPUT_METHOD
        └── 用户输入时显示

Z=2005  Toast
        ├── TYPE_TOAST
        └── 短暂提示信息

Z=1010  Dialog (微信的Dialog)
        ├── TYPE_APPLICATION_PANEL
        ├── 父窗口 = 微信Activity
        └── 层级 = 父窗口层级 + 1000

Z=10    微信Activity窗口
        ├── TYPE_APPLICATION
        └── 基础应用窗口

Z=2     壁纸窗口
        ├── TYPE_WALLPAPER
        └── 最底层

Z-order调整机制

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

/**
 * 当窗口需要置顶时(如来电界面)
 */
void setWindowOnTop(WindowState win) {
    synchronized (mGlobalLock) {
        // 1. 找到窗口所在的DisplayContent
        final DisplayContent displayContent = win.getDisplayContent();

        // 2. 移除窗口
        win.getParent().removeChild(win);

        // 3. 添加到最顶层
        displayContent.addChild(win, Integer.MAX_VALUE);

        // 4. 重新计算所有窗口的Z-order
        displayContent.assignWindowLayers(true);

        // 5. 更新SurfaceFlinger
        displayContent.scheduleAnimation();
    }
}

窗口动画系统

窗口切换时的动画效果由WindowAnimator管理。

动画触发时机

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java
// Android 15: 窗口动画管理器

public class WindowAnimator {

    /**
     * 主动画循环 (60fps)
     */
    private void animate(long frameTimeNs) {
        synchronized (mService.mGlobalLock) {
            // 1. 遍历所有DisplayContent
            for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
                DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);

                // 2. 更新每个显示屏的窗口动画
                displayAnimator.updateWindowsLocked(frameTimeNs);

                // 3. 更新壁纸动画
                displayAnimator.updateWallpaperLocked();
            }

            // 4. 准备下一帧
            if (mAnimating) {
                scheduleAnimation();
            }
        }
    }
}

窗口进入/退出动画

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java

/**
 * 窗口进入动画
 */
void applyEnterAnimation() {
    // 1. 加载动画资源
    Animation anim = mWinAnimator.mPolicy.createEnterAnimation(
        mAttrs.type, mAttrs.windowAnimations);

    if (anim != null) {
        // 2. 初始化动画
        anim.initialize(
            mFrame.width(),  // 窗口宽度
            mFrame.height(), // 窗口高度
            mFrame.width(),  // 父窗口宽度
            mFrame.height()  // 父窗口高度
        );

        // 3. 启动动画
        mWinAnimator.setAnimation(anim);
        mAnimatingExit = false;

        // 4. 触发首帧绘制
        scheduleAnimation();
    }
}

/**
 * 窗口退出动画
 */
void applyExitAnimation() {
    // 1. 加载退出动画
    Animation anim = mWinAnimator.mPolicy.createExitAnimation(
        mAttrs.type, mAttrs.windowAnimations);

    if (anim != null) {
        // 2. 启动动画
        mWinAnimator.setAnimation(anim);
        mAnimatingExit = true; // 标记正在退出

        // 3. 动画结束后的回调
        mWinAnimator.setAnimationFinishedCallback(() -> {
            // 移除窗口
            mService.mWindowMap.remove(mClient.asBinder());
            destroySurface();
        });
    } else {
        // 没有动画,直接销毁
        destroySurface();
    }
}

Activity切换动画

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
// Android 15: Activity切换动画

public class ActivityRecord extends WindowToken {

    /**
     * Activity切换动画
     */
    void applyOptionsAnimation(ActivityRecord target, int transit) {
        // 1. 确定动画类型
        final int animationType;
        switch (transit) {
            case TRANSIT_OPEN:
                animationType = ANIM_OPEN;
                break;
            case TRANSIT_CLOSE:
                animationType = ANIM_CLOSE;
                break;
            case TRANSIT_TO_FRONT:
                animationType = ANIM_TO_FRONT;
                break;
            case TRANSIT_TO_BACK:
                animationType = ANIM_TO_BACK;
                break;
            default:
                animationType = ANIM_NONE;
        }

        // 2. 加载动画资源
        Animation anim = loadAnimation(animationType);

        // 3. 应用动画到所有窗口
        forAllWindows(win -> {
            win.mWinAnimator.setAnimation(anim);
        }, true /* traverseTopToBottom */);

        // 4. 更新动画状态
        mStackSupervisor.mAnimationRunning = true;
    }

    /**
     * Android 15新增:平滑的Shared Element过渡动画
     */
    void applySharedElementTransition(ActivityRecord source) {
        // 1. 获取共享元素
        ArrayList<View> sharedElements = source.getSharedElements();

        // 2. 计算过渡动画
        for (View sharedElement : sharedElements) {
            // 起始位置
            int[] startLocation = new int[2];
            sharedElement.getLocationOnScreen(startLocation);

            // 结束位置 (在目标Activity中查找对应的View)
            View targetView = findSharedElementInTarget(sharedElement.getTransitionName());
            int[] endLocation = new int[2];
            targetView.getLocationOnScreen(endLocation);

            // 3. 创建移动+缩放动画
            AnimatorSet animSet = new AnimatorSet();
            animSet.playTogether(
                ObjectAnimator.ofFloat(sharedElement, "translationX",
                    startLocation[0], endLocation[0]),
                ObjectAnimator.ofFloat(sharedElement, "translationY",
                    startLocation[1], endLocation[1]),
                ObjectAnimator.ofFloat(sharedElement, "scaleX",
                    1.0f, targetView.getWidth() / (float) sharedElement.getWidth()),
                ObjectAnimator.ofFloat(sharedElement, "scaleY",
                    1.0f, targetView.getHeight() / (float) sharedElement.getHeight())
            );

            animSet.setDuration(300); // 300ms过渡动画
            animSet.start();
        }
    }
}

Android 15动画优化

  • ⚡ 使用RenderThread渲染动画,避免主线程阻塞
  • 🎨 支持Shared Element Transition(共享元素过渡)
  • 🚀 GPU加速的动画合成,性能提升40%
  • 📐 自适应刷新率,120Hz屏幕下更流畅

输入窗口与触摸事件分发

WMS负责确定哪个窗口应该接收用户的触摸事件。

输入窗口的概念

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

/**
 * 输入窗口:能够接收触摸事件的窗口
 */
class InputWindowInfo {
    String name;                   // 窗口名称
    IBinder windowToken;           // 窗口Token
    int displayId;                 // 所属显示屏
    Rect frame;                    // 窗口边界
    Rect touchableRegion;          // 可触摸区域
    boolean hasFocus;              // 是否有焦点
    boolean hasWallpaper;          // 是否有壁纸
    boolean paused;                // 是否暂停
    int layer;                     // Z-order层级
    float alpha;                   // 透明度
    boolean canReceiveKeys;        // 是否接收按键事件
    boolean touchModal;            // 是否模态(独占触摸)
}

触摸事件命中测试

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
// Android 15: 触摸事件命中测试

public class InputMonitor {

    /**
     * 更新输入窗口列表(发送给InputManagerService)
     */
    void updateInputWindowsLw(boolean force) {
        // 1. 收集所有可接收输入的窗口
        final InputWindowList inputWindows = new InputWindowList();

        // 2. 从最顶层开始遍历窗口
        mDisplayContent.forAllWindows(w -> {
            final WindowState windowState = w;

            // 3. 检查窗口是否可以接收输入
            if (windowState.mInputChannel == null || windowState.isGoneForLayout()) {
                return; // 跳过不可见的窗口
            }

            // 4. 构建InputWindowInfo
            final InputWindowInfo inputWindowInfo = new InputWindowInfo();
            inputWindowInfo.name = windowState.getName();
            inputWindowInfo.windowToken = windowState.mClient.asBinder();
            inputWindowInfo.displayId = windowState.getDisplayId();

            // 5. 设置窗口边界和可触摸区域
            windowState.getFrame(inputWindowInfo.frame);
            windowState.getTouchableRegion(inputWindowInfo.touchableRegion);

            // 6. 设置Z-order层级
            inputWindowInfo.layer = windowState.mLayer;

            // 7. 设置焦点状态
            inputWindowInfo.hasFocus = windowState.isFocused();

            // 8. 设置模态属性
            inputWindowInfo.touchModal = (windowState.mAttrs.flags &
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL) == 0;

            // 9. 添加到列表
            inputWindows.add(inputWindowInfo);

        }, true /* traverseTopToBottom */);

        // 10. 发送给InputManagerService
        mInputManager.setInputWindows(inputWindows.toArray());
    }

    /**
     * 查找命中的窗口
     */
    WindowState findTouchedWindowLocked(int displayId, float x, float y) {
        final DisplayContent displayContent = mRoot.getDisplayContent(displayId);

        // 1. 从最顶层开始遍历
        WindowState touchedWindow = displayContent.getWindow(w -> {
            final WindowState windowState = w;

            // 2. 检查窗口是否可以接收触摸
            if (!windowState.canReceiveKeys()) {
                return false;
            }

            // 3. 检查触摸点是否在窗口范围内
            if (!windowState.mFrame.contains((int) x, (int) y)) {
                return false;
            }

            // 4. 检查可触摸区域
            final Region touchableRegion = windowState.getTouchableRegion();
            if (!touchableRegion.contains((int) x, (int) y)) {
                return false;
            }

            // 5. 找到第一个匹配的窗口
            return true;
        });

        return touchedWindow;
    }
}

触摸事件分发流程

scss 复制代码
用户触摸屏幕
    ↓
InputReader读取触摸事件
    ↓
InputDispatcher查询WMS的输入窗口列表
    ↓
根据Z-order从上到下遍历窗口
    ↓
检查触摸点是否在窗口的TouchableRegion内
    ↓
找到目标窗口
    ↓
通过InputChannel发送事件到应用进程
    ↓
应用的View.onTouchEvent()接收事件

关键点

  • Z-order优先:从最顶层窗口开始匹配
  • 模态窗口 :如果窗口设置了FLAG_NOT_TOUCH_MODAL,则会继续向下传递
  • TouchableRegion:可以设置窗口的部分区域不接收触摸(如圆角、镂空)

WindowToken机制

WindowToken是WMS中的重要概念,用于标识窗口的归属和权限。

WindowToken的作用

  1. 权限校验:只有持有Token的组件才能创建窗口
  2. 生命周期管理:Token销毁时,所有关联窗口自动销毁
  3. 窗口分组:同一Token的窗口共享某些属性

WindowToken的创建

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
// Android 15: WindowToken

public class WindowToken extends WindowContainer<WindowState> {

    /**
     * 构造函数
     */
    WindowToken(WindowManagerService service, IBinder _token, int type,
            boolean persistOnEmpty, DisplayContent dc) {
        super(service);

        token = _token;             // Binder Token
        windowType = type;          // 窗口类型
        mPersistOnEmpty = persistOnEmpty; // 是否在无窗口时保留
        mOwnerCanManageAppTokens = false;

        // 添加到DisplayContent
        dc.addWindowToken(token, this);
    }

    /**
     * 添加窗口到Token
     */
    void addWindow(final WindowState win) {
        // 1. 检查窗口类型是否匹配
        if (win.mAttrs.type != windowType) {
            throw new IllegalArgumentException("Window type mismatch");
        }

        // 2. 添加到子窗口列表
        addChild(win, mWindowComparator);

        // 3. 更新Token状态
        mHasVisible = true;
    }

    /**
     * 移除窗口
     */
    void removeWindow(WindowState win) {
        // 1. 从子窗口列表移除
        removeChild(win);

        // 2. 检查是否还有窗口
        if (mChildren.isEmpty()) {
            // 3. 如果不需要保留,删除Token
            if (!mPersistOnEmpty) {
                removeImmediately();
            }
        }
    }

    /**
     * 销毁Token(所有关联窗口也会销毁)
     */
    void removeImmediately() {
        // 1. 移除所有子窗口
        while (!mChildren.isEmpty()) {
            final WindowState win = mChildren.get(0);
            win.removeImmediately();
        }

        // 2. 从DisplayContent移除
        if (mDisplayContent != null) {
            mDisplayContent.removeWindowToken(token);
        }

        // 3. 从父容器移除
        super.removeImmediately();
    }
}

Activity的WindowToken

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

/**
 * ActivityRecord继承自WindowToken
 * 每个Activity都有一个唯一的WindowToken
 */
public class ActivityRecord extends WindowToken {

    /**
     * Activity创建时分配Token
     */
    static ActivityRecord forTokenLocked(IBinder token) {
        try {
            return Token.tokenToActivityRecordMap.get(token);
        } catch (ClassCastException e) {
            return null;
        }
    }

    /**
     * Activity销毁时,Token也会销毁
     */
    void destroy(String reason) {
        // 1. 移除所有窗口
        removeAllWindows();

        // 2. 从任务栈移除
        if (task != null) {
            task.removeActivity(this);
        }

        // 3. 销毁Token
        removeImmediately();
    }
}

Token验证机制

java 复制代码
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

/**
 * 添加窗口时验证Token
 */
public int addWindow(Session session, IWindow client,
        WindowManager.LayoutParams attrs, ...) {

    synchronized (mGlobalLock) {
        // 1. 获取Token
        final IBinder tokenIBinder = attrs.token;

        // 2. 查找WindowToken
        WindowToken token = displayContent.getWindowToken(tokenIBinder);

        // 3. 应用窗口必须有有效Token
        if (attrs.type >= FIRST_APPLICATION_WINDOW
                && attrs.type <= LAST_APPLICATION_WINDOW) {
            if (token == null) {
                // 没有Token,拒绝添加
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }

            // 4. 验证Token类型
            if (token.windowType != attrs.type) {
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        }

        // 5. 系统窗口可以创建隐式Token
        if (token == null && attrs.type >= FIRST_SYSTEM_WINDOW) {
            token = new WindowToken(this, tokenIBinder, attrs.type,
                false, displayContent);
        }

        // ... 继续添加窗口
    }
}

Token机制的安全性

  • ✅ 防止恶意应用伪造其他应用的窗口
  • ✅ Activity销毁时,所有窗口自动清理,防止内存泄漏
  • ✅ 系统窗口需要特殊权限才能创建Token

实战案例:dumpsys window分析

让我们通过dumpsys window命令来实战分析WMS的状态。

查看窗口列表

bash 复制代码
# 查看所有窗口
adb shell dumpsys window windows

# 输出示例:
Window #0 Window{a1b2c3d u0 NavigationBar0}:
    mDisplayId=0 mSession=Session{e4f5g6h} mClient=android.os.BinderProxy@x
    mOwnerUid=1000 mShowToOwnerOnly=false
    mAttrs={(0,0)(fillxfill) sim={adjust=pan} ty=NAVIGATION_BAR fmt=TRANSLUCENT
      fl=NOT_FOCUSABLE NOT_TOUCH_MODAL LAYOUT_IN_SCREEN SPLIT_TOUCH
      pfl=COLOR_SPACE_AGNOSTIC
      fitInsetsTypes={}}
    Requested w=1080 h=132 mLayoutSeq=42
    mBaseLayer=231000 mSubLayer=0 mToken=WindowToken{i7j8k9l android.os.BinderProxy@y type=NAVIGATION_BAR}
    mFrame=[0,2208][1080,2340]
    mHasSurface=true isReadyForDisplay()=true mWindowRemovalAllowed=false
    Frames: containing=[0,0][1080,2340] parent=[0,0][1080,2340]

Window #1 Window{m1n2o3p u0 com.android.chrome/MainActivity}:
    mDisplayId=0 mSession=Session{q4r5s6t} mClient=android.os.BinderProxy@z
    mOwnerUid=10086 mShowToOwnerOnly=false
    mAttrs={(0,0)(fillxfill) sim={adjust=resize} ty=APPLICATION fmt=TRANSLUCENT
      fl=DIM_BEHIND LAYOUT_IN_SCREEN LAYOUT_INSET_DECOR SPLIT_TOUCH HARDWARE_ACCELERATED
      pfl=USE_BLAST COLOR_SPACE_AGNOSTIC DISABLE_ACTIVITY_TRANSITIONS
      fitInsetsTypes={}}
    Requested w=1080 h=2340 mLayoutSeq=42
    mBaseLayer=21000 mSubLayer=0 mToken=ActivityRecord{u8v9w0x t12 com.android.chrome/.MainActivity}
    mFrame=[0,0][1080,2208]
    mHasSurface=true isReadyForDisplay()=true mWindowRemovalAllowed=false

关键信息解读

  • mDisplayId=0:主显示屏
  • mOwnerUid=10086:所属应用的UID
  • ty=NAVIGATION_BAR:窗口类型
  • mBaseLayer=231000:Z-order基础层级
  • mFrame=[0,2208][1080,2340]:窗口位置(左上角x,y → 右下角x,y)
  • mHasSurface=true:窗口已创建Surface(可以绘制)

查看焦点窗口

bash 复制代码
# 查看当前焦点窗口
adb shell dumpsys window | grep mCurrentFocus

# 输出示例:
mCurrentFocus=Window{a1b2c3d u0 com.android.chrome/MainActivity}

查看窗口层级

bash 复制代码
# 查看窗口的Z-order层级
adb shell dumpsys window | grep "mBaseLayer"

# 输出示例(从上到下排列):
mBaseLayer=231000  # NavigationBar (最顶层)
mBaseLayer=211000  # InputMethod
mBaseLayer=200500  # Toast
mBaseLayer=100100  # Dialog
mBaseLayer=21000   # Activity窗口
mBaseLayer=2000    # Wallpaper (最底层)

查看窗口动画状态

bash 复制代码
# 查看动画状态
adb shell dumpsys window | grep "mAnimating"

# 输出示例:
mAnimating=true
mAnimatingExit=false
mAnimation=WindowAnimation{duration=300 fillBefore=true}

诊断窗口问题

问题1:窗口无法显示

bash 复制代码
# 检查WindowToken是否有效
adb shell dumpsys window | grep "BadTokenException"

# 检查窗口是否有Surface
adb shell dumpsys window | grep "mHasSurface"

问题2:触摸事件无响应

bash 复制代码
# 检查焦点窗口
adb shell dumpsys window | grep "mCurrentFocus"

# 检查TouchableRegion
adb shell dumpsys window | grep "touchableRegion"

问题3:窗口层级错误

bash 复制代码
# 对比期望的Z-order和实际的mBaseLayer
adb shell dumpsys window windows | grep -E "(Window #|mBaseLayer)"

总结

本文深入探讨了WMS的核心机制,这些机制共同构成了Android强大的窗口管理能力:

核心要点回顾

1. WMS架构

层次 组件 职责
Layer 1 WindowManagerService 核心服务,管理所有窗口
Layer 2 RootWindowContainer 根容器,管理所有显示屏
Layer 3 DisplayContent 显示内容,每个显示屏一个
Layer 4 WindowToken → WindowState 窗口容器层次

关键技术:容器嵌套、树形结构、Binder IPC

2. 窗口创建与显示

scss 复制代码
Activity.setContentView()
    ↓
WindowManager.addView()
    ↓
ViewRootImpl.setView()
    ↓ Binder IPC
WMS.addWindow()
    ↓
创建WindowState
    ↓
分配Z-order
    ↓
打开InputChannel
    ↓
触发布局计算
    ↓
窗口显示完成

关键技术:WindowManager、ViewRootImpl、Binder通信

3. Z-order层级管理

  • 应用窗口 (Z=1-99):Activity
  • 子窗口 (Z=1000-1999):Dialog、PopupWindow
  • 系统窗口 (Z=2000-2999):状态栏、导航栏、输入法

关键技术:层级计算、动态调整、SurfaceControl

4. 窗口动画系统

  • 进入动画:窗口创建时播放
  • 退出动画:窗口销毁时播放
  • 切换动画:Activity切换时播放
  • 共享元素过渡:平滑的Shared Element动画(Android 15)

关键技术:WindowAnimator、RenderThread、GPU加速

5. 输入事件分发

markdown 复制代码
触摸屏幕
    ↓
InputDispatcher查询WMS
    ↓
从顶层窗口开始命中测试
    ↓
检查TouchableRegion
    ↓
找到目标窗口
    ↓
通过InputChannel分发事件

关键技术:InputMonitor、InputWindowInfo、TouchableRegion

6. WindowToken机制

  • ✅ 权限校验:只有持有Token才能创建窗口
  • ✅ 生命周期管理:Token销毁时所有窗口自动销毁
  • ✅ 窗口分组:同一Token的窗口共享属性

关键技术:WindowToken、ActivityRecord、安全验证

Android 15的WMS增强

  1. 性能优化

    • Z-order计算优化,层级更新速度提升30%
    • 窗口动画使用RenderThread,避免主线程阻塞
    • GPU加速合成,动画性能提升40%
  2. 动画增强

    • 支持Shared Element Transition(共享元素过渡)
    • 自适应刷新率,120Hz屏幕下更流畅
    • 更丰富的过渡动画效果
  3. 多显示屏支持

    • 每个显示屏独立的DisplayContent
    • 跨显示屏的窗口拖拽
    • 虚拟显示(VirtualDisplay)性能优化
  4. 输入延迟优化

    • 触摸事件处理延迟降低20%
    • 更精确的TouchableRegion计算
    • 优化的事件分发链路

实战应用场景

  • 🎨 自定义窗口动画:修改Activity切换动画
  • 🖼️ 画中画模式:视频播放器悬浮窗
  • 💬 悬浮窗应用:聊天气泡、桌面小部件
  • 🎮 游戏优化:减少输入延迟、提升帧率
  • 📱 多窗口模式:分屏、自由窗口、桌面模式

参考资料

AOSP源码路径(Android 15)

bash 复制代码
# WMS核心实现
frameworks/base/services/core/java/com/android/server/wm/
├── WindowManagerService.java        # WMS核心服务
├── WindowState.java                 # 窗口状态
├── WindowToken.java                 # 窗口令牌
├── DisplayContent.java              # 显示内容管理
├── RootWindowContainer.java         # 根容器
├── WindowAnimator.java              # 动画管理器
└── InputMonitor.java                # 输入事件监控

# WindowManager客户端
frameworks/base/core/java/android/view/
├── WindowManager.java               # WindowManager接口
├── WindowManagerImpl.java           # WindowManager实现
├── WindowManagerGlobal.java         # 全局窗口管理
└── ViewRootImpl.java                # View树根节点

# SurfaceFlinger
frameworks/native/services/surfaceflinger/
└── SurfaceFlinger.cpp               # Surface合成服务

官方文档

调试命令

bash 复制代码
# 窗口管理
adb shell dumpsys window windows          # 列出所有窗口
adb shell dumpsys window displays         # 列出所有显示屏
adb shell dumpsys window | grep "mCurrentFocus"  # 查看焦点窗口
adb shell dumpsys window | grep "mBaseLayer"     # 查看Z-order层级

# 输入事件
adb shell dumpsys input                    # 输入系统状态
adb shell getevent -lt                     # 实时查看触摸事件

# 窗口动画
adb shell settings put global window_animation_scale 0.5  # 设置动画速度(0.5x)
adb shell settings put global window_animation_scale 0    # 关闭窗口动画

# 显示屏管理
adb shell dumpsys display                  # 显示屏信息
adb shell dumpsys SurfaceFlinger          # SurfaceFlinger状态

系列文章


本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品

相关推荐
松仔log2 小时前
JetPack——Paging
android·rxjava
城东米粉儿2 小时前
Android Kotlin DSL 笔记
android
城东米粉儿2 小时前
Android Gradle 笔记
android
城东米粉儿2 小时前
Android Monkey 笔记
android
城东米粉儿3 小时前
Android 组件化 笔记
android
编程小风筝3 小时前
Android移动端如何实现多线程编程?
android
城东米粉儿3 小时前
Android 模块化 笔记
android
城东米粉儿3 小时前
Android HandlerThread 笔记
android