LayoutInflater源码解析及常见相关报错分析

news2025/1/23 21:18:10

在日常Android开发中,最经常使用的RecyclerView控件是大家都绕不开的,而编写其Adapter时更离不开LayoutInflater的调用。当然,如果你做这一行有些时日了,相信你对其使用一定是炉火纯青了。即使如此,我觉得LayoutInflater仍旧有值得分析的地方,相信你看完之后有更多的认识。Android系统中有许多包括ActivityManagerService在内的系统级服务,我们平时在使用时可通过Context调用,这些服务会在安卓系统初始化时以单例的形式注册,其中LayoutInflater服务就是其中之一。

我们常在RecyclerView.Adapter的onCreateViewHolder()中通过LayoutInflater将xml编写的布局文件转换成Android中的一个View对象:

@NonNull
@NotNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {
    View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_recycler_item, parent,
            false);
    ViewHolder viewHolder = new ViewHolder(inflate);
    return viewHolder;
}

其实不仅仅是onCreateViewHolder(),在Activity中的setContentView()中内部也是通过LayoutInflater将xml布局转换成View。

@Override
public void setContentView(int layoutResID) {
    ...
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}  //SDK里PhoneWindow.java中

而我们最常使用的关于LayoutInflater的方法莫过于下面几个:

1、View.inflate(mContext,R.layout.layout_sence_recycler_item,null);
2、LayoutInflater.from(mContext).inflate(R.layout.layout_sence_recycler_item,parent);
3、LayoutInflater.from(mContext).inflate(R.layout.layout_sence_recycler_item,parent,false);
4、LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   View view = inflater.inflate(R.layout.main, null);

.from(Context)方法原理暂时先不做详细讲解,其内部调用的是ContextImpl类中的getSystemService(),因此其实要说的是getSystemService(),这方法代码流程过多且与本文主旨无关不做赘述。

我们现在进去看看inflate()相关源码(在相关方法上按住Ctrl+鼠标左键点击),会发现其中总共四种重载方法:

image.png
第一种方法:

/**
 * Inflate a new view hierarchy from the specified xml resource. Throws
 * {@link InflateException} if there is an error.
 *
 * @param resource ID for an XML layout resource to load (e.g.,
 *        <code>R.layout.main_page</code>)
 * @param root Optional view to be the parent of the generated hierarchy.
 * @return The root View of the inflated hierarchy. If root was supplied,
 *         this is the root View; otherwise it is the root of the inflated
 *         XML file.
 */
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

第二种方法:

/**
 * Inflate a new view hierarchy from the specified xml node. Throws
 * {@link InflateException} if there is an error. *
 * <p>
 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
 * reasons, view inflation relies heavily on pre-processing of XML files
 * that is done at build time. Therefore, it is not currently possible to
 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
 *
 * @param parser XML dom node containing the description of the view
 *        hierarchy.
 * @param root Optional view to be the parent of the generated hierarchy.
 * @return The root View of the inflated hierarchy. If root was supplied,
 *         this is the root View; otherwise it is the root of the inflated
 *         XML file.
 */
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
    return inflate(parser, root, root != null);
}

第三种方法:

/**
 * Inflate a new view hierarchy from the specified xml resource. Throws
 * {@link InflateException} if there is an error.
 *
 * @param resource ID for an XML layout resource to load (e.g.,
 *        <code>R.layout.main_page</code>)
 * @param root Optional view to be the parent of the generated hierarchy (if
 *        <em>attachToRoot</em> is true), or else simply an object that
 *        provides a set of LayoutParams values for root of the returned
 *        hierarchy (if <em>attachToRoot</em> is false.)
 * @param attachToRoot Whether the inflated hierarchy should be attached to
 *        the root parameter? If false, root is only used to create the
 *        correct subclass of LayoutParams for the root view in the XML.
 * @return The root View of the inflated hierarchy. If root was supplied and
 *         attachToRoot is true, this is root; otherwise it is the root of
 *         the inflated XML file.
 */
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
              + Integer.toHexString(resource) + ")");
    }

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

第四种方法较长:

/**
 * Inflate a new view hierarchy from the specified XML node. Throws
 * {@link InflateException} if there is an error.
 * <p>
 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
 * reasons, view inflation relies heavily on pre-processing of XML files
 * that is done at build time. Therefore, it is not currently possible to
 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
 *
 * @param parser XML dom node containing the description of the view
 *        hierarchy.
 * @param root Optional view to be the parent of the generated hierarchy (if
 *        <em>attachToRoot</em> is true), or else simply an object that
 *        provides a set of LayoutParams values for root of the returned
 *        hierarchy (if <em>attachToRoot</em> is false.)
 * @param attachToRoot Whether the inflated hierarchy should be attached to
 *        the root parameter? If false, root is only used to create the
 *        correct subclass of LayoutParams for the root view in the XML.
 * @return The root View of the inflated hierarchy. If root was supplied and
 *         attachToRoot is true, this is root; otherwise it is the root of
 *         the inflated XML file.
 */
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];  //获取Context对象
        mConstructorArgs[0] = inflaterContext;    
        View result = root;    //存储父视图

        try {
            advanceToRootNode(parser);    
            final String name = parser.getName();

            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }

            if (TAG_MERGE.equals(name)) {   //判断是否是merge标签
                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 {
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);  //解析单个元素,实例化xml布局的根view对象

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);        // 创建匹配root对象的布局参数
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);        // attachToRoot:传进来的参数,如果该view不需要添加到父布局上,则直接将根据父布局生成的params参数来设置
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);  //如果传入的父布局不为null,且attachToRoot为true,则给temp设置布局参数,将实例化的view对象加入到父布局root中
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {  //如果传入的父布局为null,且attachToRoot为false,则返回temp
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(inflaterContext, attrs)
                    + ": " + e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        return result;
    }
}

继续跟随前三种重载方法后,你会发现最后都绕到了第四种重载方法里。

此方法可见总共三个参数:

参数一、xml解析器;

参数二、解析布局的父视图;

参数三、是否将要解析的视图添加进父视图中(是否要将当前加载的xml布局添加到第二个参数传入的父布局上面)。

关于XmlPullParser就不详解了,里面解析xml的过程较长,也与本文主旨无关。这里看一个重要方法advanceToRootNode():

/**
 * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
 * found.
 */
private void advanceToRootNode(XmlPullParser parser)
    throws InflateException, IOException, XmlPullParserException {
    // Look for the root node.
    int type;   //while循环解析查找xml标签,START_TAG是开始标签,END_DOCUMENT是结束标签
    while ((type = parser.next()) != XmlPullParser.START_TAG &&
        type != XmlPullParser.END_DOCUMENT) {
        // Empty
    }

    if (type != XmlPullParser.START_TAG) {
        throw new InflateException(parser.getPositionDescription()
            + ": No start tag found!");
    }
}

根据以上代码,我们可以概括其inflate()流程如下:

1、解析xml布局文件中根标签(第一个元素);

2、如果根标签是merge标签,则调用rInflate()来将merge标签下所有子View添加到根标签中;

3、如果不是merge标签,则调用createViewFromTag();

4、调用rInflateChildren()解析temp下所有子View,并将其添加到temp下;

5、根据设置的参数判断返回对应视图。

可以说整段代码就是一个将xml解析成View结构的过程,其过程中细节实现靠rInflate、createViewFromTag和rInflateChildren方法,而rInflateChildren内部又是调用rInflate,因此我们看createViewFromTag()和rInflateChildren()即可。

createViewFromTag

其源码如下:

@UnsupportedAppUsage
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {            //主题相关
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    try {
        View view = tryCreateView(parent, name, context, attrs);  //通过tryCreateView返回view对象  

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {       //内置view控件的解析
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    view = createView(context, name, null, attrs);        //自定义View的解析
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

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

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(
                getParserStateDescription(context, attrs)
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(
                getParserStateDescription(context, attrs)
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    }
}

继续看tryCreateView()的内部实现:

@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);    //用户可以设置Factory来解析View,此对象默认为Null,可以忽略
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);        //用户可以设置Factory来解析View,此对象默认为Null,可以忽略
    } else {
        view = null;
    }

    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);     //用户可以设置Factory来解析View,此对象默认为Null,可以忽略
    }

    return view;
}

所以具体流程下来,你会发现,最终调用的还是:

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

核心判断还是在这里,createViewFromTag的参数会将该元素的parent及名字传过来,我们修改下核心逻辑部分,即可为下面:

if (view == null) {
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
        if (-1 == name.indexOf('.')) {
            view = createView(name, "android.view.", attrs);
        } else {
            view = createView(name, null, attrs);
        }
    } finally {
        mConstructorArgs[0] = lastContext;
    }
}

相信这里你就理解了,如果这个名字在查找“.”返回-1即没有包含“.”时, 则认为是一个内置View,调用onCreate(),反之,调用createView()。

createView()

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

其实onCreateView()最终还是调用的createView(),所以我们这里看createView()就行了。

@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    Constructor<? extends View> constructor = sConstructorMap.get(name);       //    全局缓存
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

        if (constructor == null) {            //判断缓存是否为空,为空则自行反射加载
            // Class not found in the cache, see if it's real, and try to add it
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);            //添加到缓存
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);

                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
        }

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

        try {
            final View view = constructor.newInstance(args);                //创建新View实例,args是自定义主题相关的变量
            if (view instanceof ViewStub) {                    // 如果是ViewStub,则用同一个Context加载这个ViewStub的LayoutInflater
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    } catch (NoSuchMethodException e) {
        final InflateException ie = new InflateException(
                getParserStateDescription(viewContext, attrs)
                + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (ClassCastException e) {
        // If loaded class is not a View subclass
        final InflateException ie = new InflateException(
                getParserStateDescription(viewContext, attrs)
                + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    } catch (ClassNotFoundException e) {
        // If loadClass fails, we should propagate the exception.
        throw e;
    } catch (Exception e) {
        final InflateException ie = new InflateException(
                getParserStateDescription(viewContext, attrs) + ": Error inflating class "
                        + (clazz == null ? "<unknown>" : clazz.getName()), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

方法很长,这里只看其核心部分:

if (constructor == null) {
    // Class not found in the cache, see if it's real, and try to add it
    clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
            mContext.getClassLoader()).asSubclass(View.class);   //如果prefix不为空则构造完整类的路径,并通过反射形式加载

    if (mFilter != null && clazz != null) {
        boolean allowed = mFilter.onLoadClass(clazz);
        if (!allowed) {
            failNotAllowed(name, prefix, viewContext, attrs);
        }
    }
    constructor = clazz.getConstructor(mConstructorSignature);    //获取构造函数
    constructor.setAccessible(true);    //将构造函数存入缓存中
    sConstructorMap.put(name, constructor);
} else {
    ...
}
    Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = viewContext;
    Object[] args = mConstructorArgs;
    args[1] = attrs;

    try {
        final View view = constructor.newInstance(args);        //通过反射构造View
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        return view;
    } finally {
        mConstructorArgs[0] = lastContext;
    }

代码里的思路还是很清晰的,通过反射找到对应类的字节码文件,然后找到其对应构造方法,实例化,拿到View。

可能这段代码会让你疑惑:

constructor = clazz.getConstructor(mConstructorSignature);

我们点进去查看内部调用:

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                    int which) throws NoSuchMethodException
{
    if (parameterTypes == null) {
        parameterTypes = EmptyArray.CLASS;
    }
    for (Class<?> c : parameterTypes) {
        if (c == null) {
            throw new NoSuchMethodException("parameter type is null");
        }
    }
    Constructor<T> result = getDeclaredConstructorInternal(parameterTypes);
    if (result == null || which == Member.PUBLIC && !Modifier.isPublic(result.getAccessFlags())) {
        throw new NoSuchMethodException(getName() + ".<init> "
                + Arrays.toString(parameterTypes));
    }
    return result;
}

但其实这就是反射获取的有两个参数的构造方法,这也就是为什么在做自定义控件的时候要重载两个函数的构造函数的原因。可以说这就是解析单个View的全部过程。但LayoutInflater要解析的是整个窗口中的视图树,这个靠rInflate()实现:

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();        //通过解析器获取视图树的深度,进行遍历
    int type;
    boolean pendingRequestFocus = false;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||              //while循环解析各view
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {            
            if (parser.getDepth() == 0) {       //如果根布局是include标签,抛异常
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {            //如果merge标签,抛异常,因为merge标签必须为根布局
            throw new InflateException("<merge /> must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, context, attrs);        //根据元素名进行解析
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);            //递归调用
            viewGroup.addView(view, params);                 //将解析的View添加到父控件(ViewGroup)中
        }
    }

    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }

    if (finishInflate) {
        parent.onFinishInflate();
    }
}

可见,添加全部View的过程就是在while循环中调用一个createViewFromTag生成根view,然后 rInflateChildren → rInflate → rInflateChildren → rInflate → … … 就这样递归调用一直到遍历完全,然后通过addView添加到父控件。

相信看到这里就都明白了,整个inflate过程可以分为两大步骤:

  1. 通过解析器来将xml文件中的内容解析出来。
  2. 使用反射将解析出来的元素创建成View对象。

root为null的情况

而根据LayoutInflater.inflate()中的内部方法和返回对象又可以总结出:

调用的inflate方法对应最终调用inflater()方法参数返回情况
inflate(id, null)inflater(parser, null, false)生成对应View对象并返回
inflate(id, root)inflater(parser, root, true)生成View对象添加到root上并返回root
inflate(id, null, false)inflate(parser, root, false)生成View对象并返回
inflate(id, null, true)inflate(parser, null, true)生成View对象并返回
inflate(id, root, false)inflate(parser, root, false)生成View对象并返回
inflate(id, root, true)inflater(parser, root, true)生成View对象添加到root上并返回root

只要传递的root不为空,则会根据root来创建生成View的LayoutParams。

如果LayoutInflater调用inflate(id, null),不传入root即父布局,则填充的View的layout_width和layout_height的值无论修改成多少,都不会有效果。确切点来讲,所有以layout_开头的属性都会失去作用,原因很简单,一个View的测量结果并不只是由它自己的layout_width和layout_height(即LayoutParams)所决定的,而是由父容器给它的约束(MeasureSpec)和它自身的LayoutParams共同决定的。有兴趣的朋友可以尝试验证下。

常见报错

LayoutInflate.inflate()方法报错大体有两个原因:

1、在传入父布局的情况下重复addView,对应非法状态异常;

java.lang.IllegalStateException The specified child already has a parent. You must call removeView() on the child's parent first.  

最常见的非法状态异常,出现场景原因五花八门,举一个最常见的例子,我们在调用Fragment时,为什么后面的参数一定要是false?

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, 
                         @Nullable ViewGroup container, 
                         @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_layout, container, false);
}

其实在Fragement源码中,onCreateView()里返回的View其实已经添加到container中了。说白了就是Fragment有自己的AddView操作,如果第三个参数传入true,那么就会直接将inflate出来的布局添加到父布局当中。然后再次addView的时候就会发现它已经有一个父布局了,从而抛出IllegalStateException。(对应逻辑源码在上述解析中可看到)

2、传入相关参数有问题导致报空指针。

java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:715)
        ...

这种没什么好说的,要么是layoutInflate对象为空、或layoutInflate所依赖的Context对象为空,要么是inflate()参数中的布局文件有错,导致XmlPullParser对象无法正确的解析布局文件原因。

总结

inflate就是先把最外层的root解析完,然后用rInflate去递归把子view解析完,子view用createViewFromTag方法去解析单个View,用createView反射出view,全都递归完再返回。当然,解析肯定是耗时操作,很明显耗时操作有两处:

解析xml布局文件反射获取实例

而谷歌官方对此做的优化是:

预编译缓存

关于LayoutInflater源码解析先到这里了,如果有其它问题或者疑惑可以留言。

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

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

相关文章

【C++】STL之string 超详解

目录 1.string概述 2.string使用 1.构造初始化 2.成员函数 1.迭代器 2.容量操作 1.size和length 返回字符串长度 2.resize 调整字符串大小 3.capacity 获得字符串容量 4.reserve 调整容量 5.clear 清除 6.empty 判空 3.string插入、追加 、拼接 1.运算…

LeetCode:67.二进制求和

67. 二进制求和 - 力扣&#xff08;LeetCode&#xff09; 又是一道求和题&#xff0c;% / 在求和的用途了解了些&#xff0c; 目录 题目&#xff1a; 思路分析&#xff1a; 博主代码: 官方代码&#xff1a; 每日表情包&#xff1a; 题目&#xff1a; 思路分析&#xf…

第五课[lmdeploy]作业 +第六课[OpenCompass评测]作业

第五课基础作业 如下图&#xff0c;采用api_server部署&#xff0c;并转发端口通过curl提交内容。 第六课基础作业 完了捏&#xff1f;

【Java程序设计】【C00252】基于Springboot的实习管理系统(有论文)

基于Springboot的实习管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的实习管理系统 本系统分为前台功能模块、管理员功能模块、教师功能模块、学生功能模块以及实习单位功能模块。 前台功能模块&#xf…

【每日一题】LeetCode——反转链表

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

网络协议与攻击模拟_17HTTPS 协议

HTTPShttpssl/tls 1、加密算法 2、PKI&#xff08;公钥基础设施&#xff09; 3、证书 4、部署HTTPS服务器 部署CA证书服务器 5、分析HTTPS流量 分析TLS的交互过程 一、HTTPS协议 在http的通道上增加了安全性&#xff0c;传输过程通过加密和身份认证来确保传输安全性 1、TLS …

C++ 广度优先搜索(bfs)(五十四)【第一篇】

今天我们来学习一下一个新的搜索&#xff0c;广度优先搜索。 1.广度优先搜索的前提 队列&#xff08;queue&#xff09; 是一种 操作受限制 的线性表&#xff0c;其限制&#xff1a; 只允许从表的前端&#xff08;front&#xff09;进行删除操作&#xff1b; 只允许在表的后端…

基于无线传感器网络的LC-DANSE波束形成算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1LC-DANSE算法原理 4.2 LCMV算法原理 5.完整程序 1.程序功能描述 在无线传感器网络中&#xff0c;通过MATLAB对比LC-DANSE波束形成算法和LCMV波束形成算法。对比SNR&#xff0c;mse等指标…

【go语言】一个简单HTTP服务的例子

一、Go语言安装 Go语言&#xff08;又称Golang&#xff09;的安装过程相对简单&#xff0c;下面是在不同操作系统上安装Go语言的步骤&#xff1a; 在Windows上安装Go语言&#xff1a; 访问Go语言的官方网站&#xff08;golang.org&#xff09;或者使用国内镜像站点&#xff0…

肯尼斯·里科《C和指针》第13章 高级指针话题(2)函数指针

我们不会每天都使用函数指针。但是&#xff0c;它们的确有用武之地&#xff0c;最常见的两个用途是转换表(jump table)和作为参数传递给另一个函数。本节将探索这两方面的一些技巧。但是&#xff0c;首先容我指出一个常见的错误&#xff0c;这是非常重要的。 简单声明一个函数指…

Linux基础I/O(三)——缓冲区和文件系统

文章目录 什么是C语言的缓冲区理解文件系统理解软硬链接 什么是C语言的缓冲区 C语言的缓冲区其实就是一部分内存 那么它的作用是什么&#xff1f; 下面有一个例子&#xff1a; 你在陕西&#xff0c;你远在山东的同学要过生日了&#xff0c;你打算送给他一份生日礼物。你有两种方…

视觉开发板—K210自学笔记(五)

本期我们来遵循其他单片机的学习路线开始去用板子上的按键控制点亮LED。那么第一步还是先知道K210里面的硬件电路是怎么连接的&#xff0c;需要查看第二节的文档&#xff0c;看看开发板原理图到底是按键是跟哪个IO连在一起。然后再建立输入按键和GPIO的映射就可以开始变成了。 …

VTK 常用坐标系 坐标系 转换

1.VTK 常用坐标系 计算机图形学里常用的坐标系统主要有四种&#xff0c;分别是&#xff1a;Model坐标系统、World坐标系统、View坐标系统和Display坐标系统 在VTK里&#xff0c;Model坐标系统用得比较少&#xff0c;其他三种坐标系统经常使用。它们之间的变换则是由类vtkCoord…

Docker容器输入汉字触发自动补全

一、描述 输入汉字自动触发补全&#xff1a; Display all 952 possibilities? (y or n)是因为容器中没有中文字符集和中文字体导致的&#xff0c;安装中文字体&#xff0c;并设置字符集即可。 二、解决 1、安装字符集 &#xff08;1&#xff09;查看系统支持的字符集 lo…

甘肃旅游服务平台:技术驱动的创新实践

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

【Java程序设计】【C00260】基于Springboot的企业客户信息反馈平台(有论文)

基于Springboot的企业客户信息反馈平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的企业客户信息反馈平台 本系统分为平台功能模块、管理员功能模块以及客户功能模块。 平台功能模块&#xff1a;在平台首页可…

计算机网络——网络安全

计算机网络——网络安全 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c; [跳转到网站](https://www.captainbed.cn/qianqiu) 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 网络安全何…

【AI绘图】初见·小白入门stable diffusion的初体验

首先&#xff0c;感谢赛博菩萨秋葉aaaki的整合包 上手 stable diffusion还是挺好上手的&#xff08;如果使用整合包的话&#xff09;&#xff0c;看看界面功能介绍简单写几个prompt就能生成图片了。 尝试 我在网上找了一张赛博朋克边缘行者Lucy的cos图&#xff0c;可能会侵…

黄金交易策略(Nerve Nnife.mql4):趋势平仓按钮的作用

当觉得行情不太对路&#xff0c;可以点击右下角按钮&#xff0c;实现趋势单的移动止盈&#xff08;止损&#xff09;。点了这个按钮回撤多少平仓是可以在参数里设定的。 完整EA&#xff1a;Nerve Knife.ex4黄金交易策略_黄金趋势ea-CSDN博客

深入理解梯度加权类激活热图(Grad-CAM)

深入理解梯度加权类激活热图&#xff08;Grad-CAM&#xff09; 项目背景与意义 在深度学习领域&#xff0c;模型的预测能力往往是黑盒子&#xff0c;难以解释。梯度加权类激活热图&#xff08;Grad-CAM&#xff09;作为一种可解释性技术&#xff0c;能够帮助模型开发者更好地…