Android setContentView详解

一、前言

在 Android 应用开发的世界里,setContentView 几乎是每个开发者都会接触到的方法。它的作用至关重要——负责将视图(View)或布局(Layout)展示在屏幕上。尽管这看起来是一个简单直接的操作,但其背后实际上隐藏着 Android 系统中复杂而精妙的窗口管理和视图渲染机制。最近,在与同行的交流中,我发现我几乎将这一块忘得一干二净。因此,我决定从 ActivitysetContentView 方法入手,重新梳理并深入探讨 Android 的窗口管理和视图展示原理,希望能够为大家带来新的理解和启发。

二、AppCompatActivity

一般而言你都会如此使用setConentView

 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(mContentLayoutId);
 }

当然你也可以在这么使用:

 class TestActivity : AppCompatActivity(R.layout.activity_test)

我们看看AppCompatActivitysetContentView藏着什么:

 @Override
 public void setContentView(@LayoutRes int layoutResID) {
     //**
     getDelegate().setContentView(layoutResID);
 }

可以看到在AppCompatActivity中,使用的getDelegate,这看起来像是委托啊!点下去看看:

 @NonNull
 public AppCompatDelegate getDelegate() {
     if (mDelegate == null) {
         mDelegate = AppCompatDelegate.create(this, this);
     }
     return mDelegate;
 }

我们找到了全新的AppCompatDelegate!官方如是说:

此类表示一个委托,您可以使用该委托将 AppCompat 的支持扩展到任何 Activity.只能 Activity 与一个 AppCompatDelegate 实例链接,因此应保留从 create(Activity, AppCompatCallback) 返回的实例,直到 Activity 被销毁。

但是AppCompatDelegate是一个抽象类,我们可以很轻松的找到它的实现类AppCompatDelegateImpl

我们可以在这里看到setContentView

 @Override
 public void setContentView(int resId) {
     ensureSubDecor();
     ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
     contentParent.removeAllViews();
     LayoutInflater.from(mContext).inflate(resId, contentParent);
     mAppCompatWindowCallback.getWrapped().onContentChanged();
 }

可以看到,我们提供的LayoutId,最后会添加到contentParent这个View上,那么mSubDecor是从哪儿来的?看到调用方法的名字,ensure sub decor ,子装饰视图,想必在这里。我们继续往下看:

 private void ensureSubDecor() {
     // 检查子装饰(sub decor)是否已经设置
     if (!mSubDecorInstalled) {
         // 创建子装饰视图
         mSubDecor = createSubDecor();
         //**
     }
 }

看样子藏在createSubDecor中, gogogo:

 private ViewGroup createSubDecor() {
     TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
     //**省略 获取当前上下文的主题属性,设置对应的样式
     a.recycle();// 确保窗口已安装其装饰
     ensureWindow();
     mWindow.getDecorView();// 获取布局填充器
     final LayoutInflater inflater = LayoutInflater.from(mContext);
     ViewGroup subDecor = null;// 根据是否有标题和是否为浮动窗口来决定使用哪个布局
     if (!mWindowNoTitle) {
         if (mIsFloating) {
             // 如果是浮动窗口,则使用对话框标题装饰
             subDecor = (ViewGroup) inflater.inflate(
                     R.layout.abc_dialog_title_material, null);
             //**
         } else if (mHasActionBar) {
             // 如果有动作栏,则使用特定主题创建布局
             // 使用主题化上下文填充视图并设置为内容视图
             subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                     .inflate(R.layout.abc_screen_toolbar, null);
             //**
         }
     } else {
         // 根据是否覆盖动作模式选择不同布局
         if (mOverlayActionMode) {
             subDecor = (ViewGroup) inflater.inflate(
                     R.layout.abc_screen_simple_overlay_action_mode, null);
             //**
         } else {
             subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
             //**
         }
     }
     //**
     // 将窗口的内容视图设置为subDecor
     mWindow.setContentView(subDecor);
     //**
     return subDecor;
 }

可以看到这个方法返回一个配置好的 subDecorsubDecor使用的是系统的布局,根据配置的不同,使用了不同的xml。返回给我们用于添加LayoutId,但是它是如何显示的还是不清楚,但是注意到mWindow.setContentView(subDecor);

我们点下去一看,回来到Window类:

 /**
  * Convenience for
  * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
  * 将屏幕内容设置为显式视图。此视图直接放置在屏幕的视图层次结构中。它本身可以是一个复杂的视图层次结构。
  * @param view The desired content to display.
  * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
  */
 public abstract void setContentView(View view);

显然这是整个视图显示过程中非常核心的一步。但是它是抽象的!不过根据我们小学二年级就学过的

The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.

是的,Window只有一个实现类-PhoneWindow

三、PhoneWindow

我们找到PhoneWindow

 @Override
 public void setContentView(View view, ViewGroup.LayoutParams params) {
     if (mContentParent == null) {
         // 如果内容父视图还未创建,则进行安装
         installDecor();
     } 
     //**
     if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
         //**
     } else {
         // 将视图添加到内容父视图中
         mContentParent.addView(view, params);
     }
     //**
 }

我们看看如何初始化mContentParent,走进installDecor()的内心。

 private void installDecor() {
     mForceDecorInstall = false;
     if (mDecor == null) {
         // 创建窗口装饰视图
         mDecor = generateDecor(-1);
         //**
     } else {
         mDecor.setWindow(this);
     }if (mContentParent == null) {
         // 生成并设置内容布局
         mContentParent = generateLayout(mDecor);
            //**
         } else {
            //**
         }
         // 省略涉及到过渡管理器和动画的配置
     }
 }

在这里我们可以看到两个,generateDecor,但是在createSubDecor中我们已经创建过了:

 protected ViewGroup generateLayout(DecorView decor) {
     // ... 省略了一部分属性设置代码 ...// 根据窗口特性选择布局资源
     int layoutResource;
     int features = getLocalFeatures();
     // 根据不同的特性标志选择不同的布局资源
     if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
         if (mIsFloating) {
             layoutResource = res.resourceId;
         } else {
             layoutResource = R.layout.screen_title_icons;
         }
     } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 
             && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
         layoutResource = R.layout.screen_progress;
     } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
         if (mIsFloating) {
             layoutResource = res.resourceId;
         } else {
             layoutResource = R.layout.screen_custom_title;
         }
     } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
         if (mIsFloating) {
             layoutResource = res.resourceId;
         } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
             layoutResource = a.getResourceId(
                     R.styleable.Window_windowActionBarFullscreenDecorLayout,
                     R.layout.screen_action_bar);
         } else {
             layoutResource = R.layout.screen_title;
         }
     } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
         layoutResource = R.layout.screen_simple_overlay_action_mode;
     } else {
         layoutResource = R.layout.screen_simple;
     }
     // 装饰视图开始变化
     mDecor.startChanging();
     // 使用LayoutInflater加载布局资源
     mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 获取内容父视图
     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
     // ... 省略了其他设置代码 ...// 装饰视图完成变化
     mDecor.finishChanging();return contentParent;
 }

在这个方法中,会根据不同的特性标志选择不同的布局资源,但是这些布局都有一个显著的特点。他们都有一个id为content的FrameLayout。就是那个老生常谈的android.R.id.content~。

 <FrameLayout
     android:id="@android:id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent" />

四、倒着回去

至此,我们知道了PhoneWindowsetContentView中的contentParent来自哪里:

在这里插入图片描述

那我们也是知道了AppCompatDelegate中的setContentViewcontentParent来自哪里:

在这里插入图片描述

捋一下:

  1. 在活动的 onCreate 方法中调用 setContentView,传入布局资源ID或者直接传入一个视图(View)对象。
  2. AppCompatDelegate中调用Window.setContentView
  3. PhoneWindow 对象负责创建和管理顶层视图容器,DecorView。如果 DecorView 还未创建,Window 会通过调用 generateDecor 方法来创建它。DecorView中一定有一个ID为android.R.id.content的FrameLayout。
  4. AppCompatDelegateImpl将视图添加到android.R.id.content

五、Activity

为什么没有提及Activity呢?细心的大家应该发现了,AppCompatActivity的setContentView是一个重写方法,它完全重写了父类。

Overrides method in Activity

我们往上看一下:

 public void setContentView(@LayoutRes int layoutResID) {
     getWindow().setContentView(layoutResID);
     initWindowDecorActionBar();
 }

显然,最后Activity也是回到了PhoneWindow

六、LayoutInflater

不知道大家有没有注意到PhoneWindow和AppCompatDelegate中的LayoutInflater,那么DecorView和我们的Layout是如何渲染到屏幕的呢?请看LayoutInflater。

用Google话来说:

将布局 XML 文件实例化到其相应的 View 对象中

请见下回分解。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值