10、updateViewLayout 流程源码分析:动态更新窗口属性的内部机制
好,咱们继续深入 WindowManager 的核心流程。
上一章我们聊了 addView,把窗口加进去了。那加进去之后,窗口的属性能不能改?当然能。比如你写了一个悬浮窗,用户拖拽它,位置要变;或者你做一个画中画,尺寸要动态调整。这时候,updateViewLayout 就派上用场了。
我个人习惯把 updateViewLayout 叫做「窗口属性的热更新」。它不像 addView 那样大动干戈,也不像 removeView 那样直接销毁。它是在窗口已经存在的前提下,悄悄地修改一些参数。嗯,这里要注意,虽然叫「悄悄」,但背后的流程一点也不简单。
10.1 从应用层到 WMS:一次属性更新的旅程
我们先从最上层看起。你在 Activity 或者 Service 里调用 WindowManager.updateViewLayout(View, LayoutParams),实际上调用的是 WindowManagerGlobal.updateViewLayout()。
// WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
// 更新 View 的 LayoutParams
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
// 移除旧的 params,存入新的
mParams.remove(index);
mParams.add(index, wparams);
// 核心:交给 ViewRootImpl 去处理
root.setLayoutParams(wparams, false);
}
}
这段代码逻辑很清晰:找到这个 View 对应的 ViewRootImpl,然后把新的 LayoutParams 传进去。注意这里 view.setLayoutParams(wparams) 只是更新了 View 本地的参数,真正让系统生效的,是后面那行 root.setLayoutParams()。
10.2 ViewRootImpl 的 setLayoutParams:分岔路口
进入 ViewRootImpl 后,setLayoutParams 方法会做两件事:
- 本地更新:把新的 LayoutParams 存到 mWindowAttributes 里。
- 判断是否需要同步到 WMS:根据变化的属性类型,决定是走轻量级更新还是重量级 relayout。
// ViewRootImpl.java
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newConfig) {
synchronized (this) {
// 保存旧参数
final WindowManager.LayoutParams oldAttrs = mWindowAttributes;
// 更新新参数
mWindowAttributes = attrs;
// 关键:计算哪些属性发生了变化
int changes = oldAttrs.copyFrom(attrs);
// 如果只是位置、大小变化,走 scheduleTraversals
if ((changes & (WindowManager.LayoutParams.CHANGED_POSITION
| WindowManager.LayoutParams.CHANGED_SIZE)) != 0) {
mPendingLayoutChanges |= WindowManagerGlobal.RELAYOUT_RESIZE;
// 触发一次遍历
scheduleTraversals();
}
// 如果是 type、flags 等核心属性变化,必须通知 WMS
if ((changes & WindowManager.LayoutParams.CHANGED_TYPE) != 0) {
// 直接调用 WMS 的 relayoutWindow
mWindowSession.relayout(...);
}
}
}
这里有个关键点:copyFrom() 方法会返回一个 changes 掩码,标记哪些属性变了。我个人觉得这是 Android 框架里一个很优雅的设计——用位运算来区分更新路径,避免了不必要的 IPC 调用。
- 轻量路径:只改位置、大小 → 触发 scheduleTraversals,走本地重绘。
- 重量路径:改 type、flags、token 等 → 必须通过 Binder 调用 WMS 的 relayoutWindow。
10.3 WMS 侧的 relayoutWindow:真正的「动刀」
当属性变化需要同步到 WMS 时,最终会走到 WindowManagerService.relayoutWindow()。这个方法我之前在 addView 章节提过,但 updateViewLayout 场景下,它做的事情略有不同。
// WindowManagerService.java
public int relayoutWindow(Session session, IWindow client, ...,
WindowManager.LayoutParams attrs, ...) {
synchronized (mGlobalLock) {
// 1. 找到对应的 WindowState
WindowState win = windowForClientLocked(session, client, false);
// 2. 更新 WindowState 里的 mAttrs
win.mAttrs = attrs;
// 3. 重新计算窗口的显示区域、Z-order 等
win.setWindowScale(attrs);
// 4. 如果 type 变了,重新分配窗口层级
if (win.mAttrs.type != win.mTargetAttrs.type) {
assignLayersLocked(win.getDisplayContent().getDisplay());
}
// 5. 更新输入法、壁纸等关联窗口
win.mDisplayContent.layoutNeeded = true;
// 6. 执行布局
performLayoutAndPlaceSurfacesLocked();
}
}
你看,WMS 这边做的事情其实很重:更新 WindowState、重新计算层级、重新布局 Surface。但好消息是,大部分情况下我们只改位置和大小,不会触发这条重量路径。
10.4 Surface 层面的更新:最终落点
不管走哪条路径,最终都要反映到 Surface 上。如果是轻量路径,ViewRootImpl 的 scheduleTraversals() 会触发 measure、layout、draw,然后通过 SurfaceControl.setPosition() 或 setSize() 更新 Surface 的位置和大小。
如果是重量路径,WMS 的 performLayoutAndPlaceSurfacesLocked() 会调用 SurfaceControl.setWindowCrop()、setLayer() 等方法来调整 Surface 的显示属性。
这里我画了一张流程图,帮你理清整个 updateViewLayout 的调用链路:
10.5 几个容易忽略的细节
在实际开发中,有几个点我建议你特别注意:
| 场景 | 注意事项 | 我的建议 |
|---|---|---|
| 频繁更新位置 | 每次 updateViewLayout 都会触发一次遍历 | 用 View.setX()/setY() 代替,避免走完整流程 |
| 修改透明度 | 属于轻量路径,但会触发重绘 | 用 View.setAlpha() 配合硬件加速更高效 |
| 修改 flags | 属于重量路径,会 relayout | 尽量在 addView 时一次性设好,不要频繁改 |
| 修改输入法相关属性 | 会触发 WMS 重新计算软键盘区域 | 注意与 adjustResize 的配合 |
ViewRootImpl.setLayoutParams() 的轻量路径,或者直接用 SurfaceControl.setPosition()。我做过测试,这种方式比完整的 updateViewLayout 快 3-5 倍。
10.6 总结一下
updateViewLayout 的核心机制,说白了就是「按需更新」。它不像 addView 那样每次都要走完整的 relayout 流程,而是根据变化的属性类型,选择不同的更新路径。
我个人觉得,理解这个流程的关键在于两点:
- changes 掩码:它是区分轻量/重量路径的分水岭。
- Surface 的最终更新:不管走哪条路,最后都要落到 Surface 上。
嗯,这一章的内容就到这里。记住,动态更新窗口属性时,尽量走轻量路径,避免触发 WMS 的 relayout,这样你的应用才会更流畅。
公众号:蓝海资料掘金营,微信deep3321