在 Android 开发中,我们通常从 Activity 和布局 XML 开始写页面:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

代码看起来只是把一个布局设置给 Activity,但 Activity 自己并不负责绘制界面。

在它背后,还有 WindowPhoneWindowDecorViewWindowManagerViewRootImpl 等组件共同工作。

本文只讨论 Android 的界面组织与窗口接入体系。至于 Canvas、Skia、Surface 和 SurfaceFlinger 如何把界面渲染到屏幕,会在第二篇中继续说明。


一、先看整体关系

flowchart TD A[Activity] -->|持有| B[PhoneWindow] B -->|创建并管理| C[DecorView] C -->|包含| D[android.R.id.content] D -->|承载| E[setContentView 设置的页面 View 树] F[WindowManager] --> G[WindowManagerImpl] G --> H[WindowManagerGlobal] H -->|创建| I[ViewRootImpl] I -->|管理| C I -->|Binder IPC| J[WindowManagerService]

可以先记住:

Activity 负责生命周期和页面逻辑,Window 负责窗口能力,DecorView 是窗口的根 View,ViewRootImpl 负责让整棵 View 树接入系统窗口并运行起来。


二、Activity:页面控制器,不是绘制容器

Activity 是 Android 四大组件之一,它主要负责:

  • 管理生命周期;

  • 接收和处理 Intent;

  • 保存、恢复页面状态;

  • 承载页面业务逻辑;

  • 协调 Fragment、ViewModel 和 View;

  • 管理返回事件;

  • 持有并控制当前 Window。

Activity 本身不是 View,也不会直接参与 measurelayoutdraw

下面的代码:

setContentView(R.layout.activity_main)

并不是把布局直接添加到 Activity,而是继续交给 Activity 持有的 Window。

sequenceDiagram participant Activity participant Window participant PhoneWindow participant Content as 内容容器 Activity->>Window: setContentView(layoutResId) Window->>PhoneWindow: 调用具体实现 PhoneWindow->>PhoneWindow: installDecor() PhoneWindow->>Content: 解析布局并加入内容区域

因此,更准确的说法是:

Activity 管理一个页面,但真正承载页面 View 树的是 Window。


三、Window:Activity 与 View 之间的窗口抽象

Window 是一个抽象类,用来描述应用窗口应具备的能力。

Activity 默认使用的具体实现通常是:

PhoneWindow

Window 负责的内容包括:

  • 管理 DecorView;

  • 设置页面内容;

  • 管理窗口背景;

  • 设置状态栏和导航栏相关属性;

  • 管理软键盘模式;

  • 管理 WindowInsets;

  • 管理窗口动画;

  • 管理窗口特性;

  • 协助完成按键和触摸事件分发。

例如:

window.statusBarColor = Color.BLACK

window.setSoftInputMode(
    WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)

这些操作针对的是整个窗口,而不是某一个普通 View。

Activity 与 Window 的关系如下:

flowchart LR A[Activity] -->|持有并管理| B[Window] B -->|默认实现| C[PhoneWindow]

严格来说:

Activity 不等于 Window,只是一个 Activity 通常持有一个主 Window。

Dialog 通常也有自己的 Window,所以 Dialog 并不是简单地把布局添加到 Activity 的内容区域中。

flowchart TD A[Activity] --> B[Activity PhoneWindow] B --> C[Activity DecorView] D[Dialog] --> E[Dialog PhoneWindow] E --> F[Dialog DecorView]

四、DecorView:窗口中的根 View

PhoneWindow 内部会创建一个 DecorView

DecorView 是一个 ViewGroup,也是当前窗口 View 树的根节点。

页面结构可以简化为:

flowchart TD A[PhoneWindow] --> B[DecorView] B --> C[窗口装饰与系统栏相关区域] B --> D[内容容器 android.R.id.content] D --> E[业务页面布局] E --> F[TextView] E --> G[ImageView] E --> H[RecyclerView] E --> I[其他 View]

执行:

setContentView(R.layout.activity_main)

并不会让 activity_main.xml 对应的 View 成为窗口根节点,而是把它加入 android.R.id.content

可以分别获取根 View 和内容区域:

val decorView = window.decorView

val contentParent =
    findViewById<ViewGroup>(android.R.id.content)

两者关系是:

flowchart TD A[window.decorView] --> B[android.R.id.content] B --> C[setContentView 设置的布局]

五、setContentView 到底做了什么?

Activity.setContentView() 最终会调用 Window 的同名方法,而 Activity 默认使用的是 PhoneWindow。

PhoneWindow 的核心处理逻辑可以概括为:

flowchart TD A[Activity.setContentView] --> B[PhoneWindow.setContentView] B --> C{DecorView 是否已安装} C -->|否| D[installDecor] D --> E[创建 DecorView] E --> F[根据主题加载窗口基础布局] C -->|是| G[复用现有内容容器] F --> H[找到 mContentParent] G --> H H --> I[LayoutInflater 解析业务布局] I --> J[将 View 加入内容容器] J --> K[通知内容发生变化]

可以用下面的伪代码理解:

if (contentParent == null) {
    installDecor()
}

contentParent.removeAllViews()
layoutInflater.inflate(layoutResId, contentParent)

真实源码还会处理窗口特性、内容转场、Insets 和回调等逻辑,但主线就是:

创建 DecorView,找到内容容器,再把业务页面加入其中。


六、WindowManager:应用侧的窗口操作入口

Activity 页面准备显示时,需要把 DecorView 交给窗口系统。

应用侧通过 WindowManager 操作顶层窗口:

windowManager.addView(view, layoutParams)
windowManager.updateViewLayout(view, layoutParams)
windowManager.removeView(view)

WindowManager 是接口,应用中通常拿到的是 WindowManagerImpl

调用链可以简化为:

flowchart LR A[WindowManager] --> B[WindowManagerImpl] B --> C[WindowManagerGlobal] C --> D[ViewRootImpl]

这里需要注意:

WindowManager 并不是 Window 的容器,也不是系统侧真正管理全部窗口的服务。

它只是应用进程中的调用入口。真正负责全局窗口管理的是运行在 system_server 中的 WindowManagerService


七、为什么还需要 WindowManagerService?

Android 屏幕上可能同时存在:

  • 多个应用窗口;

  • Dialog;

  • 输入法窗口;

  • 状态栏;

  • 导航栏;

  • 通知面板;

  • 锁屏窗口;

  • 画中画窗口;

  • 分屏窗口;

  • 悬浮窗。

这些窗口来自不同进程。任何一个 Activity 都没有能力看到并管理全部窗口。

因此,系统需要一个全局、可信的窗口管理中心:

WindowManagerService,简称 WMS
flowchart TD A[应用 A 窗口] --> E[WindowManagerService] B[应用 B 窗口] --> E C[输入法窗口] --> E D[SystemUI 窗口] --> E E --> F[计算窗口位置和大小] E --> G[管理窗口层级] E --> H[管理焦点和输入目标] E --> I[校验 Token 与窗口权限]

WMS 主要负责:

  • 管理系统中的全部窗口;

  • 计算窗口位置、尺寸和可见区域;

  • 管理窗口 Z-Order;

  • 决定哪个窗口拥有焦点;

  • 校验 WindowToken;

  • 校验系统窗口和悬浮窗权限;

  • 管理多窗口、分屏和画中画;

  • 管理屏幕旋转和窗口转场;

  • 向图形合成系统提供窗口元数据。

应用侧 WindowManager 与系统侧 WMS 的关系是:

flowchart LR A[应用代码] --> B[WindowManagerImpl] B --> C[WindowManagerGlobal] C --> D[ViewRootImpl] D -->|Binder IPC| E[WindowManagerService]

八、ViewRootImpl:View 树与系统窗口之间的枢纽

ViewRootImpl 名字中虽然有 View,但它并不是 View,也不是 ViewGroup。

它不会作为节点出现在 View 树中。

ViewRootImpl 位于以下两部分之间:

  • 上层的 DecorView 和页面 View 树;

  • 下层的窗口、输入和渲染系统。

flowchart TD A[ViewRootImpl] -->|管理根 View| B[DecorView] B --> C[页面 View 树] A -->|Binder IPC| D[WindowManagerService] A -->|接收并分发| E[输入事件] A -->|调度| F[measure layout draw] A -->|管理窗口渲染目标| G[Surface 相关资源]

ViewRootImpl 的核心职责包括:

  • 保存并管理根 View;

  • 将 DecorView 接入窗口系统;

  • 与 WMS 通信;

  • 发起 View 的测量、布局和绘制;

  • 处理窗口尺寸和配置变化;

  • 处理 WindowInsets;

  • 接收系统输入事件;

  • 将输入事件分发给 View 树;

  • 调度下一帧遍历;

  • 管理窗口使用的渲染资源。

可以把它理解为:

DecorView 是 View 树的根节点,ViewRootImpl 是整棵 View 树的总调度器。


九、ViewRootImpl 是什么时候创建的?

setContentView() 主要负责创建和填充 View 树,但这时页面不一定已经真正接入窗口系统。

当 WindowManagerGlobal 执行 addView() 时,会创建 ViewRootImpl,并把 DecorView 交给它。

sequenceDiagram participant Activity participant Window as PhoneWindow participant WMG as WindowManagerGlobal participant VRI as ViewRootImpl participant WMS as WindowManagerService Activity->>Window: setContentView Window->>Window: 创建 DecorView 与业务 View 树 Activity->>WMG: addView(DecorView) WMG->>VRI: 创建 ViewRootImpl WMG->>VRI: setView(DecorView) VRI->>WMS: 请求添加窗口

这也解释了为什么:

  • setContentView() 不等于页面已经显示;

  • View 只有接入 ViewRootImpl 后,才真正附着到窗口;

  • View.isAttachedToWindow() 与是否执行过 setContentView() 不是同一个概念。


十、ViewRootImpl 如何驱动 View 三大流程?

View 的核心工作流程是:

flowchart LR A[measure] --> B[layout] B --> C[draw]

这三个阶段通常由 ViewRootImpl 的遍历流程发起。

flowchart TD A[系统请求新一帧] --> B[Choreographer 调度] B --> C[ViewRootImpl 执行遍历] C --> D[performMeasure] D --> E[View.measure] C --> F[performLayout] F --> G[View.layout] C --> H[performDraw] H --> I[View.draw]

measure:确定需要多大

父 View 将测量约束传给子 View,最终得到每个 View 的测量宽高。

layout:确定放在哪里

父 View 根据测量结果确定每个子 View 的位置。

draw:生成页面绘制内容

View 按照层级绘制背景、内容、子 View 和前景。

需要强调:

ViewRootImpl 负责发起和协调绘制,但具体页面内容由 View 自己定义。


十一、输入事件为什么也要经过 ViewRootImpl?

触摸、按键等事件最初来自系统输入服务,不是直接由 Activity 创建。

系统确定输入目标窗口后,事件会进入应用进程,再由 ViewRootImpl 送入 View 树。

flowchart TD A[InputManagerService] --> B[应用输入通道] B --> C[ViewRootImpl] C --> D[DecorView] D --> E[Activity.dispatchTouchEvent] E --> F[Window.superDispatchTouchEvent] F --> G[ViewGroup / View 事件分发]

因此,ViewRootImpl 同时连接着:

  • 窗口系统;

  • View 系统;

  • 输入系统;

  • 渲染系统。

这也是它在 Android UI 框架中如此关键的原因。


十二、Activity、Window、ViewRootImpl 分别管理什么?

组件

核心职责

Activity

生命周期、页面状态和业务逻辑

Window

管理窗口属性并承载 DecorView

PhoneWindow

Window 的主要实现

DecorView

窗口中的根 View

普通 View

测量、布局、绘制和交互

WindowManager

应用侧窗口操作接口

WindowManagerGlobal

管理当前进程的顶层 View 与 ViewRootImpl

ViewRootImpl

将 View 树接入窗口系统并驱动遍历

WindowManagerService

在系统侧统一管理全部窗口


十三、常见误区

误区一:Activity 就是页面 View

不准确。

Activity 是组件和控制器,页面内容由 View 树表示。

误区二:Activity 直接持有业务布局

不准确。

Activity 持有 Window,Window 的 DecorView 再包含业务布局。

flowchart LR A[Activity] --> B[PhoneWindow] B --> C[DecorView] C --> D[业务页面 View]

误区三:ViewRootImpl 是根 View

不准确。

DecorView 才是根 View。ViewRootImpl 只是根 View 的管理者。

误区四:setContentView 后页面已经显示

不准确。

setContentView() 主要完成 View 树的创建和装配,之后还要添加窗口、创建 ViewRootImpl,并执行测量、布局和绘制。

误区五:WindowManagerService 负责绘制 View

不准确。

WMS 管理窗口关系,但不理解 TextView、Button 或 RecyclerView 的绘制逻辑。


十四、最终关系图

flowchart TD subgraph App[应用进程] A[Activity] B[PhoneWindow] C[DecorView] D[业务 View 树] E[WindowManagerImpl] F[WindowManagerGlobal] G[ViewRootImpl] A --> B B --> C C --> D E --> F F --> G G -->|管理| C G -->|驱动| H[measure layout draw] H --> D end subgraph System[system_server] I[WindowManagerService] end G -->|Binder IPC| I

总结

Android 界面体系可以按下面的顺序理解:

flowchart LR A[Activity] --> B[PhoneWindow] B --> C[DecorView] C --> D[页面 View 树] E[WindowManagerGlobal] --> F[ViewRootImpl] F -->|管理并驱动| C F -->|通信| G[WindowManagerService]

各组件的核心分工是:

Activity 管页面逻辑,Window 管窗口能力,DecorView 是根 View,ViewRootImpl 驱动 View 树并连接系统窗口,WindowManagerService 负责全局窗口管理。

到这里,我们只是让 View 树成功接入了窗口系统。

接下来还需要回答另一个问题:

ViewRootImpl 发起 draw() 后,Canvas、Skia、Surface 和 SurfaceFlinger 又是怎样把这一帧真正显示到屏幕上的?

这部分内容请继续阅读:

Android 图形系统:Canvas、Skia、Surface 与 SurfaceFlinger


参考资料

  1. Android Developers:WindowManager API

  2. AOSP 源码:PhoneWindow.java

  3. AOSP 源码:WindowManagerImpl.java

  4. AOSP 源码:WindowManagerGlobal.java

  5. AOSP 源码:ViewRootImpl.java

  6. AOSP:SurfaceFlinger 与 WindowManager

说明:PhoneWindow、ViewRootImpl 等属于 Android Framework 内部实现,具体字段和调用细节可能随 Android 版本调整。本文重点描述长期稳定的职责边界和主调用链。