Android LayoutInflater源码解读

这个类主要用途就是将布局文件转化成view,通常情况下的调用方式就是LayoutInflater.from(this).inflate(R.layout.test_1,parentViewGroup,false);,且setContentView方法就是通过这个方式来设置布局的。虽然网络上已经有了很多源码解读的文章,但是我还是写了本篇文章,就是想要加深自己的理解和印象,只有在你真正一行一行的源码读下去,才能发现自己的很多不足,下面我们就根据上面这句代码的调用执行顺序来看一下这个类的相关代码


    protected LayoutInflater(Context context) {
        mContext = context;
    }

    /**
     * Create a new LayoutInflater instance that is a copy of an existing
     * LayoutInflater, optionally with its Context changed.  For use in
     * implementing {@link #cloneInContext}.
     *
     * @param original The original LayoutInflater to copy.
     * @param newContext The new Context to use.
     */
    protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
    }

    /**
     * Obtains the LayoutInflater from the given context.
	 * 这个类的初始化方法是我们没有办法直接调用的,但LayoutInflater对外提供了 LayoutInflater.from(context)方法来获取LayoutInflater的实例化对象,而这个方法内部是调用
	 * context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来获取对象的,这个方法实际上初始化的对象是LayoutInflater的子类PhoneLayoutInflater对象,这个暂且不提,继续往下看
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
	

    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();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);//根据传入的布局的id生成xml解析器
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    /**
     * inflate的所有重载的方法都会调用这个最终的方法
	 *
     * @param parser xml解析器
     * @param root 是即将把解析出来的view添加到ViewGroup中的ViewGroup对象,如果attachToRoot是true就会将生成的view添加到root中并且返回root 
     * @param attachToRoot 是否将xml解析出来的view添加到root中去
     * @return 如果attachToRoot是true返回root,为false返回解析出来的root对象
     */
    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;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {//跳过xml开头
                    // Empty
                }

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

                final String name = parser.getName();//获取顶部View的name

                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) {//如果是merge标签,root一定不为空且attachToRoot必须为true否则就会抛出异常
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
					
                    //循环添加View
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //创建根视图
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {//此处如果root不为空程序就会设置根视图的setLayoutParams,否则根视图的LayoutParams就是全部是默认的参数
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        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);
                        }
                    }

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

                    //循环解析添加到根视图temp中
                    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中去
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) { //如果没有将创建的根视图添加到父布局root中就返回给result重新赋值返回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(parser.getPositionDescription()
                        + ": " + 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;
        }
    }

	


    /**
     *  递归添加子控件
     */
    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 ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {//循环读取

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

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {//requestFocus标签用于请求焦点
                pendingRequestFocus = true;
                consumeChildElements(parser);//next
            } else if (TAG_TAG.equals(name)) {//tag标签,用于给view添加多个tag
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {//include标签
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {//merge标签必须在使用在根布局
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);//创建view
                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();//前面的requestFocus,请求焦点
        }

        if (finishInflate) {
            parent.onFinishInflate();//viewGroup中所有子view都被从xml解析成view之后就会调用
        }
    }


   /**
	* 处理include标签
    */
    private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;
		
		//父布局只能是viewGroup
        if (parent instanceof ViewGroup) {

		    //include的主题覆盖父控件的主题
            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();

            // 提取layout
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
            if (layout == 0) {
                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 = context.getResources().getIdentifier(
                        value.substring(1), "attr", context.getPackageName());

            }

            // The layout might be referencing a theme attribute.
            if (mTempValue == null) {
                mTempValue = new TypedValue();
            }
            if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
                layout = mTempValue.resourceId;
            }

            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 {
				//下面的内容基本同inflate方法类似,所以不多解释
                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();

                    if (TAG_MERGE.equals(childName)) {
                        // The <merge> tag doesn't support android:theme, so
                        // nothing special to do here.
                        rInflate(childParser, parent, context, childAttrs, false);
                    } else {
                        final View view = createViewFromTag(parent, childName,
                                context, childAttrs, hasThemeOverride);
                        final ViewGroup group = (ViewGroup) parent;

                        final TypedArray a = context.obtainStyledAttributes(
                                attrs, R.styleable.Include);
                        final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                        final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                        a.recycle();

                        // We try to load the layout params set in the <include /> tag.
                        // If the parent can't generate layout params (ex. missing width
                        // or height for the framework ViewGroups, though this is not
                        // necessarily true of all ViewGroups) then we expect it to throw
                        // a runtime exception.
                        // We catch this exception and set localParams accordingly: true
                        // means we successfully loaded layout params from the <include>
                        // tag, false means we need to rely on the included layout params.
                        ViewGroup.LayoutParams params = null;
                        try {
                            params = group.generateLayoutParams(attrs);
                        } catch (RuntimeException e) {
                            // Ignore, just fail over to child attrs.
                        }
                        if (params == null) {
                            params = group.generateLayoutParams(childAttrs);
                        }
                        view.setLayoutParams(params);

                        // Inflate all children.
                        rInflateChildren(childParser, view, childAttrs, true);

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

                        group.addView(view);
                    }
                } finally {
                    childParser.close();
                }
            }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }

        LayoutInflater.consumeChildElements(parser);//next
    }


    /**
     * Creates a view from a tag name using the supplied attribute set.
     * 根据传入的参数生成view
     *
     * @param parent 
     * @param name the name of the XML tag used to define the view
     * @param context the inflation context for the view, typically the
     *                {@code parent} or base layout inflater context
     * @param attrs the attribute set for the XML tag used to define the view
     * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
     *                        attribute (if set) for the view being inflated,
     *                        {@code false} otherwise
     */
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
	    //view标签从class属性中获取类名
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        //主题设置
        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();
        }

		//BlinkLayout 一个LayoutInflater中自带的View,继承自FrameLayout,效果是包裹的内容会闪烁
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
			//mFactory2 mFactory mPrivateFactory 都是外部设置用于创建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);
            }
			
			//如果mFactory2都没有设置就会执行自己的创建view逻辑
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {//用name中是否有字符.来判断是否是原生控件,
                        view = onCreateView(parent, name, attrs);//没有系统最终就会调用createView(name, "android.view.", attrs); 来加上前缀
                    } else {
                        view = createView(name, null, attrs);//有就不是系统原生控件,所以name不需要处理
                    }
                } 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 ie;

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

	
	/**
	 *真正生成view的方法
	 */
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
		//sConstructorMap 是一个静态的map集合,key是要生成的控件名称,value是该对象的构造器	
        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对象
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {//外部设置的过滤器
                    boolean allowed = mFilter.onLoadClass(clazz);//如果过滤器返回false
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);//抛出异常
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);//通过class,传入参数calss数组来获取对应的构造函数的构造器
                constructor.setAccessible(true);//构造器执行时不检查权限
                sConstructorMap.put(name, constructor);//缓存起来
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    //之前执行过的结果会被存储起来
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);//存起来,下次再遇到这个name就不会重复调用
                        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;

            final View view = constructor.newInstance(args);//调用view两参方法构造对象
            if (view instanceof ViewStub) {//如果是ViewStub
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));//克隆一个本类对象给viewStub
            }
            mConstructorArgs[0] = lastContext;
            return view;//返回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);
        }
    }

关于LayoutInflater主要代码就在上面,我并没有贴出来全部的代码,所以可以在Android studio 中结合源码以及我对代码的注释来看

下面就对上面的某些地方做一些解释:

requestFocus

这个标签的作用就是用于请求焦点的例如:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">


    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

     <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <requestFocus />
    </EditText>


</LinearLayout>

一般情况如果没有 标签进入页面焦点就会在第一个EditText上面,但是给第三个EditText加上requestFocus标签之后,进入页面时焦点就会在第三个EditText上,这个标签LinearLayout之类的控件也是可以使用的

tag

关于tag标签,我们都知道可以给view设置一个Object类型的tag,但其实我们还可以用类似map键值对的形式来设置tag

public void setTag(int key, final Object tag)

而这个方法在xml中的表现形式就是:

    <EditText
        android:id="@+id/edit_1"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:tag="@string/app_name">

        <tag
            android:id="@+id/a"
            android:value="@string/app_name" />
        <tag
            android:id="@+id/b"
            android:value="@string/app_name" />

    </EditText>

获取方式就是:

        EditText editText = findViewById(R.id.edit_1);
        Object object = editText.getTag(R.id.a);

这个tag可以设置多个,没有看这个源码之前我都不知道有这个东西-_-

BlinkLayout

这个控件也很简单,继承自FrameLayout

    <blink
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="文艺二B青年" />

    </blink>

效果:

Factory

这个的用途主要就是不通过系统的方法创建View而是在系统创建xml的时候把参数都给Factory,让Factory来创建view,如果Factory没有创建view则仍旧通过系统的方法来创建View
使用方法:

        LayoutInflater layoutInflater = LayoutInflater.from(this);
        layoutInflater.setFactory(new LayoutInflater.Factory() {
            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        });
        layoutInflater.setFactory2(new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                return null;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        });

Factory2是对Factory的扩展,且Factory和Factory2只能设置1个,再次设置就会抛出异常,因为系统activity执行super.onCreate(savedInstanceState);的时候就给LayoutInflater设置了Factory所以如果在super.onCreate之前就设置不会抛出异常,如果在这之后设置就会抛出异常。这个方法请慎用,使用了自己的Factory系统的Factory就无法设置,就会出现未知的影响。
可参考:https://blog.csdn.net/lmj623565791/article/details/51503977

在这里如果只需要给自己的单独的设置,可以使用下面这个方法:

    layoutInflater.cloneInContext(this);

用系统提供的来克隆出来一个然后再去使用,这个方法是在这里看到的

Filter

这个就是一个设置一个过滤器例如:

        LayoutInflater layoutInflater = LayoutInflater.from(this);
        layoutInflater.setFilter(new LayoutInflater.Filter() {
            @Override
            public boolean onLoadClass(Class clazz) {
                if (clazz == ImgLook.class) {
                    return false;
                }
                L.d(clazz.getName());
                return true;
            }
        });
        layoutInflater.inflate(R.layout.test_1, layout, true);

当onLoadClass返回值为false时inflate方法就会抛出异常,需要及时处理,这里需要注意的是,TextView,Button这类控件因为系统已经设置了Factory所以,不会走到判断过滤器的那一步

以上代码基于Android 8.1
相关参考链接:https://blog.csdn.net/l540675759/article/details/78080656

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值