LayoutInflater加载流程

简介

LayoutInflater在日常的Android开发中是经常使用的类,常常用于XML中View的加载相关流程。本文主要总结一些其常见api的源码流程。

获取LayoutInflater

我们一般会在Activity的onCreate方法中会通过setContentView方法设置自己的布局layoutId,Activity会将加载View委托给Window。

java 复制代码
  	public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    public LayoutInflater getLayoutInflater() {
        return getWindow().getLayoutInflater();
    }

而Window对应的实现是PhoneWindow类

java 复制代码
@Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        *****省略部分代码
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
	 public PhoneWindow(@UiContext Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
        ****省略部分代码
    }

从上述可知activity中的相关inflater来自PhoneWindow,其本质来自 LayoutInflater.from(context)

Factory和Factory2

Factory和Factory2是两个接口,用户创建具体的View对象,Factory2继承自Factory,新增了onCreateView 重载API,该api新增了parent参数。

java 复制代码
public interface Factory {
        @Nullable
        View onCreateView(@NonNull String name, @NonNull Context context,
                @NonNull AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        
        @Nullable
        View onCreateView(@Nullable View parent, @NonNull String name,
                @NonNull Context context, @NonNull AttributeSet attrs);
    }

上述两个接口主要用在LayoutInflater类inflater()流程中,其中解析xml获取到对应的view描述信息后,调用tryCreateView去创建View。

java 复制代码
public abstract class LayoutInflater {
    @UnsupportedAppUsage
    private Factory mFactory;
    @UnsupportedAppUsage
    private Factory2 mFactory2;
    @UnsupportedAppUsage
    private Factory2 mPrivateFactory;

	View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        //*****省略
        try {
            View view = tryCreateView(parent, name, context, attrs);
            if (view == null) {
                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;
            //****
    }

	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;
    }

可以看到,在tryCreateView中会按优先级去调用对应Factory的onCreateView方法去获取View实例,如果该方法返回空,则createViewFromTag方法会调用layoutInflater的onCreateView方法去创建View。该方法会反射创建View对象。

LayoutInflater中设计的Factory和Factory2相关接口和类,给了开发者自定义View创建和适配的机会。比如应用换肤、framework的View替换等。比如包含TextView的xml设置给AppCompatActivity时,其TextView会被替换成AppCompatTextView。

inflate流程

我们一般使用layoutInflater对象的inflater方法来加载xml中的View。该方法需要关注root和attachToRoot参数,root表示当前待加载的View的父容器。该参数对加载的影响主要是父容器的尺寸和主题对当前View的效果影响。而attachToRoot表示是否将当前待加载的View添加到父容器中,这个还会影响inflate方法返回值,当attachToRoot=true,返回父容器,attachToRoot=false返回当前加载到View。

java 复制代码
	public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) 								{
        return inflate(resource, root, root != null);
    }

  
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }

 
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        //如果是预编译的View,则反射创建该View。这里的预编译是指在打包时将xml描述的布局编译成.CompiledView文件。这样创建时不用解析xml。默认预编译为关闭状态
        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();
        }
    }
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            try {
                advanceToRootNode(parser);
                final String name = parser.getName();
                if (TAG_MERGE.equals(name)) {
                //merge标签时,root必须非NULL,并且attachToRoot必须为true。因为meger标签本身没有parent,无法正确加载。
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 根据name创建对应的View,前文我们有提到这个方法
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        //生成params
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
                    // 加载当前节点的子View
                    rInflateChildren(parser, temp, attrs, true);
                    // 如果root不为空并且attachToRoot=true,则把当前加载View添加到root中
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    //result为返回值,这里root == null || !attachToRoot时返回值时当前View,否则是root
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } 
            //***省略***
            return result;
        }
    }

LayoutInflater创建View

前文我们分析createViewFromTag这个方法时提到,如果factory和factory2创建View时都返回了Null,那么会兜底走LayoutInflater的onCreateView方法,该方法最终会调用createView创建View。我们来看看View是是不是通过反射创建的。

java 复制代码
public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        //*******
        Class<? extends View> clazz = null;
        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                //****
                //获取View的构造函数并缓存,这样不用每次都去查
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            }
            //****省略部分代码

            Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            try {
                //反射创建View
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    //如果是ViewStub,则设置LayoutInflater给它
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        } //***
    }
相关推荐
长亭外的少年4 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿7 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神8 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛8 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法9 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter10 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快11 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl11 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江12 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-12 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记