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

在日常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.mfbz.cn/a/384927.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux应用 进程间通信之信号量(POSIX)

1、前言 1.1 定义 POSIX信号量是一种用于同步进程之间对共享资源访问的机制。它允许进程在访问共享资源之前进行互斥和同步操作&#xff0c;以确保数据的一致性和正确性。POSIX信号量通常由一个整数值表示&#xff0c;可以进行原子增减操作&#xff0c;以及等待和通知操作。 …

Verilog刷题笔记29

题目&#xff1a; Create a 100-bit binary ripple-carry adder by instantiating 100 full adders. The adder adds two 100-bit numbers and a carry-in to produce a 100-bit sum and carry out. To encourage you to actually instantiate full adders, also output the ca…

《Linux 简易速速上手小册》第2章: 命令行的艺术(2024 最新版)

文章目录 2.1 基本 Linux 命令2.1.1 重点基础知识2.1.2 重点案例&#xff1a;整理下载文件夹2.1.3 拓展案例 1&#xff1a;批量重命名文件2.1.4 拓展案例 2&#xff1a;查找并删除特定文件 2.2 文件和目录管理2.2.1 重点基础知识2.2.2 重点案例&#xff1a;部署一个简单的网站2…

从零开始学howtoheap:理解fastbins的​unsorted bin攻击

how2heap是由shellphish团队制作的堆利用教程&#xff0c;介绍了多种堆利用技术&#xff0c;后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;从零开始配置pwn环境&#xff1a;优化pwn虚拟机配置支持libc等指…

政安晨:在Jupyter中【示例演绎】Matplotlib的官方指南(二){Image tutorial}·{Python语言}

咱们接着上一篇&#xff0c;这次咱们讲使用Matplotlib绘制图像的简短尝试。 我的这个系列的上一篇文章在这里&#xff1a; 政安晨&#xff1a;在Jupyter中【示例演绎】Matplotlib的官方指南&#xff08;一&#xff09;{Pyplot tutorial}https://blog.csdn.net/snowdenkeke/ar…

【Java八股面试系列】JVM-类和对象加载过程

目录 类和对象的加载过程 类的生命周期 类的加载过程 加载 验证 准备 解析 初始化 类卸载 对象的加载过程 类和对象的加载过程 什么是类加载和对象加载? 类加载&#xff08;Class Loading&#xff09;&#xff1a;这是指JVM在运行时将类的字节码文件加载到内存中的…

【5G NR】【一文读懂系列】移动通讯中使用的信道编解码技术-卷积码原理

目录 一、引言 二、卷积编码的发展历史 2.1 卷积码的起源 2.2 主要发展阶段 2.3 重要里程碑 三、卷积编码的基本概念 3.1 基本定义 3.2 编码器框图 3.3 编码多项式 3.4 网格图(Trellis)描述 四、MATLAB示例 一、引言 卷积编码&#xff0c;作为数字通信领域中的一项…

快速学习Spring

Spring 简介 Spring 是一个开源的轻量级、非侵入式的 JavaEE 框架&#xff0c;它为企业级 Java 应用提供了全面的基础设施支持。Spring 的设计目标是简化企业应用的开发&#xff0c;并解决 Java 开发中常见的复杂性和低效率问题。 Spring常用依赖 <dependencies><!-…

java之Maven

1. maven Maven是管理和构建java项目的工具 项目依赖资源(jar包)的管理,避免版本冲突统一项目结构项目构建&#xff0c;标准跨平台(Linux,window,MacOS)的自动化项目管理 2.maven依赖仓库 2.maven安装 maven安装视频教程 3. IDEA集成Maven 4. maven的依赖范围 5. maven生命…

可视化大屏:工作要干的好,也要汇报好,不然资源为啥向你倾斜。

有些友友们感受不到可是大屏的价值&#xff0c;认为没啥作用&#xff0c;这就是典型的下层思维&#xff0c;格局小了。 估计也没有当过领导或者管理层。可视化大屏的其他价值放在一边不说&#xff0c;就单纯这个汇报价值就十分巨大&#xff0c;包括对内和对外的汇报。 如何让…

【51单片机】DS18B20(江科大)

一、DS18B20温度传感器 1.DS18B20介绍 DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点 测温范围 :- 55℃到125℃ 通信接口:1-Wire(单总线) 其它特征:可形成…

【lesson51】信号之信号处理

文章目录 信号处理可重入函数volatileSIGCHLD信号 信号处理 信号产生之后&#xff0c;信号可能无法被立即处理&#xff0c;一般在合适的时候处理。 1.在合适的时候处理&#xff08;是什么时候&#xff1f;&#xff09; 信号相关的数据字段都是在进程PCB内部。 而进程工作的状态…

【动态规划】【数学】【C++算法】1449. 数位成本和为目标值的最大数字

作者推荐 【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目 本文涉及知识点 动态规划汇总 LeetCode1449. 数位成本和为目标值的最大数字 给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数&#xff1a; 给当前结果添加…

《UE5_C++多人TPS完整教程》学习笔记1 ——《P2 关于本课程(About This Course)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P2 关于本课程&#xff08;About This Course&#xff09;》 的学习笔记&#xff0c;该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版&#xff0c;UP主&#xff08;也是译者&…

使用 Windows 11/10 上的最佳 PDF 转 Word 转换器释放 PDF 的潜力

毫无疑问&#xff0c;PDF 是最好的文档格式之一&#xff0c;但就像其他格式一样&#xff0c;有时它们确实会带来一些限制。例如&#xff0c;在某些情况下&#xff0c;您可能想要将 PDF 转换为 Word。在这种情况下&#xff0c;您始终可以借助 PDF 到 Word 转换器的帮助。 为了说…

ChatGPT高效提问—prompt实践(生成VBA)

ChatGPT高效提问—prompt实践&#xff08;生成VBA&#xff09; 2. 生成VBA函数操作Excel ​ 当前Excel表格数据无背景颜色&#xff0c;区分不明显。假如我们想美化数据展示效果&#xff0c;把标题行设置为浅蓝色&#xff0c;其余奇数行设置为橙色&#xff0c;该怎么操作呢&am…

Spark MLlib

目录 一、Spark MLlib简介 &#xff08;一&#xff09;什么是机器学习 &#xff08;二&#xff09;基于大数据的机器学习 &#xff08;三&#xff09;Spark机器学习库MLlib 二、机器学习流水线 &#xff08;一&#xff09;机器学习流水线概念 &#xff08;二&#xff09…

【Java程序设计】【C00249】基于Springboot的私人健身与教练预约管理系统(有论文)

基于Springboot的私人健身与教练预约管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的私人健身与教练预约管理系统 本系统分为系统功能模块、管理员功能模块、教练功能模块以及用户功能模块。 系统功能模…

小白速成法:剖析一个Android项目以快速上手

这是一个基于Tasmota的设备、用MQTT协议来通信控制的安卓应用程序。支持ON/OFF命令插座和基本的RGB LED控制。 源码点击此处 只需要关注SmartController-main\app\src的代码 项目解压之后如图 只需要关注“app”文件夹里的东西即可&#xff0c;“gradle”是配置文件&#xf…

【国产MCU】-CH32V307-基本定时器(BCTM)

基本定时器(BCTM) 文章目录 基本定时器(BCTM)1、基本定时器(BCTM)介绍2、基本定时器驱动API介绍3、基本定时器使用实例CH32V307的基本定时器模块包含一个16 位可自动重装的定时器(TIM6和TIM7),用于计数和在更新新事件产生中断或DMA 请求。 本文将详细介绍如何使用CH32…