一、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 下面。

我画了一张图,帮你理清它们的关系:

Window 与 DecorView 层级关系 Window (接口) PhoneWindow (实现类) DecorView (根 ViewGroup) 子 View / ViewGroup (你的布局)

2.2 PhoneWindow 的核心职责

  • 管理 DecorView:创建、添加、移除 DecorView
  • 处理窗口属性:比如标题栏、状态栏、全屏模式等
  • 分发触摸事件:将事件从 Window 传递到 DecorView
  • 管理回调:比如 onWindowFocusChanged 等

个人经验:我曾经在做一个沉浸式状态栏的需求时,发现某些机型上状态栏颜色不生效。查了半天,最后发现是 PhoneWindow 的 flags 设置顺序有问题。先 setFlags 再 setContentView,顺序反了就会失效。这个坑我踩过,你注意一下。

三、DecorView 的创建与加载

DecorView 是什么时候创建的?

答案是:Activity 启动的时候。

具体流程是这样的:

  1. ActivityThread 启动 Activity
  2. 调用 Activity.attach() 方法
  3. 在 attach() 中创建 PhoneWindow
  4. PhoneWindow 的构造函数里,并没有立即创建 DecorView
  5. 真正创建 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 的大小的。


公众号:蓝海资料掘金营,微信deep3321