当前位置: 首页 > news >正文

Android setContentView源码与原理分析

目录

  • 一、概述
  • 二、基本用法回顾
  • 三、源码分析 (基于 Android API 30+)
    • 1. Activity.setContentView(int layoutResID)
    • 2. PhoneWindow.setContentView(int layoutResID)
    • 3. PhoneWindow.installDecor()
    • 4. PhoneWindow.generateLayout(DecorView decor)
    • 5. 看看 screen_simple.xml 布局模板
  • 三、核心成员解析
    • 1.核心类
    • 2 核心概念
    • 3.DecorView 结构详解
    • 4.LayoutInflater 的角色
    • 5. 与 WindowManager 的关系
  • 四、流程图
  • 五、常见问题与扩展
    • 1. 为什么必须在 `super.onCreate()` 之后调用 `setContentView()`?
    • 2. `setContentView()` 调用了多次会发生什么?
    • 3. 如何获取 DecorView?有什么用途?
  • 六、总结:`setContentView` 原理精要

好的,我们来深入分析一下 Android 中 setContentView 方法的源码和原理。这是一个理解 Android 视图系统如何工作的绝佳入口。

一、概述

setContentViewActivity 中一个至关重要的方法,它负责将我们编写的 XML 布局文件与 Activity 关联起来,从而在屏幕上显示用户界面。它的核心工作是构建一个由 DecorViewContentParent 组成的视图树,并将我们的自定义布局嵌入其中。


二、基本用法回顾

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); // 关键调用}
}

作用activity_main.xml 布局加载并显示在屏幕上

但它的背后涉及了多个系统组件的协作:ActivityPhoneWindowWindowDecorViewLayoutInflaterViewRootImpl

三、源码分析 (基于 Android API 30+)

我们将沿着调用链,从 Activity 深入到 PhoneWindow

1. Activity.setContentView(int layoutResID)

这是最常用的入口。

// Activity.java
public void setContentView(@LayoutRes int layoutResID) {// 关键点 1:getWindow() 返回的是 PhoneWindow 实例getWindow().setContentView(layoutResID);// 关键点 2:初始化 ActionBar(如果存在)initWindowDecorActionBar();
}// 返回的是 PhoneWindow 对象
public Window getWindow() {return mWindow;
}

要点:

  • mWindow 是在 Activity.attach() 方法中被初始化的,它是一个 PhoneWindow 实例。PhoneWindowWindow 抽象类的唯一实现。
  • Activity 将具体的视图设置工作委托给了 Window

2. PhoneWindow.setContentView(int layoutResID)

现在我们进入核心类 PhoneWindow

// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {// 关键点 3:installDecor() - 初始化 DecorView 和 ContentParentif (mContentParent == null) {installDecor();} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// 如果已经设置过内容且没有转场动画,就移除旧视图mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// 处理场景转场动画...final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext());transitionTo(newScene);} else {// 关键点 4:将我们的布局 inflate 到 mContentParent 中mLayoutInflater.inflate(layoutResID, mContentParent);}mContentParent.requestApplyInsets();// 关键点 5:回调通知 Activity 内容视图已改变final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}mContentParentExplicitlySet = true;
}

要点:

  • mContentParentViewGroup 类型,它是我们自定义布局的直接父容器。
  • 如果 mContentParent 为空,说明是第一次调用 setContentView,需要调用 installDecor() 来创建整个窗口的视图结构。
  • 最终,通过 LayoutInflater.inflate() 将我们的布局文件解析成 View 对象树,并添加到 mContentParent 中。
  • 完成后,会通过 Callback(也就是 Activity)的 onContentChanged() 方法通知 Activity。

3. PhoneWindow.installDecor()

这是构建窗口根视图结构的地方。

// PhoneWindow.java
private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {// 关键点 6:生成 DecorViewmDecor = generateDecor(-1);mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {// 关键点 7:根据窗口风格(如有无ActionBar)生成 mContentParentmContentParent = generateLayout(mDecor);// 初始化其他UI部件,如标题栏、ActionBar等// ... (代码省略)}
}

要点:

  • generateDecor() 会创建一个 DecorView 对象,它是整个 Activity 窗口的根视图(一个 FrameLayout)。
  • generateLayout(mDecor) 是重中之重,它负责选择并加载具体的窗口布局模板,并从中找到 mContentParent

4. PhoneWindow.generateLayout(DecorView decor)

这个方法决定了窗口的整体外观。

// PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {// 应用系统主题样式...TypedArray a = getWindowStyle();// ... (读取各种窗口属性,如 FEATURE_NO_TITLE, FEATURE_ACTION_BAR 等)// 关键点 8:根据请求的 Features 和样式,选择一个预定义的布局文件int layoutResource;int features = getLocalFeatures();// 一系列 if-else 逻辑,根据 features 选择不同的布局if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {layoutResource = R.layout.screen_swipe_dismiss;} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {layoutResource = R.layout.screen_simple_overlay_action_mode;} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {// 有标题栏的布局if (mIsFloating) {layoutResource = R.layout.dialog_title;} else {layoutResource = R.layout.screen_title; // 例如这个}} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {layoutResource = R.layout.screen_action_bar;} else {// 关键点 9:最常用的,简单的全屏布局layoutResource = R.layout.screen_simple; // <--- 就是这个!}// 关键点 10:将选中的布局 inflate 到 DecorView 中mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 关键点 11:找到 id 为 "content" 的 ViewGroup,它就是 mContentParentViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);if (contentParent == null) {throw new RuntimeException("Window couldn't find content container view");}// ... (其他样式设置)return contentParent;
}

要点:

  • 系统根据 requestWindowFeature() 设置的 Window 特性(如 FEATURE_NO_TITLE),选择一个内置的布局模板。最常见的模板是 R.layout.screen_simple
  • mDecor.onResourcesLoaded() 会将这个选中的模板(一个 XML 布局)加载并作为 DecorView 的直接子视图。
  • 通过 findViewById(com.android.internal.R.id.content) 找到模板中那个准备承载我们自定义内容的 FrameLayout,它就是 mContentParent

5. 看看 screen_simple.xml 布局模板

这个文件位于 frameworks/base/core/res/res/layout/screen_simple.xml,它清晰地展示了视图层级。

<!-- screen_simple.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:orientation="vertical"><!-- 这个是可选的,用于处理状态栏背景等 --><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><!-- 关键点 12:这就是我们的 mContentParent! --><FrameLayoutandroid:id="@android:id/content"android:layout_width="match_parent"android:layout_height="match_parent"android:foregroundInsidePadding="false"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

最终层级结构:

DecorView (FrameLayout)
└── LinearLayout (来自 screen_simple.xml)├── ViewStub (可选,用于ActionMode)└── FrameLayout (id = android.R.id.content) <-- mContentParent└── Your Custom Layout (通过 inflate(layoutResID, mContentParent) 添加)

三、核心成员解析

1.核心类

类名作用
Activity提供 setContentView() 接口
Window抽象类,定义窗口行为
PhoneWindowWindow 的唯一实现类,管理 DecorView
DecorView窗口的根视图,继承自 FrameLayout,包含系统 UI 和用户内容
AppCompatDelegateImpl兼容库实现,处理 Material Design、夜间模式等

2 核心概念

  1. 委托模式Activity 并不直接处理视图,而是将工作委托给 Window(具体实现是 PhoneWindow)。
  2. 构建窗口根视图PhoneWindow 在第一次调用 setContentView 时,会创建 DecorView 作为根视图。DecorView 是一个 FrameLayout
  3. 应用窗口模板:系统根据主题和 requestWindowFeature 的设置,选择一个预定义的 XML 布局模板(如 screen_simple),并将其加载到 DecorView 中。
  4. 定位内容区域:在这个模板中,有一个 ID 为 android.R.id.contentFrameLayout,这就是 mContentParent,它是我们自定义布局的直接容器。
  5. 嵌入用户布局:最后,通过 LayoutInflater 将开发者提供的布局文件解析成 View 树,并作为子视图添加到 mContentParent 中。

3.DecorView 结构详解

运行时你可以通过以下方式查看结构:

View decorView = getWindow().getDecorView();
ViewGroup content = findViewById(android.R.id.content);

打印 decorView 的层级:

DecorView@123456
├── StatusBarBackground (状态栏背景)
├── NavigationBarBackground (导航栏背景)
├── LinearLayout (整体结构)
│   ├── ActionBar 或 Toolbar
│   └── ContentFrameLayout (@android:id/content)
│       └── ConstraintLayout (你的 activity_main.xml)
└── SubDecor (用于对话框样式等)

💡 所以你用 findViewById(android.R.id.content) 得到的是一个 ViewGroup,它只包含一个子 View —— 你的布局根节点。


4.LayoutInflater 的角色

setContentView 内部使用了 LayoutInflater 来解析 XML 布局文件:

LayoutInflater.from(context).inflate(resId, parent, true);
  • 将 XML 解析为 Java 对象(View 树)。
  • 添加到 @android:id/content 容器中。

5. 与 WindowManager 的关系

虽然 setContentView 设置了界面,但真正把 View 显示到屏幕上是由 WindowManager 完成的。

ActivityThread.handleResumeActivity() 中:

// 此时才会将 DecorView 添加到 WindowManager
wm.addView(decorView, layoutParams);

此时触发:

  • ViewRootImpl 创建
  • requestLayout() → measure → layout → draw 流程启动
  • 界面真正绘制到屏幕上

四、流程图

Activity.setContentView
PhoneWindow.setContentView
mContentParent 是否存在?
installDecor
移除旧视图
generateDecor: 创建 DecorView
generateLayout: 选择模板并加载到 DecorView
找到 ID_ANDROID_CONTENT 的 FrameLayout 作为 mContentParent
I
将自定义布局 inflate 到 mContentParent
回调 Activity.onContentChanged

五、常见问题与扩展

1. 为什么必须在 super.onCreate() 之后调用 setContentView()

因为 super.onCreate() 会初始化 WindowAppCompatDelegate,如果没有这些对象,setContentView 无法工作。


2. setContentView() 调用了多次会发生什么?

setContentView(R.layout.a);
setContentView(R.layout.b); // 覆盖之前的

第二次调用会清空 @android:id/content 的所有子 View,然后加载新布局。相当于替换整个界面内容


3. 如何获取 DecorView?有什么用途?

View decorView = getWindow().getDecorView();
// 例如:实现沉浸式状态栏
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);

六、总结:setContentView 原理精要

阶段关键动作
1. 初始化Activity 创建 PhoneWindow
2. 构建壳子ensureSubDecor() 创建 DecorView
3. 加载内容LayoutInflater 将布局 inflate 到 @android:id/content
4. 显示WindowManager.addView(decorView) 触发绘制流程

🔑 核心思想:setContentView() 并不直接显示界面,而是将布局“塞进”窗口的“内容区”,真正的显示由系统在 resume 阶段完成。

http://www.dtcms.com/a/438143.html

相关文章:

  • dlink nas建设网站有什么免费推广项目的好软件
  • 开源 C++ QT QML 开发(一)基本介绍
  • Java学习笔记Day14
  • C++进阶(4)——C++11右值引用和移动语义
  • 从入门到精通【Redis】理解Redis主从复制
  • 公司网站不备案wordpress地址怎么打开
  • 柯西显威:一道最值题的降维打击
  • Java 集合 “Map(2)”面试清单(含超通俗生活案例与深度理解)
  • 网站怎么做悬浮图片放大带后台的网站模板下载
  • java学习:四大排序
  • npm install 中的 --save 和 --save-dev 使用说明
  • 个人网站欣赏h5网站和传统网站区别
  • Inception V3--J9
  • Spring——编程式事务
  • 如何比较两个目录档案的差异
  • 美发店收银系统教程
  • wordpress网站怎么打开对于高校类建设网站的要求
  • 理解神经网络流程
  • 2025年渗透测试面试题总结-99(题目+回答)
  • Linux启动流程与字符设备驱动详解 - 从bootloader到驱动开发
  • 探讨区块链与生物识别技术融合的安全解决方案
  • 手机应用商店app下载官方网站下载建设厅网站技术负责人要求
  • 电子商务网站开发过程论文6保定网站建设哪家好
  • Lua语法
  • stm32摇杆adc数据分析
  • 公司网站开发费用如何做账网站三合一建设
  • MySQL 进阶知识点(十二)---- 管理
  • C/C++贪吃蛇小游戏
  • 【Linux】多线程创建及封装
  • 苏州网站推广公司创业商机网餐饮