一、setContentView() 的核心使命:UI与数据的解耦
setContentView() 方法的本质是将一个XML布局文件解析成一个视图树(View Tree),并将其绑定到 Activity 的 Window 上。这个过程使得 UI 的设计与业务逻辑(Activity)分离,遵循了“关注点分离”的设计原则。
二、setContentView() 的执行流程
setContentView() 的调用触发了一系列精密的底层操作,最终将你的布局呈现在屏幕上。
Window的初始化:当Activity启动时,系统会自动创建一个PhoneWindow实例,它作为Activity的唯一Window。PhoneWindow是Window抽象类的唯一实现。DecorView的创建:PhoneWindow在内部创建一个DecorView实例。DecorView是一个特殊的ViewGroup,它作为整个Window的根视图。它包含了应用界面的基本框架,如状态栏、标题栏区域,以及一个用于容纳用户自定义内容的FrameLayout(通常ID为content)。LayoutInflater的解析:setContentView()内部会使用LayoutInflater。LayoutInflater负责将 XML 布局文件解析成 Java 对象,并构建一个完整的View树。View树的添加:PhoneWindow随后会将新生成的View树添加到DecorView的content区域中。此时,你的布局已经成功地成为了Activity窗口的一部分。
三、关键问题解答
1. 为什么不能在 onCreate 中获取 View 的宽高?
setContentView() 只是将布局加载到 Window 中,但此时 Window 还没有被添加到 WindowManager,因此 View 树尚未进行测量(onMeasure)和布局(onLayout) 。这两个步骤是确定 View 尺寸的唯一方法。它们通常发生在 Activity 的 onResume() 之后,由 ViewRootImpl 驱动。
2. 如何正确获取 View 的宽高?
开发者需要在 View 树完成测量和布局后,才能安全地获取宽高。
View.post():将一个Runnable放入主线程消息队列,确保代码在measure/layout之后执行。ViewTreeObserver:通过addOnGlobalLayoutListener()监听布局变化,获取到最新的宽高。
3. DecorView 的作用是什么?
DecorView 是 Activity 窗口的根视图,它的主要作用是:
- 统一管理系统 UI:为状态栏、标题栏等预留位置,使得应用界面与系统 UI 能够和谐共存。
- 提供内容容器:提供一个
FrameLayout作为用户布局的容器,实现了系统 UI 和应用 UI 的解耦。
4. 多次调用 setContentView() 会怎样?
setContentView() 会先移除 DecorView 的内容区域中原有的所有 View,然后添加新的布局。这不仅会造成性能浪费,还可能导致 UI 状态的丢失。因此,应避免在 Activity 的生命周期中多次调用。