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

0 阅读22分钟

引言

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

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

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

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

用户点击应用图标
    ↓
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需要统一管理:

// 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示例

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

WMS的核心数据结构

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

// 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的层次结构

18-01-wms-architecture.png

图: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启动触发窗口创建

// 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

// 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通信

// 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添加窗口核心逻辑

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

窗口创建流程总结

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计算规则

// 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计算

// 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实际案例

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

// 场景:用户打开微信,弹出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调整机制

// 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管理。

动画触发时机

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

窗口进入/退出动画

// 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切换动画

// 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负责确定哪个窗口应该接收用户的触摸事件。

输入窗口的概念

// 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;            // 是否模态(独占触摸)
}

触摸事件命中测试

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

触摸事件分发流程

用户触摸屏幕
    ↓
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的创建

// 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

// 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验证机制

// 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的状态。

查看窗口列表

# 查看所有窗口
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(可以绘制)

查看焦点窗口

# 查看当前焦点窗口
adb shell dumpsys window | grep mCurrentFocus

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

查看窗口层级

# 查看窗口的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 (最底层)

查看窗口动画状态

# 查看动画状态
adb shell dumpsys window | grep "mAnimating"

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

诊断窗口问题

问题1:窗口无法显示

# 检查WindowToken是否有效
adb shell dumpsys window | grep "BadTokenException"

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

问题2:触摸事件无响应

# 检查焦点窗口
adb shell dumpsys window | grep "mCurrentFocus"

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

问题3:窗口层级错误

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

总结

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

核心要点回顾

1. WMS架构

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

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

2. 窗口创建与显示

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. 输入事件分发

触摸屏幕
    ↓
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)

# 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合成服务

官方文档

调试命令

# 窗口管理
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可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品