Android界面体系:Activity、Window、View与ViewRootImpl
在 Android 开发中,我们通常从 Activity 和布局 XML 开始写页面:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
代码看起来只是把一个布局设置给 Activity,但 Activity 自己并不负责绘制界面。
在它背后,还有 Window、PhoneWindow、DecorView、WindowManager 和 ViewRootImpl 等组件共同工作。
本文只讨论 Android 的界面组织与窗口接入体系。至于 Canvas、Skia、Surface 和 SurfaceFlinger 如何把界面渲染到屏幕,会在第二篇中继续说明。
一、先看整体关系
可以先记住:
Activity 负责生命周期和页面逻辑,Window 负责窗口能力,DecorView 是窗口的根 View,ViewRootImpl 负责让整棵 View 树接入系统窗口并运行起来。
二、Activity:页面控制器,不是绘制容器
Activity 是 Android 四大组件之一,它主要负责:
管理生命周期;
接收和处理 Intent;
保存、恢复页面状态;
承载页面业务逻辑;
协调 Fragment、ViewModel 和 View;
管理返回事件;
持有并控制当前 Window。
Activity 本身不是 View,也不会直接参与 measure、layout 和 draw。
下面的代码:
setContentView(R.layout.activity_main)
并不是把布局直接添加到 Activity,而是继续交给 Activity 持有的 Window。
因此,更准确的说法是:
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 的关系如下:
严格来说:
Activity 不等于 Window,只是一个 Activity 通常持有一个主 Window。
Dialog 通常也有自己的 Window,所以 Dialog 并不是简单地把布局添加到 Activity 的内容区域中。
四、DecorView:窗口中的根 View
PhoneWindow 内部会创建一个 DecorView。
DecorView 是一个 ViewGroup,也是当前窗口 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)
两者关系是:
五、setContentView 到底做了什么?
Activity.setContentView() 最终会调用 Window 的同名方法,而 Activity 默认使用的是 PhoneWindow。
PhoneWindow 的核心处理逻辑可以概括为:
可以用下面的伪代码理解:
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。
调用链可以简化为:
这里需要注意:
WindowManager 并不是 Window 的容器,也不是系统侧真正管理全部窗口的服务。
它只是应用进程中的调用入口。真正负责全局窗口管理的是运行在 system_server 中的 WindowManagerService。
七、为什么还需要 WindowManagerService?
Android 屏幕上可能同时存在:
多个应用窗口;
Dialog;
输入法窗口;
状态栏;
导航栏;
通知面板;
锁屏窗口;
画中画窗口;
分屏窗口;
悬浮窗。
这些窗口来自不同进程。任何一个 Activity 都没有能力看到并管理全部窗口。
因此,系统需要一个全局、可信的窗口管理中心:
WindowManagerService,简称 WMS
WMS 主要负责:
管理系统中的全部窗口;
计算窗口位置、尺寸和可见区域;
管理窗口 Z-Order;
决定哪个窗口拥有焦点;
校验 WindowToken;
校验系统窗口和悬浮窗权限;
管理多窗口、分屏和画中画;
管理屏幕旋转和窗口转场;
向图形合成系统提供窗口元数据。
应用侧 WindowManager 与系统侧 WMS 的关系是:
八、ViewRootImpl:View 树与系统窗口之间的枢纽
ViewRootImpl 名字中虽然有 View,但它并不是 View,也不是 ViewGroup。
它不会作为节点出现在 View 树中。
ViewRootImpl 位于以下两部分之间:
上层的 DecorView 和页面 View 树;
下层的窗口、输入和渲染系统。
ViewRootImpl 的核心职责包括:
保存并管理根 View;
将 DecorView 接入窗口系统;
与 WMS 通信;
发起 View 的测量、布局和绘制;
处理窗口尺寸和配置变化;
处理 WindowInsets;
接收系统输入事件;
将输入事件分发给 View 树;
调度下一帧遍历;
管理窗口使用的渲染资源。
可以把它理解为:
DecorView 是 View 树的根节点,ViewRootImpl 是整棵 View 树的总调度器。
九、ViewRootImpl 是什么时候创建的?
setContentView() 主要负责创建和填充 View 树,但这时页面不一定已经真正接入窗口系统。
当 WindowManagerGlobal 执行 addView() 时,会创建 ViewRootImpl,并把 DecorView 交给它。
这也解释了为什么:
setContentView()不等于页面已经显示;View 只有接入 ViewRootImpl 后,才真正附着到窗口;
View.isAttachedToWindow()与是否执行过setContentView()不是同一个概念。
十、ViewRootImpl 如何驱动 View 三大流程?
View 的核心工作流程是:
这三个阶段通常由 ViewRootImpl 的遍历流程发起。
measure:确定需要多大
父 View 将测量约束传给子 View,最终得到每个 View 的测量宽高。
layout:确定放在哪里
父 View 根据测量结果确定每个子 View 的位置。
draw:生成页面绘制内容
View 按照层级绘制背景、内容、子 View 和前景。
需要强调:
ViewRootImpl 负责发起和协调绘制,但具体页面内容由 View 自己定义。
十一、输入事件为什么也要经过 ViewRootImpl?
触摸、按键等事件最初来自系统输入服务,不是直接由 Activity 创建。
系统确定输入目标窗口后,事件会进入应用进程,再由 ViewRootImpl 送入 View 树。
因此,ViewRootImpl 同时连接着:
窗口系统;
View 系统;
输入系统;
渲染系统。
这也是它在 Android UI 框架中如此关键的原因。
十二、Activity、Window、ViewRootImpl 分别管理什么?
十三、常见误区
误区一:Activity 就是页面 View
不准确。
Activity 是组件和控制器,页面内容由 View 树表示。
误区二:Activity 直接持有业务布局
不准确。
Activity 持有 Window,Window 的 DecorView 再包含业务布局。
误区三:ViewRootImpl 是根 View
不准确。
DecorView 才是根 View。ViewRootImpl 只是根 View 的管理者。
误区四:setContentView 后页面已经显示
不准确。
setContentView() 主要完成 View 树的创建和装配,之后还要添加窗口、创建 ViewRootImpl,并执行测量、布局和绘制。
误区五:WindowManagerService 负责绘制 View
不准确。
WMS 管理窗口关系,但不理解 TextView、Button 或 RecyclerView 的绘制逻辑。
十四、最终关系图
总结
Android 界面体系可以按下面的顺序理解:
各组件的核心分工是:
Activity 管页面逻辑,Window 管窗口能力,DecorView 是根 View,ViewRootImpl 驱动 View 树并连接系统窗口,WindowManagerService 负责全局窗口管理。
到这里,我们只是让 View 树成功接入了窗口系统。
接下来还需要回答另一个问题:
ViewRootImpl 发起
draw()后,Canvas、Skia、Surface 和 SurfaceFlinger 又是怎样把这一帧真正显示到屏幕上的?
这部分内容请继续阅读:
Android 图形系统:Canvas、Skia、Surface 与 SurfaceFlinger
参考资料
说明:PhoneWindow、ViewRootImpl 等属于 Android Framework 内部实现,具体字段和调用细节可能随 Android 版本调整。本文重点描述长期稳定的职责边界和主调用链。
Android界面体系:Activity、Window、View与ViewRootImpl
https://lautung.com/archives/wp9MdNIu
评论