一、Window 到底是什么?
说实话,我刚入行那会儿,对 Window 这个概念也是一头雾水。
很多人以为 Window 就是手机屏幕上那个方方正正的界面。其实不对。Window 是一个抽象概念,它代表了一块「可绘制区域」的承载者。你可以把它理解成——画板。
Activity 是画师,View 是画的内容,而 Window 就是那块画板。没有画板,你画哪儿去?
核心定义:Window 是 Android 中用于承载 View 的顶级容器,它管理着 View 的层级、布局、触摸事件分发等核心职责。
每个 Activity 都对应一个 Window。但 Window 本身是个接口,真正的实现类是 PhoneWindow。嗯,这里要注意:PhoneWindow 这个名字虽然带个 Phone,但它跟打电话没关系,只是历史遗留命名。
二、PhoneWindow 的实现细节
PhoneWindow 是 Window 的唯一实现类。我翻过它的源码,说实话,代码量不小,但核心逻辑其实很清晰。
2.1 PhoneWindow 的结构
PhoneWindow 内部维护了一个 ViewGroup,叫做 DecorView。DecorView 是整个窗口的根 View。所有的 View 最终都挂载到 DecorView 下面。
我画了一张图,帮你理清它们的关系:
2.2 PhoneWindow 的核心职责
- 管理 DecorView:创建、添加、移除 DecorView
- 处理窗口属性:比如标题栏、状态栏、全屏模式等
- 分发触摸事件:将事件从 Window 传递到 DecorView
- 管理回调:比如 onWindowFocusChanged 等
个人经验:我曾经在做一个沉浸式状态栏的需求时,发现某些机型上状态栏颜色不生效。查了半天,最后发现是 PhoneWindow 的 flags 设置顺序有问题。先 setFlags 再 setContentView,顺序反了就会失效。这个坑我踩过,你注意一下。
三、DecorView 的创建与加载
DecorView 是什么时候创建的?
答案是:Activity 启动的时候。
具体流程是这样的:
- ActivityThread 启动 Activity
- 调用 Activity.attach() 方法
- 在 attach() 中创建 PhoneWindow
- PhoneWindow 的构造函数里,并没有立即创建 DecorView
- 真正创建 DecorView 是在 setContentView 的时候
我翻一下源码给你看:
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
// 如果 DecorView 还没创建,就创建它
if (mContentParent == null) {
installDecor(); // 这里创建 DecorView
} else {
mContentParent.removeAllViews();
}
// 把布局文件 inflate 到 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
看到了吗?installDecor() 就是关键。它里面做了两件事:
- 创建 DecorView 实例
- 根据主题样式,往 DecorView 里填充一些系统布局(比如标题栏、内容区域)
注意:DecorView 其实是一个 FrameLayout。它内部包含了一个 LinearLayout,这个 LinearLayout 又分为标题区域和内容区域。内容区域就是 mContentParent,也就是我们 setContentView 时布局要放进去的地方。
四、setContentView 背后的故事
setContentView 这个方法,我们天天写。但你有没有想过,它到底干了什么?
我把它拆解成三步:
| 步骤 | 做了什么 | 关键方法 |
|---|---|---|
| 第一步 | 确保 DecorView 已创建 | installDecor() |
| 第二步 | 将布局文件解析成 View 树 | LayoutInflater.inflate() |
| 第三步 | 把 View 树添加到 mContentParent | mContentParent.addView() |
说白了,setContentView 就是帮你把 XML 布局文件变成一棵 View 树,然后挂到 DecorView 的内容区域里。
但这里有个细节:
你写的 Activity 继承自 AppCompatActivity 还是 Activity?
如果是 AppCompatActivity,那 setContentView 会被 AppCompatDelegate 拦截,做一层兼容处理。比如把 Material 主题的控件替换成 AppCompat 版本。这个我后面会细讲,今天先提一嘴。
避坑指南:我曾经遇到过一个 bug,在 setContentView 之前调用了 getWindow().addFlags() 设置全屏,结果在某些 Android 版本上界面错乱了。原因是 DecorView 还没创建,flags 设置没有生效。正确的做法是在 setContentView 之后设置窗口属性,或者在 Activity 的 onCreate 之前通过主题设置。
五、总结一下
这一章我们讲了三个核心概念:
- Window:抽象概念,代表画板
- PhoneWindow:Window 的唯一实现,负责管理 DecorView
- DecorView:根 View,所有 View 的起点
setContentView 的背后,其实就是把我们的布局挂到 DecorView 上。这个过程看似简单,但涉及到的细节很多。比如 DecorView 的创建时机、主题的影响、兼容库的处理等等。
我个人建议你去看一下 PhoneWindow 的源码,尤其是 installDecor() 和 generateLayout() 这两个方法。看完之后,你对整个 View 体系的认知会上一个台阶。
嗯,这一章就到这里。下一章我们会深入 DecorView 的测量过程,看看 Android 是怎么计算每个 View 的大小的。