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;
            }
        } //***
    }
相关推荐
元争栈道21 分钟前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
居居飒1 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He4 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗4 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_397562316 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio
峥嵘life6 小时前
Android Studio版本升级那些事
android·ide·android studio
新手上路狂踩坑6 小时前
Android Studio的笔记--BusyBox相关
android·linux·笔记·android studio·busybox
TroubleMaker8 小时前
OkHttp源码学习之retryOnConnectionFailure属性
android·java·okhttp
叶羽西10 小时前
Android Studio IDE环境配置
android·ide·android studio
发飙的蜗牛'10 小时前
23种设计模式
android·java·设计模式