引言
在前面的文章中,我们深入了解了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;
}
关键数据结构说明:
-
WindowState:代表一个窗口的完整状态
- 包含窗口位置、大小、可见性、Z-order等信息
- 对应应用侧的Window对象
-
WindowToken:窗口令牌,标识窗口的归属
- 同一个Activity的所有窗口共享一个WindowToken
- 用于权限校验和生命周期管理
-
DisplayContent:显示内容管理
- 每个物理/虚拟显示屏对应一个DisplayContent
- 管理该显示屏上的所有窗口
-
RootWindowContainer:根容器
- 整个窗口树的根节点
- 包含所有DisplayContent
WMS架构深度解析
WMS的层次结构

图:WMS架构展示了从顶层WindowManagerService到底层WindowState的完整层次结构,以及与SurfaceFlinger、InputManagerService的交互关系
架构层次说明:
-
Layer 1 - 核心服务层
WindowManagerService:整个窗口系统的核心控制器- 单例模式,运行在SystemServer进程
-
Layer 2 - 根容器层
RootWindowContainer:管理所有显示屏的根容器- 负责跨显示屏的窗口管理和任务协调
-
Layer 3 - 显示内容层
DisplayContent:每个物理/虚拟显示屏一个实例- 主屏幕(Display 1):手机/平板的主显示
- 副屏幕(Display 2):外接显示器、投影等
-
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的作用
- 权限校验:只有持有Token的组件才能创建窗口
- 生命周期管理:Token销毁时,所有关联窗口自动销毁
- 窗口分组:同一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:所属应用的UIDty=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增强
-
性能优化
- Z-order计算优化,层级更新速度提升30%
- 窗口动画使用RenderThread,避免主线程阻塞
- GPU加速合成,动画性能提升40%
-
动画增强
- 支持Shared Element Transition(共享元素过渡)
- 自适应刷新率,120Hz屏幕下更流畅
- 更丰富的过渡动画效果
-
多显示屏支持
- 每个显示屏独立的DisplayContent
- 跨显示屏的窗口拖拽
- 虚拟显示(VirtualDisplay)性能优化
-
输入延迟优化
- 触摸事件处理延迟降低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可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品