Android最全的setContentView源码分析

news2025/4/9 0:49:23

前言

我们在开发过程中,在布局文件里添加TextView,代码运行起来就可以看到对应文字显示出来,那系统是如何把我们的TextView加载并显示出来的呢?

源码分析(这里版本对应30)

第一阶段

我们直接从Activity.setContentView()【为什么不是AppCompatActivity呢?其实最终继承Activity,只不过进行了高版本的适配】源码开始分析:

Activity.setContentView()

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

getWindow()对应Window类,它是一个抽象类,我们知道它的唯一实现类是PhoneWindow

PhoneWindow.setContentView()

    public void setContentView(int layoutResID) {
        if (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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

mContentParent是一个ViewGroup,一开始默认为null,我们先看下installDecor()方法都做了什么?

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
           ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        }
        ...
    }

installDecor()方法代码比较多,我们看源码最忌讳一行行弄清楚,我们只关心我们需要关心的代码,这里重点方法为generateDecor(-1)generateLayout(mDecor),我们继续跟进下:

    protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

可以看到generateDecor()方法如其名,最终就是创建了一个DecorView对象;我们再看下generateLayout(mDecor)方法;

protected ViewGroup generateLayout(DecorView decor) {
		....
        // Inflate the window decor.
        int layoutResource;
        
        //下面会根据features不同的值给layoutResource赋值不同的布局文件,features就是对应不同的窗口样式
        int features = getLocalFeatures();
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
         	...
        } 
        ...
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
        	//默认加载R.layout.screen_simple布局
            layoutResource = R.layout.screen_simple;
        }

        mDecor.startChanging();
        //在这里将layoutResource添加到DecorView上
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        contentParent对应布局文件中ID_ANDROID_CONTENTView
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
		...
        return contentParent;
    }

generateLayout方法中,会根据不同的features(窗口样式,比如带不带标题栏等等)加载不同的布局文件,默认采用R.layout.screen_simple布局文件,我们看下这个布局文件代码:

<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" />
    <FrameLayout
         android: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>

获取到需要加载的布局文件后,紧跟着调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法:

 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
		...
        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

可以看到onResourcesLoaded方法就是将layoutResource布局添加到DecorView的根布局位置。添加完成后,最终generateLayout方法返回的就是ID对应ID_ANDROID_CONTENTFrameLayout!!

到这里我们先简单画一下当前界面的显示内容:
Activity窗口布局

第二阶段

分析完了installDecor(),接下来,我们就来分析mLayoutInflater.inflate(layoutResID, mContentParent)

在分析之前,我们先简单了解下mLayoutInflater,在PhoneWindow初始化时,会完成mLayoutInflater的初始化工作:

 public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
        ...
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

Context是一个抽象类,它对应的实现类为ContextImpl:

    public Object getSystemService(String name) {
		....
        return SystemServiceRegistry.getSystemService(this, name);
    }
public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        ...
        final Object ret = fetcher.getService(ctx);
		...
        return ret;
    }

SYSTEM_SERVICE_FETCHERS是一个Map集合,那什么时候把LayoutInflater放进集合的呢?答案在SystemServiceRegistry类的静态代码块中:

....
static{
       registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});

}
...
	### registerService方法
    private static <T> void registerService(@NonNull String serviceName,
            @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }

从这里我们可以看出mLayoutInflater是一个单例,整个APP启动只会创建一个实例。

我们继续分析mLayoutInflater.inflate(layoutResID, mContentParent),会调用到LayoutInflater.inflate方法

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
		...
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

其中tryInflatePrecompiled是Android 10(Android Q)中新增的方法,用来根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View,从而减少XmlPullParser解析Xml的时间。它是一个编译优化选项。

我们重点看下inflate(parser, root, attachToRoot)方法:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
          		...
                if (TAG_MERGE.equals(name)) {
					//有merge标签的解析
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                  	//普通布局解析
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                      // 解析Children,最终调用rInflate方法,一层层解析
                    rInflateChildren(parser, temp, attrs, true);

                   //添加创建的temp到root中,root即对应上面的FrameLayout,这里就完成了整个界面的解析
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            return result;
        }
    }

我们重点看下createViewFromTag(root, name, inflaterContext, attrs)

 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
		...
        try {
       		//先去创建View
            View view = tryCreateView(parent, name, context, attrs);
            if (view == null) {
            	//创建不成功,则直接通过反射去创建View,并做缓存
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;
            .....
		}
    }

我们先看下tryCreateView(parent, name, context, attrs)方法:

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
    	....
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        return view;
    }

我们看到创建View又交给了mFactory2处理二者都是LayoutInflater类内部定义的接口。Factory2继承自Factory接口,Factory2比Factory多增加了一个onCreateView(View parent, String name, Context context, AttributeSet attrs),该方法多了一个parent,用来存放构建出的View。

然后会交给AppCompatDelegateImpl.createView来处理:

public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
   	....
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed() 
        );
    }

mAppCompatViewInflater.createView方法如下:

   final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;
        View view = null;
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
			....
			//匹配其他View
            default:             
                view = createView(context, name, attrs);
        }
		//没有匹配成功
        if (view == null && originalContext != context) {
            view = createViewFromTag(context, name, attrs);
        }
        if (view != null) {
            checkOnClickListener(view, attrs);
            backportAccessibilityAttributes(context, view, attrs);
        }

        return view;
    }

我们就看下TextView是如何创建的:

    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }

就是直接new了一个AppCompatTextView返回,对于没有匹配成功的View(如自定义的View),会调用createViewFromTag方法进行创建:

 private View createViewFromTag(Context context, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
        try {
            mConstructorArgs[0] = context;
            mConstructorArgs[1] = attrs;
			//表示name里不包含.如LinearLayout/RetiveLayout等,就是拼上sClassPrefixList前缀,如android.widget.LinearLayout
            if (-1 == name.indexOf('.')) {
                for (int i = 0; i < sClassPrefixList.length; i++) {
                    final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
                    if (view != null) {
                        return view;
                    }
                }
                return null;
            } else {
                return createViewByPrefix(context, name, null);
            }
        } catch (Exception e) {
			...
        }
    }

我们看下createViewByPrefix方法:

    private View createViewByPrefix(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
            //先从缓存map中获取,减少反射带来的开销
        Constructor<? extends View> constructor = sConstructorMap.get(name);

        try {
        	//缓存中没有则通过反射根据类的全名去创建View
            if (constructor == null) {
                Class<? extends View> clazz = Class.forName(
                        prefix != null ? (prefix + name) : name,
                        false,
                        context.getClassLoader()).asSubclass(View.class);

                constructor = clazz.getConstructor(sConstructorSignature);
                //存放到缓存集合中
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            //这里是调用两参的构造方法
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            return null;
        }
    }

小结

布局解析主要以下几个步骤:

  1. 先会调用tryInflatePrecompiled进行解析添加到FrameLayout中【它会根据布局文件的xml预编译生成的dex文件,然后通过反射来生成对应的View,从而减少XmlPullParser解析Xml的时间。它是一个编译优化】,如果添加完成直接返回。

  2. 否则调用inflate(parser, root, attachToRoot)方法进行解析加载,会调用createViewFromTag方法进行根View创建,先调用tryCreateView()方法,最终会调用到AppCompatViewInflater.createView方法,对于TextViewImageViewButton这类View,直接调用两参的构造方法完成创建,对于LinearLayout或自定义View则通过反射进行创建,并进行了缓存处理。

  3. 如果上述tryCreateView()方法创建的根View返回为null,则会直接调用createView方法使用反射进行创建,同样进行了缓存处理。

  4. 根布局创建完成会调用rInflateChildren进行子View的创建,一层层创建添加到根布局View中;

  5. 最后将根布局View添加到FrameLayout中,完成整个界面View的解析。

总结

通过对setContentView的源码分析,了解了View是如何添加到当前界面上的,对于插件换肤方案有很大的帮助!

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/24299.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《第一行代码》核心知识点:Activity(活动)

Android四大组件之一&#xff1a;Activity前言二、Android四大组件之一&#xff1a;Activity(活动)2.1 活动基本介绍2.2 活动的基本用法2.2.1 如何在应用中弹出提示信息2.2.2 如何在活动中添加Menu菜单&#xff08;就一般右上角的三点&#xff09;2.2.3 如何实现活动跳转2.2.5 …

CANoe-什么是Vector Tool Platform(全网唯一讲明白的文章)

在CANoe软件:Home -> Measurement下,有一个功能项,Vector Tool Platform,是做什么用的呢? 点击后打开这个功能页面,发现界面内容不多,包含:设备选择、组件更新、系统更新、远程连接,还有一个连接状态显示 从界面功能猜测:这是一个设备管理和连接的平台。那么是什么…

购买窗帘时哪些可以不做?-江南爱窗帘十大品牌

在家居软装上&#xff0c;窗帘的选择很重要&#xff0c;因为它的存在感很强&#xff0c;占据了墙面的半壁江山。选对了&#xff0c;满心欢喜&#xff0c;选错了&#xff0c;就只能悔恨痛苦了。 1.不做拼色、花纹&#xff1a;拼色窗帘在酒店十分常见&#xff0c;但是不建议照搬回…

14 C++11线程同步之条件变量

在学习条件变量之前需要先了解下std::unique_lock;条件变量 condition_variable需要配合std::unique_lock使用&#xff1b; std::unique_lock std::unique_lock的详细细节参考此篇文章。 C11条件变量 条件变量是 C11 提供的另外一种用于等待的同步机制&#xff0c;它能阻塞…

第04章_运算符

第04章_运算符 1. 算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;和取模&…

使用dbeaver连接GaussDB数据库(集中式)

服务端方式登录 默认初始用户登录方式&#xff1a; [ommgaussdb01 ~]$ gsql -d postgres -p 30100 gsql ((GaussDB Kernel V500R002C10 build 04860477) compiled at 2022-10-28 20:04:35 commit 3892 last mr 8894 release) Non-SSL connection (SSL connection is recommen…

XAML标记扩展(3)

一、RelativeSource属性 我们进行Bingding时&#xff0c;如果明确知道数据源的Name&#xff0c;就能用Source或者ElementName进行绑定&#xff0c;但是有时候我们需要绑定的数据源可能没有明确的Name&#xff0c;此时我们就需要利 用Bingding的RelativeSource进行绑定&#xf…

虚拟数字人/直播/捏脸/3D/metahuman 实时人脸动作捕捉 开发笔记

拍照生成数字人 流程 手机&#xff08;iphone xr以上&#xff09;拍照&#xff08;脸部&#xff09;&#xff0c;导入到unrealmetahuman做数字人 【中文】从0开始捏一个自己的虚拟人&#xff0c;手机扫描到MetaHuman做一个自己的虚拟人_哔哩哔哩_bilibili 涉及APP iphone …

[附源码]java毕业设计校园兼职招聘系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

YUV图像基础知识

概念 YUV和RGB的功能类似&#xff0c;都是用来表示图像色彩的。但是对于 YUV 所表示的图像&#xff0c;Y 和 UV 分量是分离的。如果只有 Y 分量而没有 UV 分离&#xff0c;那么图像表示的就是黑白图像。彩色电视机采用的就是 YUV 图像&#xff0c;解决与和黑白电视机的兼容问题…

swift枚举(二)

swift枚举(一) No-payload enums 布局比较简单&#xff0c;也好理解&#xff0c;接下来看看 Single-payload enums Single-payload enums enum IFLEnum {case test_one(Bool)case test_twocase test_threecase test_four}print(MemoryLayout<IFLEnum>.size)print(Memor…

Vue事件处理器:事件绑定基础、事件修饰符:stop、prevent、capture、self、once;

先看代码&#xff1a; <body><div id"box">{{count}}<button click"handleAdd1()">add1</button><button click"handleAdd2">add2</button></div><script>new Vue({el: "#box",dat…

关于电脑使用的实用技巧

电脑几乎是我们每天都会用到的工具&#xff0c;那么电脑的使用技巧你知道多少呢&#xff1f;今天&#xff0c;我来为大家整理了几个常用的技巧&#xff0c;希望对大家的工作或学习效率有所帮助。 技巧一&#xff1a;快速查找文档按Windows E键打开电脑中的资源管理器&#xff0…

[附源码]SSM计算机毕业设计个性化新闻推荐系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

记一次神奇的 pipe 错误

文章目录1. 写在最前面2. 问题原因3. 解决问题3.1 CAP 的历史说明3.2 CAP 拆分的能力集合说明3.3 如何知道某个程序的能力集合3.3.1 查看只能写入 4096B 大小的程序能力位图3.3.2 查看能写入 65536B 大小的能力位图3.3.3 比较两个能力位图3.3.4 为 pod 增加 CAP_SYS_RESOURCE 的…

数字信号处理-4-三角函数合成与傅里叶级数

1 三角函数合成 函数正交&#xff08;数字信号处理-3-函数的正交&#xff09;&#xff0c;那它们相互之间无法通过组合得出对方的表达式&#xff0c;如&#xff1a;sinx 与 cosx 正交&#xff0c;acosnx 是无法表示 sinx 的。相互正交的各类三角函数是制作许多波形的基本单位。…

KT148A语音芯片按键版本一对一触发播放功能描述_V4

目录 一、简介 KT148A语音芯片--按键版本&#xff0c;支持3个IO口一对一触发 。同时也支持用户自己更换芯片内部的声音文件&#xff0c;方法&#xff0c;参考我们另外一份文档的描述“20220704_KT148A芯片自己更换声音的方法V3”。请留意&#xff0c;需要样品联系客服&#xf…

钡铼EdgeIO边缘计算 I/O 控制器

BL200 系列耦合器是一个数据采集和控制系统&#xff0c;基于强大的 32 位微处理器设计&#xff0c; 采用 Linux 操作系统&#xff0c;支持 Modbus&#xff0c;MQTT&#xff0c;OPC UA&#xff0c;Profinet&#xff0c;EtherCAT&#xff0c;Ethernet/IP&#xff0c; BACnet/IP…

程序员月薪8000,丢人吗?

近日&#xff0c;拉勾招聘数据研究院针对平台的程序员群体开展了深度调研&#xff0c;发布了《拉勾招聘2022程序员群体职场洞察报告》&#xff0c;呈现程序员最新的职场薪资水平。 *数据来源拉勾 从上图中可以看到&#xff0c;74%的00后应届毕业生的月薪在1-3万元区间&#…

C++语言的return语句的说明

C语言的return语句的说明 为了完成某一功能的程序指令&#xff08;语句&#xff09;的集合&#xff0c;称为函数。在程序中&#xff0c;编写函数的主要目的是将一个需要很多行代码的复杂问题分解为一系列简单的任务来解决&#xff0c;而且&#xff0c;同一个任务&#xff08;函…