第3章:Activity的窗口管理与ViewRootImpl
说实话,窗口管理这块,我当年刚接触时也是一头雾水。
Activity、Window、DecorView、ViewRootImpl……这些概念堆在一起,很容易让人懵。但搞懂了它们之间的关系,你才算真正理解了Android的UI体系。
今天我们就来拆解一下,窗口是怎么创建的,ViewRootImpl又是怎么把布局变成屏幕上的像素的。
3.1 Window与WindowManager:窗口的抽象与操作
先问一个问题:Activity是怎么把布局显示到屏幕上的?
很多人以为Activity直接持有View,其实不是。中间还隔着一层——Window。
Window是个抽象类,它定义了窗口的基本行为。比如添加View、设置布局参数、管理触摸事件等等。但Window本身不干活,真正干活的是它的实现类——PhoneWindow。
核心要点:每个Activity对应一个PhoneWindow实例。这个PhoneWindow是在Activity的attach()方法里创建的。
我们来看一下源码中的创建过程:
// Activity.java
final void attach(...) {
// 创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 设置WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
}
嗯,这里要注意:mWindowManager并不是系统服务那个WindowManager,而是WindowManagerImpl的实例。它内部持有WindowManagerGlobal,这才是真正干活的单例。
个人经验:我在做悬浮窗功能时,就踩过这个坑。直接用系统服务拿到的WindowManager添加View,和Activity内部拿到的WindowManager,行为是不一样的。前者需要SYSTEM_ALERT_WINDOW权限,后者不需要。
3.2 DecorView:窗口的根布局
有了Window,接下来就是往里面放View了。
Activity在setContentView()时,会触发一个关键操作——创建DecorView。
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 首次调用,创建DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 把我们的布局inflate到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
installDecor()里做了什么?说白了就是创建了一个DecorView,然后从它里面找到android.R.id.content这个FrameLayout,赋值给mContentParent。
DecorView的结构大概是这样的:
DecorView
├── LinearLayout (垂直)
│ ├── TitleArea (标题栏区域,可选)
│ └── ContentFrameLayout (id = content)
│ └── 你的布局
└── StatusBarBackground (状态栏背景)
所以,你写的布局其实是被塞进了DecorView的content区域里。DecorView才是真正的根View。
避坑指南:我曾经在自定义Dialog时,直接拿DecorView做动画,结果发现状态栏也跟着动了。原因就是DecorView包含了整个窗口区域,不只是内容区。后来我改用getWindow().getDecorView()的子View做动画,才解决问题。
3.3 ViewRootImpl:窗口管理的核心
DecorView创建好了,但还没显示出来。谁来触发绘制?谁来处理事件?
答案是ViewRootImpl。
当Activity调用onResume()后,WindowManager会通过addView()把DecorView添加进去。这个过程会创建一个ViewRootImpl,并建立关联。
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 创建ViewRootImpl
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
// 建立关联
root.setView(view, wparams, panelParentView);
// 保存到列表中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
ViewRootImpl一创建,就开始干活了。它会发起第一次requestLayout(),触发整个绘制流程。
3.4 performTraversals:一次完整的绘制流程
这是ViewRootImpl里最核心的方法,没有之一。
performTraversals()负责协调测量、布局、绘制三个阶段。我把它称为「三件套」:
- performMeasure — 测量每个View的大小
- performLayout — 确定每个View的位置
- performDraw — 把View画到屏幕上
我们来看简化后的流程:
// ViewRootImpl.java (简化)
private void performTraversals() {
// 1. 获取窗口参数
WindowManager.LayoutParams lp = mWindowAttributes;
// 2. 测量阶段
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 3. 布局阶段
performLayout(lp, mWidth, mHeight);
// 4. 绘制阶段
performDraw();
}
每个阶段内部,都会递归调用子View的对应方法。比如performMeasure会调用DecorView的measure(),然后DecorView再调用子View的measure(),一层层传下去。
关键点:performTraversals并不是只执行一次。每次调用requestLayout()或invalidate(),都会触发新一轮的遍历。但requestLayout()只会触发measure和layout,不会触发draw;而invalidate()只会触发draw。
为什么会这样?因为requestLayout()设置了mLayoutRequested = true,而invalidate()设置了mDirty区域。performTraversals里会根据这些标志位决定执行哪些阶段。
3.5 窗口添加的完整链路
最后,我们把整个流程串起来:
| 步骤 | 触发者 | 做了什么 |
|---|---|---|
| 1 | Activity.attach() | 创建PhoneWindow,设置WindowManager |
| 2 | Activity.setContentView() | 创建DecorView,inflate布局到content区域 |
| 3 | Activity.onResume() | 触发WindowManager.addView() |
| 4 | WindowManagerGlobal.addView() | 创建ViewRootImpl,调用root.setView() |
| 5 | ViewRootImpl.setView() | 发起requestLayout(),触发performTraversals() |
| 6 | ViewRootImpl.performTraversals() | 执行measure → layout → draw |
你看,从Activity到屏幕上的像素,中间经过了这么多层。但每一层各司其职,配合得相当默契。
我的建议:如果你遇到UI显示异常的问题,比如布局没出来、位置不对、闪烁等,可以沿着这条链路排查。先看DecorView有没有创建,再看ViewRootImpl有没有触发遍历,最后看measure/layout的结果对不对。我排查过不下20个UI bug,90%都能在这条链路上找到根因。
嗯,窗口管理这块内容确实不少。但搞懂了Window、DecorView、ViewRootImpl这三者的关系,以及performTraversals的流程,你就掌握了Android UI系统的核心脉络。下一章我们会深入View的测量过程,看看MeasureSpec到底是怎么传递的。