Android中不得不谈的setContentView

2023-11-03 19:48

本文主要是介绍Android中不得不谈的setContentView,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


转载地址:http://www.jianshu.com/p/a2f07773f37d


几个月之前在做项目的布局优化时,使用 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 ,所以我们直接去看 ActivitysetContentView

    /*** 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() 方法。

    @Overridepublic 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 和 mContentParentinstallDecor();} 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 一个 DecorViewmDecor = 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 中两个成员变量 mDecormContentParent 分别是什么:


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 并赋值给成员变量 mContentParentreturn 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 什么时候能改个名字啊!



文/MeloDev(简书作者)
原文链接:http://www.jianshu.com/p/a2f07773f37d
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

这篇关于Android中不得不谈的setContentView的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/340472

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk