Android中不得不谈的setContentView

Android 源码解析 之 setContentView 小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人。 阅读详情

本文原创,转载请注明出处。
欢迎关注我的 简书 ,关注我的专题 Android Class 我会长期坚持为大家收录简书上高质量的Android相关博文。

写在前面:

几个月之前在做项目的布局优化时,使用 Hierarchy Viewer 查看项目的层级结构,然后发现顶层的布局并不是在XML中我写的根布局,而是嵌套了多层 Layout ,简单查阅了一些资料之后明白这是系统为我们加上的。把这个知识点写在了印象笔记中的 TODO list(里面还有好多知识想研究,一直在拖延T.T),搁置了好久最近重新拿出来好好研究了一下,争取做到温故知新,融会贯通嘛。

也许有的同学没看过 Hierarchy Viewer 下项目的界面布局,没关系,我现在带大家了解下。
新建一个 module ,打开 sdk tool 文件夹下的 Hierarchy Viewer ,布局结构展示如下:

MainActivity界面层级

先别着急找放大镜,想想我们新建项目的默认布局,按理说根布局应该是 RelativeLayout ,并且子 View 是一个 TextView 写着 “Hello World”才对啊~ 多出来的这些布局层级是什么?

既然陌生又看不懂,那就先从我们熟悉的入手,找一下我们自己写的布局:

RelativeLayout

原来 RelativeLayout 和它的子 View TextView 在这里,看一下左下角的位置标识,红框部分指明 RelativeLayout 是 Toolbar 以下的部分。

再想想,我们是通过什么方法将这个布局填充到 Activity 上的呢?

没错是 setContentView

那就在 setContentView 中寻找蛛丝马迹吧

因为在 Android Studio 中 MainActivity 默认继承于v7包下的 AppCompatActivity ,目的是为了提供控件的向下兼容或者新控件,AppCompatActivity 也是层层继承于 Activity ,所以我们直接去看 Activity 的 setContentView

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow() 拿到了 Activity 的成员变量 mWindow ,进而调用了 setContentView() 方法,mWindow 是 Window 类,继续跟进,看看 Window 类是什么

Window类

注释中的描述翻译过来就是,Window 是 视觉和行为表现的顶层抽象基类,它的实例会当作顶层视图添加进 WindowManager , 它有一个唯一的实现类是 PhoneWindow。
本文我们不会去剖析 WindowManager 有哪些作用和行为,我默默地把它加入了我的 TODO list 中,拖延到什么时候就不一定了哈T.T。

为了防止你忘了我们在做什么和我们即将做什么,先来一个中场回顾:
首先我们查看布局时发现有很多“超出我们预料和理解范畴”的布局出现,跟进 setContentView() 方法,发现 Acitvity 中是 Window 调用了 setContentView() ,而抽象基类 Window 有一个唯一的实现类 PhoneWindow。不多说,来看看实现类 PhoneWindow 中的 setContentView() 方法。

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //初始化 DectorView 和 mContentParent
            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 {
            //首次 setContentView 走到这里
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

当我们没有调用 setContentView() 时,mContentParent (是ViewGroup) 是 null ,所以有两行代码值得我们关注 installDecor() 和 mLayoutInflater.inflate(layoutResID, mContentParent)
首先 mContentParent 作为第二个参数传入了 inflate 方法中, 也就是说 我的布局中的 RelativeLayout 被层层解析之后的 View 视图树 作为了 mContentParent 的子 View 插入。

现在不知道 mContentParent 是什么没关系,继续跟进 installDecor() 方法。

随着API level的升高,源码发生了很多有关 Feature 、 Style 和 Wiget 的细微变化,还是蛮有意思的
这里我还想说一句,相信在 Android 设计之初 PhoneWindow 这个类就存在了,显然现在的这个命名有些问题,毕竟目前的设备不仅仅是 phone 了,也许改成 DeviceWindow 会比较合适

    private void installDecor() {
        if (mDecor == null) {
            // new 一个 DecorView
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        }
        if (mContentParent == null) {
            //初始化 mContentParent 
            mContentParent = generateLayout(mDecor);
            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            // 找到一个带ActionBar属性的布局容器 decorContentParent 
            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());             
                //配置UI设置
                mDecorContentParent.setUiOptions(mUiOptions);
            }
         } else {
             if (mContentParent instanceof FrameLayout) {
                  ((FrameLayout)mContentParent).setForeground(null);
                }
         }                   
    }

省略了与分析无关的代码,其中很多是对 feature 和 style 属性的一些判断和设置,首先 installDecor() 方法从字面意思看,很有可能是初始化加载 DecorView 的,首先看看 PhoneWindow 中两个成员变量 mDecor 和 mContentParent 分别是什么:

mDector 和 mContentParent

描述的信息可以概括为 mDector 是 窗体的顶级视图,mContentParent 是放置窗体内容的容器,也就是我们 setContentView() 时,所加入的 View 视图树。

当二者为 null 时,有两行代码值得关注,分别为 mDecor = generateDecor() 和 mContentParent = generateLayout(mDecor)

不过在此之前,先来看看这行寻找 decorContentParent 布局的代码

final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

decor_content_parent 看起来很眼熟的样子,点击它进入布局来看看:

screen_toolbar.xml

为什么说 decor_content_parent 眼熟呢?打开布局查看器来看看

这里写图片描述

在 Hierarchy Viewer 中可以看到 ActionBarOverlayLayout 的布局文件的 id 正是 decor_content_parent 不光如此 布局文件中的每个 View 节点的名称和 id 都与 Hierarchy Viewer 视图中的一一对应。再看其中的 FrameLayout 的 id 为 content , 我们自然而然的猜测它就是我们根布局 RelativeLayout 的父布局,心里一下有了底,继续研究~

跟进 generateDecor() 方法:

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

这个没什么可多说的,就是为我们的窗体 new 了 一个 DecorView 。

再来看 generateLayout(mDecor)

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 获得窗体的 style 样式
        TypedArray a = getWindowStyle();

        // 省略大量无关代码

        // Inflate the window decor.
        int layoutResource;
        int features = getLocalFeatures();

        //填充带有 style 和 feature 属性的 layoutResource (是一个layout id)

        View in = mLayoutInflater.inflate(layoutResource, null);

        // 插入的顶层布局 DecorView 中

        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        // 找到我们XML文件的父布局 contentParent 

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        // 省略无关代码
        mDecor.finishChanging();
        // 返回 contentParent 并赋值给成员变量 mContentParent
        return contentParent;
    }

这个方法的代码有300多行,剔除了很多无关代码,我们分模块来看:

    View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

首先 layoutResource 是系统的 xml 布局文件的 id,里面有我们设置窗体的 features 和 style 属性,然后通过 decor.addView 添加进 mDector 视图。这里也是我们要在 setContentView() 之前执行requestWindowFeature() 才可以的原因

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.

        mDecor.finishChanging();

        return contentParent;

关键点来了, ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
通过 findViewById 找到系统修饰布局文件中 id 为:
这里写图片描述

这个 id 是不是非常眼熟,与我们上文的猜测不谋而合,这就是我们一直在寻找的作为 activity_main 的父布局的 FrameLayout

我们在布局文件查看器中再找一下:

容器 FrameLayout id = content

return contentParent 这一步就返回了我们的成员变量 mContentParent

到现在为止其实整个知识点主干的逻辑已经走完了,为大家花了一张简单的思维导图

调用逻辑

并不复杂,线性逻辑调用还是蛮清晰的。

不过相信你也许会问,上文你仅仅提到了两个布局呀,一个顶层的 DecorView 和 我们布局文件的父布局 FrameLayout ,而查看布局层级时,为什么有这么多其他这么多额外的布局呢?

因为随着 Android API level 的不断变化,组件也在随之增多,比如 ActionBar Toolbar 等等,这些组件相关的布局是否加载与你的 feature 设置设备的特性相关联,而且版本不同,布局文件的层级结构也在不断变化着丰富着,我这个是 API22 的源码,我做了一些对比,有许多代码细节是不一样的,比如在这里的 feature 就新增了 Toolbar ,但是大体上的逻辑框架肯定不会变
比如我们目前的 MainActivity 的视图主要有两大分支,一条设置 Toolbar 的相关配置,一条就是我们的 RelativeLayout 了。

写在后面:
写这篇博客的原因一是我自己要研究梳理总结这个知识点,二是想让大家明白,Android 版本之间的迭代很快,一年前的博客阐述的观点到今天可能就再不适用了,但是 PhoneWindow 管理布局视图的这套逻辑框架,却一直没怎么改变。通过阅读源码,可以学习 Google 工程师们良好的代码风格,汲取他们搭建框架的思想,让我们自己写的代码也能如此健壮。

PS: PhoneWindow 什么时候能改个名字啊!

深入解析Android中的setContentView加载布局原理 前言 对于Android的开发者来说,setContentView大家再熟悉不过了,在我们的Activity中首先就是要用它加载我们的布局,但是应该有一部分人是不知道加载布局的原理,也包括我,今天就从源码的角度分析setContentView加载布局原理。 准备工作 由于我们使用的Android API部分源码是隐藏的,当我们在AndroidStudio中是不能找到源码的,我们可以去官网下载相应源码去查看,当然在GitHub下载相应版本的API替换我们sdk下platforms相应api的android.jar。这样我们就可以在AndroidStudio查看到隐藏的api了,可以断点调试帮助我 立即下载

相关推荐

记录:N10激光雷达串口编程,将数据存进 SQLite,并进行 ROS2 - cartographer 单雷达建图

N10 系列采用 TOF(time of flight)测距原理,通过测量调制激光的发射、返回时间差来测量物体与传感器的相对距离。激光发射器发出调制脉冲激光,内部定时器开始 从t1时刻计算时间,当激光照射到目标物体后,部分能量返回,当雷达接收到返回的激光信号时,在t2时刻停止内部定时器计时,光速C,激光雷达到达物体的距离D为:经过N10系列激光雷达内嵌的信号处理单元的实时解算得到探测物体的距离值,结合高精度自适应角度测量模块输出的角度信息,可以得到量程内周围 360 度环境 的二维平面信息。

m0_74013893的博客 1096

文件管理搜不到Android 里的文件,Android:在原始文件夹中添加文件后窗口找不到内容容器视图...

正在获取窗口找不到内容容器视图。这个错误询问应用程序何时将检查权限Android:在原始文件夹中添加文件后窗口找不到内容容器视图我注意到,对于穿着应用程序,我正在将穿着apk复制到原始文件夹中。它有大约2.5 Mb。添加这个我得到这次崩溃java.lang.RuntimeException: Window couldn't find content container viewat com.and...

weixin_39930276的博客 948

基于51单片机+SYN6288语音播报

51单片机控制SYN6288语音播报资料SYN6288模块原理图语音合成命令接线如何接线实现原理程序代码main.csyn6288.cuart.cdelay.c基于 51单片机的其他项目基于51单片机+遥控灯基于51单片机+LCD1602显示基于51单片机+红外遥控解码+LCD1602显示基于51单片机+DHT11温湿度+LCD1602显示基于51单片机+DS18B20温度测温器+LCD1602显示尾声 资料 我用的是绿深电子技术的模块,资料也挺齐全的。也有相关例程。 SYN6288模块原理图 语音合成

weixin_47457689的博客 3万+

从源码探索Activity布局加载过程

众所周知,Activity是承载布局的重要组件,是通过setContentView()方法来设置xml布局文件的,那么我们来提出几个问题 1.布局文件到底是怎么加载到Activity并显示到屏幕的呢? 2.我们给activity设置了主题,它对应的样式代码是在哪设置的呢? 下面我们跟踪源码来探索一下。 首先,我们点开setContentView()方法 publi...

qq_27246079的博客 408

Quanto option期权公式推导(更新中)

Quanto期权不同于以标的股票或资产的计价货币支付最终收益的普通欧式期权,它以另一种货币以预先确定的汇率支付最后的收益。以股票B的计价货币来计价的quanto看涨期权的收益为F0×max⁡(Sa(T)−K,0)×Sb(T)F_0 \times \max(S_a(T)-K, 0) \times S_b(T)F0​×max(Sa​(T)−K,0)×Sb​(T) 其中SaS_aSa​以货币A计价,SbS_bSb​以货币B计价,F0F_0F0​是预先固定的汇率. 假设上述股票均遵循几何布朗运动,可以得到以下恒等式

qq_41782124的博客 3683

大模型推理中Prefill与Decode、KV Cache三者说明

Prefill 阶段对输入 Prompt 并行编码,一次性构建KV Cache,属于计算密集型任务。Decode 阶段逐 token 迭代生成,复用KV Cache避免重复计算,属于显存带宽密集型任务。KV Cache是自回归推理的核心基石,用显存换时间,实现两阶段衔接与高效生成。PD 分离通过任务解耦、异构硬件、异步调度、独立批处理,解决传统一体化推理的资源冲突与利用率低下问题,是当前云端大模型推理服务的主流工程优化方向。

a486259的博客 1489

setContentView那些事

刨根问底setContentView在平时的android开发中,经常会使用到Activity#setContentView方法来设置我们自己的布局,那么setContentView中到底做了什么,我们的布局 是怎么加载并显示到手机屏幕上的,这就是今天要讨论的内容,看下Activity#setContentView方法public void setContentView(@LayoutRes in

mockingbirds的专栏 2733

Android 14.0 开机广播接收慢广播延迟问题分析

在14.0的系统rom定制化开发中,在某些时候在接收开机广播的时候发现,过了一分钟才收到开机广播,接下来就需要分析下 开机广播的相关分发机制,来处理下相关问题

安卓兼职framework和app工程师的博客 631

Android滑动冲突解决方法

现阶段你以为过不去的事,天大的事,当过去以后就会发现那只是屁大点事。实际上,也真的只是屁大点事儿。所有发生的都是应当发生的,无论是对是错。你需要为自己所做的任何决定和选择进行承担和负责。你需要做的是不断充实自己,做自己喜欢做的事情,让自己忙碌起来。你要感谢所有的人,所有人的到来,都是为了增加你感情的丰富程度,教会你如何处世如何为人,是他们让你波澜不惊的生活起了波澜,给你的生活增添了色彩,你会感激你

nicolelili1的专栏 1627

30. 如何使用 SEGW 的 redefine 功能对 SAP 标准 OData 服务进行扩展

SAP 发布了很多标准的 Fiori 应用,可以通过笔者的文章里提到的进行浏览。比如 My Opportunities 就是 SAP CRM 领域的一个 Fiori 应用:下图是 Opportunity 明细页面的 Info 标签页:假设我们想在这些标准字段的末尾增添一个新字段 Created By,用来维护当前 Opportunity 的创建者。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1174

机器人动力学仿真软件:Webots_(14).机器人声音仿真

Webots 中的声音仿真主要通过Sound和两种节点来实现。Sound节点用于播放预定义的音频文件,而节点则允许在仿真过程中动态生成和播放声音。这些节点可以用于模拟机器人的语音输出、报警声、环境噪音等。以下是一个示例,展示如何在控制器代码中播放预定义的声音。// 控制器代码(.c) # include <webots/robot.h> # include <webots/sound.h> # define TIME_STEP 64 const char * sound_name = "sound";

kkchenjj的博客 1312

Android 从setContentView谈Activity界面的加载过程

Android窗口     先来了解下Android系统的窗口布局。当我们设置窗口的Theme为可见样式时,Android的窗口如下图:     查看Activity的源码,Activity中有一个私有成员Window(mWindow),而Window是一个抽象类,它提供了绘制窗口的一组通用API(如下第一段代码);PhoneWindow继承自Window,是Window的具体实现类;

Kitty_Landon的专栏 981

小米15修复“NV损坏” 去除主板校验电阻步骤解析与图示

小米15系列将搭载骁龙8至尊版处理器,采用台积电3nm工艺。文章分析了手机出现"NV损坏"故障的表现及修复方法,包括通信异常、系统启动问题等典型症状,并介绍了去除主板校验电阻和刷写工程固件两种修复方案。重点讲解了去除主板电阻后刷写QCN文件的操作流程,强调在维修过程中需要结合分析与思考。适用于小米15系列特定机型(24129PN74G/I/C)的硬件修复。

小马哥的专栏 313

android 加载过程,Android 从setContentView谈Activity界面的加载过程

一、前言作为一个Android开发人员,setContentView方法肯定相当不陌生,因为在我们每一个需要呈现页面的Activity的onCreate方法中都会调用setContentView方法来加载我们事先写好的布局文件。然而或许大部分人也和我一样一直都是用用就好,也没有深入思考该方法具体是怎样将我们的布局文件呈现给用户的。接下来我们来好好研究研究这个方法的作用原理吧!二、Android窗口...

weixin_42116585的博客 456

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Android完美解析setContentView 你真的理解setContentView吗?

导读:本篇文章的前半部分为源码分析,后半部分为一个例子,在例子中我们会遇到一些问题,从而回答前半部分留下的问题!

阿拉灯神灯的专栏 4万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android setContentView源码与原理分析

Android 中 setContentView 方法源码分析:该方法负责将 XML 布局与 Activity 关联,核心流程涉及 PhoneWindow 创建 DecorView 和 ContentParent。调用链为 Activity.setContentView() → PhoneWindow.setContentView() → installDecor() → generateLayout(),最终通过 LayoutInflater 将布局加载到 mContentParent 视图中。整个过程构

csdn_silent的博客 5099

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Android的setContentView流程

1.在ActivityThread的performLaunchActivity里面调用Activity的attach方法,创建PhoneWindow。2.在PhoneWindow里面调用setContentView的installDecor方法里面的generateDecor创建一个DecorView。

qq_42426725的博客 1480
上一篇: 开发一个Android应用之前,应该做点什么?
下一篇: DrawerLayout+Toolbar用法初体验
MeloDev
博客等级 码龄11年 93粉丝 47原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值