LayoutInflater背后隐藏的一些东西

作者: 的一幕 | 来源:发表于2019-08-06 19:34 被阅读12次

本来是要继上一篇来写CoordinatorLayout源码分析的,由于在项目中遇到LayoutInflater一些问题,所以今天把LayoutInflater一些相关问题给分析下,还是和往常一样,先来demo事例:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout layout = findViewById(R.id.main);
        View inflate = getLayoutInflater().inflate(R.layout.inflate1, null);
        ViewGroup.LayoutParams lp = inflate.getLayoutParams();
        Log.d(TAG, "lp is null:" + (lp == null));
        layout.addView(inflate);
        Log.d(TAG, "add view========");
        LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) inflate.getLayoutParams();
        Log.d(TAG, "layoutParams_width:" + layoutParams.width);
        Log.d(TAG, "layoutParams_height:" + layoutParams.height);
    }
}

addView之前获取下inflateLayoutParams是否为空,并且在addView之后又获取下LayoutParams的宽高
activity_main.xml布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff0000"
    android:orientation="vertical"
    tools:context=".MainActivity" />

demo很简单,通过获取到LayoutInflater将inflate1布局中的view添加进来,下面看看inflate1布局长啥样:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#cccccc"
    android:gravity="center"
    android:text="我是inflate进来的">

</TextView>

啥也没有,只是一个文本。为了演示事例,这里设置了背景颜色,以及layout_widthlayout_height都是wrap_parent,心想肯定是个宽高匹配textview,然而我们看下效果,以及运行出来的日志:

image.png image.png
从日志上看,效果图和后面获取到的layoutParams_widthlayoutParams_height是对应上的,宽是match_parent,高是wrap_content。我们再看一种情况:
image.png
没想到在addView的时候,直接抛了异常:
image.png
相信这个异常大家比较熟悉吧,意思是child已经有parent了,此时又去添加到别的viewgroup身上了。说明在inflate过程中已经被添加过了,那咱们去掉addView过程看下结果:
image.png
此处我把外面的LinearLayout的id打印出来了,并且把inflate之后的结果inflate的id打印出来了:
image.png
image.png
从日志上看,inflate返回的结果是传进去的layout,并且子view的宽高都是wrap_content。那此时如果改下inflate1.xml文件呢:
image.png
结果如图所示:
image.png
说明这个时候inflate1.xml中的layoutparams起作用了。所以说第二个参数中的root参数不为空,说明此时生成的layoutparams以传进去的layout文件为主,并且inflate方法返回就是当前root。

还有种情况调用带第三个参数的方法,最后一个参数是一个布尔值,我们直接传false:


image.png

此时效果图和日志如下:


image.png
image.png
说明此时在inflate的时候没有操作过addView方法,而且此时返回的结果也不是刚才的root了。那么下面带着这些效果图和日志咋们一起分析下源码是怎么回事:

源码分析

  • activity.getLayoutInflater()
    image.png
    此处调用了window中的getLayoutInflater方法,window中该方法是抽象的,说明需要到相应的子类中看下该方法,大家都知道activity中实例化window是一个phoneWindow对象,因此看下phoneWindow对象的getLayoutInflater方法:
    image.png
    直接返回的成员变量mLayoutInflater,该变量在构造器的时候初始化的:
    image.png
    这个代码很熟悉吧,其实最终都是通过LayoutInflater.from(context)来获取LayoutInflater的,只不过在activity中方便我们直接用罢了:
    image.png
    这里最终通过context.getSystemService获取LayoutInflater的,其实跟我们获取各种服务一样的获取,如果大家看过android源码设计模式这本书,相信看工厂模式的时候,有讲过android中获取各种服务的由来。在ActivityThread中有涉及到context的由来,其实它是ContextImpl类来实例化的。那咱们直接看ContextImplgetSystemService方法:
    image.png
    所以最终锁定在了SystemServiceRegistry类中获取到各种服务的:
    image.png
    最终我们定位到SYSTEM_SERVICE_FETCHERS变量,其实它就是个hashMap,用来存储android中要用到的各种服务,它是在什么时候初始化这些服务的呢:
    image.png
    看到了没,它是在SystemServiceRegistry静态代码块中获取各种服务的,那咱们直接看Context.LAYOUT_INFLATER_SERVICE是什么服务:
    image.png
    看到了没,真面目终于出来了,获取的是一个PhoneLayoutInflater对象,它是LayoutInflater的子类。
    我们再来屡屡Activity.getLayoutInflater由来:

Activity.getLayoutInflater调用window.getLayoutInflater,而Activitywindow对象是phoneWindow对象,在phoneWindow对象中的getLayoutInflater方法,返回的是mLayoutInflater变量,mLayoutInflater变量是在phoneWindow构造器中通过LayoutInflater.from(context)方法获取的,在该方法中通过context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取的,因此LayoutInflater的获取其实跟android其他的服务也是一样获取的,而大家知道context的实现类是ContextImpl,因此是调用了ContextImpl.getSystemService方法,而在该方法中是通过SystemServiceRegistry.getSystemService方法获取到的,因此可以知道android中所有的服务都是通过SystemServiceRegistry.getSystemService获取的,而在SystemServiceRegistry类中,可以发现所有的服务都是通过静态代码块放到了SYSTEM_SERVICE_FETCHERS中,在用的时候直接从SYSTEM_SERVICE_FETCHERS中取,所以最终发现LayoutInflater其实是PhoneLayoutInflater对象。

  • LayoutInflater.inflate
    上面分析了getLayoutInflater方法其实获取的是PhoneLayoutInflater对象,因此咱们看下它的inflate方法,打开一看原来inflate方法还是在LayoutInflater中:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

上面可以看到是调用了三个参数的inflate方法,如果传进来的root是空的,那个第三个参数就是false,否则为true。那咱们看下带三个参数的inflate方法:

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) + ")");
    }
    //获取到xml解析器
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        //调用了另外一个重构的inflate方法
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

首先是获取到XmlResourceParser解析器,该处解析器是一个XmlPullParser类型的解析器,从字面意思看xml解析是遵从pull解析的规则,最后还是调用了inflate重构的方法,把刚刚获取的XmlResourceParser传了重构的inflate方法:

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];
        mConstructorArgs[0] = inflaterContext;
        //默认返回的是root
        View result = root;

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

            if (TAG_MERGE.equals(name)) {
                //如果布局的根标签是merge标签,那么通过inflate传过来的root必须不为空,并且attachToRoot=true,否则抛出异常,
                //其实想想也是那么回事,咋们的merge标签的布局是要被添加到外层的viewgroup上的
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                //解析根标签是merge标签
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                //如果是view类型的标签,直接通过该方法创建view
                final View temp = createViewFromTag(root, name, inflaterContext, attires

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    //如果root不为空,那么子view的layoutParames是通过root.generateLayoutParams获取到的
                    params = root.generateLayoutParams(attires
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);
                //看到了没,如果传过来的root不为空,并且attachToRoot=true,直接将创建的view添加到root上,并且params是通过root.generateLayoutParams获取到的
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                //如果root为空或者attachToRoot=false,那么此时返回的result直接是获取到的xml中的根布局
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

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

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        return result;
    }
}

通过注释可以看到,默认inflate方法返回的view是root。首先判断根标签是不是merge标签,大家都知道merge标签其实是减少布局层次的一个标签,如果外面的布局和merge标签要用的布局是同一个布局的话,那么此时里面可以用merge标签来代替,所以如果根布局是merge标签,如果root为空或者attachToRoot=false,直接抛了异常,大家可以写个merge的布局试试。接着调用了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循环不断地取里面的标签,如果没有了下一个标签或者到了END_DOCUMENT才停止
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

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

        final String name = parser.getName();
        //requestFocus标签
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {//tag标签
            parseViewTag(parser, parent, attires
        } else if (TAG_INCLUDE.equals(name)) {//include标签
            //如果include标签的层次为0,直接抛出异常,意思是include标签在顶层
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            //解析include标签
            parseInclude(parser, context, parent, attires
        } else if (TAG_MERGE.equals(name)) {//如果里面的还是meger标签直接抛出异常,因为merge标签必须在xml的根标签上
            throw new InflateException("<merge /> must be the root element");
        } else {
            //这里才是根据标签来创建不同的view,很关键
            final View view = createViewFromTag(parent, name, context, attires
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attires
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }

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

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

咋们直接看如果第二层的标签是include,如果include标签是在顶层,那么直接抛出异常,下面看下parseInclude方法:

private void parseInclude(XmlPullParser parser, Context context, View parent,
        AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;

    if (parent instanceof ViewGroup) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        final boolean hasThemeOverride = themeResId != 0;
        if (hasThemeOverride) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();

        // If the layout is pointing to a theme attribute, we have to
        // massage the value to get a resource identifier out of it.
        //获取layout属性的xml的id
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        if (layout == 0) {//如果layout属性不是resource类型的
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            if (value == null || value.length() <= 0) {
                throw new InflateException("You must specify a layout in the"
                        + " include tag: <include layout=\"@layout/layoutID\" />");
            }

            // Attempt to resolve the "?attr/name" string to an attribute
            // within the default (e.g. application) package.
            //为了解决字符串类型的layout属性
            layout = context.getResources().getIdentifier(
                    value.substring(1), "attr", context.getPackageName());

        }

        //如果上面的layout的xml的id还是为0,直接抛异常
        if (layout == 0) {
            final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout "
                    + "reference. The layout ID " + value + " is not valid.");
        } else {
            final XmlResourceParser childParser = context.getResources().getLayout(layout);

            try {
                final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty.
                }

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

                final String childName = childParser.getName();
                //merge标签还是继续跟上面merge标签一样
                if (TAG_MERGE.equals(childName)) {
                    //递归调用
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    //看到了没,所有的解析跟view相关的标签都走createViewFromTag方法
                    final View view = createViewFromTag(parent, childName,
                            context, childAttrs, hasThemeOverride);
                    final ViewGroup group = (ViewGroup) parent;

                    final TypedArray a = context.obtainStyledAttributes(
                            attrs, R.styleable.Include);
                    //获取include标签的id和visibility属性        
                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                    a.recycle();
                    ViewGroup.LayoutParams params = null;
                    try {
                        //获取当前include的layoutParams
                        params = group.generateLayoutParams(attires
                    } catch (RuntimeException e) {
                        // Ignore, just fail over to child attire
                    }
                    //如果当前的include的layoutParams为空,才会用自己的layoutParams
                    if (params == null) {
                        params = group.generateLayoutParams(childAttrs);
                    }
                    view.setLayoutParams(params);

                    // 继续递归调用
                    rInflateChildren(childParser, view, childAttrs, true);
                    //如果include标签有id,那么layout中的xml的根布局id以这个为主了
                    if (id != View.NO_ID) {
                        view.setId(id);
                    }

                    switch (visibility) {
                        case 0:
                            view.setVisibility(View.VISIBLE);
                            break;
                        case 1:
                            view.setVisibility(View.INVISIBLE);
                            break;
                        case 2:
                            view.setVisibility(View.GONE);
                            break;
                    }
                    //最后将include中的view添加到传过来的parent里面,可以看出来include标签是直接添加到当前的parent
                    group.addView(view);
                }
            } finally {
                childParser.close();
            }
        }
    } else {
        throw new InflateException("<include /> can only be used inside of a ViewGroup");
    }

    LayoutInflater.consumeChildElements(parser);
}

parseInclude方法中, 获取到layout标签的xml的资源文件id,如果id=0,则获取layout属性的string类型的值,如果string类型不为空,则获取字符串类型的layout属性值。如果获取到的layout属性是某个xml文件的id,然后和上面inflate过程一样,判断标签是merge标签还是普通的view标签,如果是merge标签,继续递归调用rInflate方法,如果是view标签,则调用createViewFromTag方法,如果include标签上有id和visible属性,那么include的layout中的xml文件的id就是include的id,visible属性也是如此,如果include标签的layoutparams属性不为空,那么layout中的xml文件的layoutparams就以include的layoutparams为主,否则以自己的layoutparams为主。所以这就是为什么Include标签的layoutparams会覆盖layout的layoutparams。然后继续去rInflateChildren里面的子view,形成一个递归。最后将include里面的子view添加到当前root身上。

说完了上面的parseInclude,下面来说说createViewFromTag过程,这个过程是咱们平常写自定义view和android自带的view,比如TextView、Button等控件的生成,咱们回到rInflate方法的调用createViewFromTag处:

image.png
上面可以看到createViewFromTag完了之后,进行了rInflateChildren操作,最后将获取到的view添加到parent身上。并且此时的params是parent身上的LayoutParams。已经好几次看到了createViewFromTag影子和rInflateChildren的影子,后面再分析这两个方法,说完了rInflate方法,我们再回到inflate方法,还记得刚才在inflate方法中,我们一直在顺着标签为merge的标签不,下面再看看,非merge标签的部分,也就是我们经常写各种view的部分:
//首先还是和rInflate中一样,如果遇到view的标签,去创建view
final View temp = createViewFromTag(root, name, inflaterContext, attires
ViewGroup.LayoutParams params = null;
//如果传进来的root不为空,并且attachToRoot=false,才会给我们的xml布局设置LayoutParams,所以这就是为什么在root为空的情况下,获取到的LayoutParams是空的
if (root != null) {
    params = root.generateLayoutParams(attires
    if (!attachToRoot) {     
        temp.setLayoutParams(params);
    }
}
//还是和rInflate过程一样,createViewFromTag完之后继续rInflateChildren
rInflateChildren(parser, temp, attrs, true);
//这里能解释为什么在root不为空,直接能看到xml布局在根布局上显示
if (root != null && attachToRoot) {
    root.addView(temp, params);
}
//如果root为空或者attachToRoot为false,那么result返回的是xml布局中的根布局,否则为root
if (root == null || !attachToRoot) {
    result = temp;
}

这里只贴上了inflate中标签为view的部分,上面已经分析了inflate方法的merge标签部分,可以看到首先也是调用了createViewFromTag方法,紧接着通过传过来的root是否为空以及attachToRoot是否为false的情况下来确定要不要给xml中的布局设置LayoutParams,如果root不为空,并且attachToRoot=false情况下,才会给xml布局设置LayoutParams,如果root不为空,attachToRoot=true,是直接将xml布局直接添加到root上,并且LayoutParams也不为空,此时两种情况的LayoutParams都是通过ViewGroup的generateLayoutParams获取到的,关于generateLayoutParams其实就是获取当前要生成的view的LayoutParams。看此处也是调用了rInflateChildren方法进行字view的递归调用。最后我们通过root=null或者attachToRoot=false来确定inflate返回xml中的根布局view。

我们再来总结下此处的逻辑:如果root不为空,(attachToRoot=true,此时是直接将inflate出来的view添加到root上,并且此时inflate出来view的LayoutParams是以自己在xml布局文件中设置的保持一致,如果attachToRoot=false,此时就不会将inflate出来的view添加到root上,并且此时返回的结果是inflate出来的view。)如果root为空,(不管attachToRoot是为true还是false,inflate出来的结果就不会被添加到任何view上,并且此时返回的结果就是inflate出来的view),为了方便记住,写了个表格如下:

root!=null root=null
attachToRoot=true addView、LayoutParams、返回root 返回xml中根布局
attachToRoot=false LayoutParams、返回xml中的根布局 返回xml中根布局

其实到现在我们可以分析开篇那几个例子的现象:

LinearLayout layout = findViewById(R.id.main);
View inflate = getLayoutInflater().inflate(R.layout.inflate1, null);
ViewGroup.LayoutParams lp = inflate.getLayoutParams();
Log.d(TAG, "lp is null:" + (lp == null));
layout.addView(inflate);
Log.d(TAG, "add view========");
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) inflate.getLayoutParams();
Log.d(TAG, "layoutParams_width:" + layoutParams.width);
Log.d(TAG, "layoutParams_height:" + layoutParams.height);

main是一个LinearLayout,inflate1布局就一个textview,textview的宽高都是wrap_content,在日志上显示addView之前lp=null,最后得到的inflate里面的textview宽是match_parent、高是wrap_content。在这个例子中,root=null,attachToRoot没传,其实在源码中默认调用两个参数的inflate方法中,如果root为空,attachToRoot=false,root不为空,attachToRoot=true。

image.png
也就是在上面事例中inflate出来的结果直接返回了,没有设置LayoutParams和addView。所以印证了在addView之前获取不到LayoutParams,而在addView之后又能获取,咋们直接看ViewGroup的addView方法:
image.png
可以看出来如果在addView时获取不到child的LayoutParams,通过generateDefaultLayoutParams方法设置:
image.png
在viewgroup中默认是wrap_content,而在上面例子中是LinearLayout,所以看下LinearLayout重写该方法没:
image.png
果真如此,在LinearLayout中默认的LayoutParams宽是match_parent,高是wrap_content。因此验证了上面的例子。如果你把外层的LinearLayout换成FrameLayout肯定是个全屏的TextView,大家可以自己去验证下。

在第二个例子中,在inflate方法传入了layout参数,因此直接可以得出结论,被addView过了,设置了LayoutParams,并且返回的结果就是当前的root,也就是我们传过去的layout参数,所以在后面再addView的时候直接抛了该异常:


image.png

并且从运行出来的结果看宽高都是wrap_content,因为此时LayoutParams是来自xml布局的。

分析完了事例后,我们接着看在inflate方法中有个比较重要的方法一直没有说,rInflateChildrencreateViewFromTag
每次createViewFromTag方法调用了,紧接着就是调用rInflateChildren方法:

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attire
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

该方法只是简单地调用了下rInflate,不断地找里面的下一个子view标签。

方法调用流程图.png

createViewFromTag
该方法就是我们在xml布局中写的view标签来创建view的方法:

View createViewFromTag(View parent, String name, Context context, AttributeSet attire
        boolean ignoreThemeAttr) {
    //如果标签是view的,直接通过class属性来获取name
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    try {
        View view;
        //默认都是空
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attires
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attires
        } else {
            view = null;
        }
        //默认mPrivateFactory是空的
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attires
        }

        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    //该处是创建系统的view
                    view = onCreateView(parent, name, attires
                } else {
                    //创建自定义的view
                    view = createView(name, null, attires
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

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

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

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

首先如果是view标签,那么获取view的class属性来获取name,如果name中有点的话,说明该view是自定义的view,如果没有点,说明是系统自带的view,比如TextView、Button等,首先看下创建系统的view:



看到了没此处传了个android.view的前缀,如果是自定义的view就不会有前缀:

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    //看有没有缓存的构造器
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    //验证如果不是view类型的,直接将进行清除缓存操作
    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) {
            // 如果有前缀加上前缀操作
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            //将class类字节码放到mFilter中,方便下次有缓存的时候用
            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            //这里就是为什么在自定义view中,如果是通过xml生成view要重写两个参数的原因
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            //放到缓存map中
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // 首次该map是空的
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // 直接加载class字节码文件
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);

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

        Object lastContext = mConstructorArgs[0];
        if (mConstructorArgs[0] == null) {
            // Fill in the context if not already within inflation.
            mConstructorArgs[0] = mContext;
        }
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        //获取到当前的view
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            //viewstub类型的直接调用setLayoutInflater方法
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        return view;

    } catch (NoSuchMethodException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": 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(attrs.getPositionDescription()
                + ": 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(
                attrs.getPositionDescription() + ": Error inflating class "
                        + (clazz == null ? "<unknown>" : clazz.getName()), e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

该方法看似很长,实际后面的代码都是多余抛的异常,首先是获取到map中存储的构造器,如果有,再去验证是不是继承自view类型的,如果是就不进行移除操作,然后通过反射调用了两个参数的构造器,这就是为什么我们在xml中用的view,需要重写两个参数的构造器。

陆陆续续,终于把inflate整个过程屡了一遍,其实在activity的setContentView方法中最终也是调用了LayoutInflater中的inflate方法,大家有兴趣也可以去看看这个流程。

思考
在RecyclerView中重写adapter中,item的布局明明是match_parent,可最终显示是wrap_content,解决办法是动态设置holder.itemView.setLayoutParams,还可以设置LayoutManager
总结

所有的获取LayoutInflater都是最终通过context.getSystemService获取到的,该服务是PhoneLayoutInflater
inflate操作逻辑:如果root不为空,(attachToRoot=true,此时是直接将inflate出来的view添加到root上,并且此时inflate出来view的LayoutParams是以自己在xml布局文件中设置的保持一致,如果attachToRoot=false,此时就不会将inflate出来的view添加到root上,并且此时返回的结果是inflate出来的view。)如果root为空,(不管attachToRoot是为true还是false,inflate出来的结果就不会被添加到任何view上,并且此时返回的结果就是inflate出来的view)
addView操作的时候,如果没有设置LayoutParams,默认会用ViewGroup的generateDefaultLayoutParams生成的,每个ViewGroup是不一样的,大家可以看看LinearLayout和FrameLayout重写的该方法
通过在xml中生成的view,需要重写两个参数的构造方法

相关文章

网友评论

    本文标题:LayoutInflater背后隐藏的一些东西

    本文链接:https://www.haomeiwen.com/subject/icdorctx.html