Android源码分析(LayoutInflater.from(this).inflate(resId,null);源码解析)

本文深入解析 Android 中 LayoutInflater 的工作原理,包括其不同获取方式、inflate 方法的使用及内部实现细节。重点分析 merge 和 include 标签的处理过程,揭示如何减少布局层级和复用布局文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码基于安卓8.0分析结果

  • 在这篇文章Android源码分析(Activity.setContentView源码解析),分析得出,底层走的就是LayoutInflater.from(this),inflate(),如果inflate()传入的view的话,就调用两次LayoutInflater.from(this),inflate(),,如果是inflate()传入的resId(布局的id)的话,就调用三次LayoutInflater.from(this),inflate(),,具体请看上篇文章。
  • 怎么使用,一共有三种的方式,主要看下这种方式LayoutInflater inflater1 = getLayoutInflater();,其实调用的就是Activity的getLayoutInflater()方法。
  LayoutInflater inflater1 = getLayoutInflater();//调用Activity的getLayoutInflater()
 LayoutInflater inflater2 = LayoutInflater.from(this);
 LayoutInflater inflater3 = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
复制代码
  • Activity的getLayoutInflater()方法。
  /**
     * Convenience for calling
     * {@link android.view.Window#getLayoutInflater}.
     */
    @NonNull
    public LayoutInflater getLayoutInflater() {
        return getWindow().getLayoutInflater();
    }
复制代码
 public PhoneWindow(Context context) {
     super(context);
      mLayoutInflater = LayoutInflater.from(context);
   }
复制代码

LayoutInflater inflater2 = LayoutInflater.from(this);的实现的方式

 /**
     * Obtains the LayoutInflater from the given context.
     */
    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;
    }
复制代码
  • 以上三种方法,最终还是调用了context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取LayoutInflater实例对象。
  • inflate的方法
   public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }
复制代码
  • 到这里来inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot),个人的对这个方法的理解是:把布局文件填充成View,如果传入的root不为null,并且attachToRoottrue,就把view填充到root上,相反就不添加上来!
   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) + ")");
        }
        //这里通过底层的方法,得到一个XmlResourceParser对象
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
复制代码
  • inflate(parser, root, attachToRoot),特别关心这个方法
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            //底层的方法,不知道原理
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            // TODO: 2018/6/5  我没有搞明白 为啥整个值在这里做什么的, 在这个方法里面都没有使用
            // from传入的Context
            final Context inflaterContext = mContext;
            // 判断parser是否是AttributeSet,如果不是则用XmlPullAttributes去包装一下。
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            // 保存之前的Context
            Context lastContext = (Context) mConstructorArgs[0];
            // 赋值为传入的Context
            mConstructorArgs[0] = inflaterContext;
            // 默认返回的是传入的Parent
            View result = root;
            try {
                // Look for the root node.
                int type;// 迭代xml中的所有元素,挨个解析
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
               // //如果没找到有效的开始标签则抛出InflateException
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
                //// 如果根节点是“merge”标签
                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");
                    }
                    // 递归实例化root(也就是传入Parent)下所有的View
                    // 如果xml中的节点是merge节点,则调用rInflate()--方法  // 递归实例化根节点的子View
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    //通过View的父View,View的名称、attrs属性实例化View(内部调用onCreateView()和createView())。
                    // TODO: 2018/6/6  // 实例化根节点的View 
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

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

                    // Inflate all children under temp against its context.
                    // 递归实例化跟节点的子View
                    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); // TODO: 2018/6/6      返回父View
                    }
               
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    // TODO: 2018/6/6  父View是空或者不把填充的View添加到父View)
                    if (root == null || !attachToRoot) {
                        result = temp;  // TODO: 2018/6/6 返回根节点View 
                    }
                }

            } 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.
                //不要在上下文中保留静态引用。
                // 把这之前保存的Context从新放回全局变量中。
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }
复制代码
  • 注意其中有个判断TAG_MERGE.equals(name)) ,这里就是merge标签的开始,以下的问题,都带着这个疑问往下面看。
   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");
                    }
                    // 递归实例化root(也就是传入Parent)下所有的View
                    // 如果xml中的节点是merge节点,则调用rInflate()--方法  // 递归实例化根节点的子View
                    rInflate(parser, root, inflaterContext, attrs, false);
  }
复制代码
  • 解析到merge标签的话,就会进入到这个方法rInflate(parser, root, inflaterContext, attrs, false);:递归实例化根节点的子View,在这里说明一点的是这个方法传入的root会不会为null.这篇文章说过Android源码分析(Activity.setContentView源码解析),肯定不会为null,一般是我们自己去使用的时候,有可能为null,具体的差别可以参考这篇文章Android LayoutInflater深度解析 给你带来全新的认识

  • 关于rInflate(parser, root, inflaterContext, attrs, false);方法:递归方法用于降序XML层次结构并实例化视图,实例化它们的子节点,然后调用onFinishInflate(),, 完成填充

 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;
        // 迭代xml中的所有元素,挨个解析
        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();
            //1、解析请求焦点 ,这个控件一直有焦点 requestFocus
            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)) {// 如果xml中的节点是include节点,则调用parseInclude方法
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                //解析include标签
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                // TODO: 2018/5/23 merge  作为布局里面的元素 会报错的哦 ,注意哦
                //想象下 两个merge 标签重合在一起
                throw new InflateException("<merge /> must be the root element");
            } else {
                // TODO: 2018/5/23 其实就是如果是merge标签,
                // TODO: 2018/5/23  那么直接将其中的子元素添加到merge标签parent中,这样就保证了不会引入额外的层级。
                // 1、我们的例子会进入这里  // 通过View的名称实例化View
                final View view = createViewFromTag(parent, name, context, attrs);
                // 2、获取merge标签的parent
                final ViewGroup viewGroup = (ViewGroup) parent;
                // 3、获取布局参数
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                // 4、递归解析每个子元素
                rInflateChildren(parser, view, attrs, true);
                // 5、将子元素直接添加到merge标签的parent view中
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }
        /**
         * onFinishInflate() View 中的一个空实现,标记完全填充完成了
         * The method onFinishInflate() will be called after all children have been added.
         * 以后是所有的孩子调用完成了,之后就调用这个方法
         */
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

复制代码
  • 通过对上面的方法分析,还打了断点,打断点注意一点,用模拟器,基本上手机厂商都改了frameWork层的代码,打出来是乱的。代码会走到这里来,看代码的分析,就把新的View,通过ViewGroup.addView()进去了,这也是merge便签的原理:merge标签可以自动消除当一个布局插入到另一个布局时产生的多余的View Group,也可用于替换FrameLayout
               // TODO: 2018/5/23 其实就是如果是merge标签,
                // TODO: 2018/5/23  那么直接将其中的子元素添加到merge标签parent中,这样就保证了不会引入额外的层级。
                // 1、我们的例子会进入这里  // 通过View的名称实例化View
                final View view = createViewFromTag(parent, name, context, attrs);
                // 2、获取merge标签的parent
                final ViewGroup viewGroup = (ViewGroup) parent;
                // 3、获取布局参数
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                // 4、递归解析每个子元素
                rInflateChildren(parser, view, attrs, true);
                // 5、将子元素直接添加到merge标签的parent view中
                viewGroup.addView(view, params);
复制代码
  • merge的原理我们清楚了,分析另外一条线,没有merge标签,就会走到else的方法来。
                // 如果根节点是“merge”标签
                if (TAG_MERGE.equals(name)) {
                   
                } else {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                             params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    // 递归实例化跟节点的子View start inflating children
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
                    if (root != null && attachToRoot) {
                        root.addView(temp, params); // TODO: 2018/6/6      返回父View
                    }
           
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    // TODO: 2018/6/6  父View是空或者不把填充的View添加到父View)
                    if (root == null || !attachToRoot) {
                        result = temp;  // TODO: 2018/6/6 返回根节点View 
                    }
复制代码
  • 1、这个方法createViewFromTag(View parent, String name, Context context, AttributeSet attrs)
 private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
复制代码
  • 最终调用的是这个方法createViewFromTag(parent, name, context, attrs, false);这个方法
  • ,什么时候会走到下面来,name.equals("view")成立的情况,我记得以前我为了在布局中加了一根线就是这样写的,写快了,写成小写的了view,应该写成大写View.
 <view
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
复制代码

小写的view:我自己打的断点发现,如果根标签是view的,注意是小写的,view,这个name会为null ,接着下面就会出现空指针的异常---> 我很聪明 很牛逼

 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
          //根据我的断点的话,只要进来的话,这个那么就会为null
            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();
        }
复制代码

name ==null,就会抛出ava.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference,也就是这个原因,所以写代码还是要仔细,仔细!!

在这里有个很有意思的布局,开发中很少使用到,而且也不清楚,作者为啥写,姑且这样理解Let's party like it's 1995! 写这个代码的哥们,在1995年经历了一件很开心的事情

        //如果有这个标签的话,就出现这个布局,闪动的布局
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995! 写这个代码的哥们,在1995年经历了一件很开心的事情
            return new BlinkLayout(context, attrs);
        }
复制代码

BlinkLayout使用的方式,blink 翻译过来会一闪一闪的

     <blink
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@android:color/holo_blue_bright"
                android:text="blink单词也是这个意思.闪烁频率500毫秒" />
        </blink>

复制代码

BlinkLayout代码如下, BLINK_DELAY 这个是闪动的时间间隔,如果开发中需要,改动这个时间就行了,感觉这个布局也没啥用!

public  class BlinkLayout extends FrameLayout {
    private static final int MESSAGE_BLINK = 0x42;
    private static final int BLINK_DELAY = 500;
    private boolean mBlink;
    private boolean mBlinkState;
    private final Handler mHandler;

    public BlinkLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO: 2018/5/22 对handler  源码比较熟悉的话,这里就是Handler另外的一种使用的方法
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if (msg.what == MESSAGE_BLINK) {
                    if (mBlink) {
                        mBlinkState = !mBlinkState;
                        makeBlink();
                    }
                    //invalidate()是用来刷新View的,必须是在UI线程中进行工作。
                    // 比如在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面
                    invalidate();
                    return true;
                }
                return false;
            }
        });
    }

    private void makeBlink() {
        Message message = mHandler.obtainMessage(MESSAGE_BLINK);
        mHandler.sendMessageDelayed(message, BLINK_DELAY);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        mBlink = true;
        mBlinkState = true;

        makeBlink();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        mBlink = false;
        mBlinkState = true;

        mHandler.removeMessages(MESSAGE_BLINK);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        //根据这个状态是否去dispatchDraw
        if (mBlinkState) {
            super.dispatchDraw(canvas);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

}
复制代码

下面是后续的代码,通过工厂创建了view。去定位过,在public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2,很多的地方,如果需要研究的,还需要更深层的源码理解,这里就先mark下,只需要知道view是通过工厂来得到的

        try {
            View view;
            if (mFactory2 != null) {
                //各个工厂先onCreateView()
                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);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } 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;
        }
    }
复制代码
  • 2、关于这个方法rInflateChildren(parser, temp, attrs, true);:递归实例化跟节点的子View.其实调用的还是这个方法rInflate(parser, root, inflaterContext, attrs, false);,文章的前部分贴出了这部分的代码
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
复制代码

主要是为了分析几个Tag

private static final String TAG_MERGE = "merge";
    private static final String TAG_INCLUDE = "include";
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";
    private static final String TAG_TAG = "tag";
    private static final String ATTR_LAYOUT = "layout";

复制代码

requestFocus:解析请求焦点 ,这个控件一直有焦点 requestFocus,一般作用于 EditText,需要长期获取焦点的控件,但是真正的实现的方法,我们都是代码去实现了,很少这样使用,这里就说明一下,有这样的使用的方法!

 if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
   } 
复制代码
       <EditText
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
            <!-- 当前控件处于焦点状态 -->
            <requestFocus />
        </EditText>
复制代码

tag:在包含的视图上设置键标记,其实就是给view设置上了一个tag标签

if (TAG_TAG.equals(name)) {
                //在包含的视图上设置键标记。
                parseViewTag(parser, parent, attrs);
    } 
 final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
   final CharSequence value = ta.getText(R.styleable.ViewTag_value);
   view.setTag(key, value);
复制代码

merge:在这里如果解析到了merge标签的话,这里就直接抛出异常了,必须是根元素!两种情况下,一种是套用了两个merge标签,或者是一个ViewGruop标签嵌套了一个merge,都会抛出异常

 if (TAG_MERGE.equals(name)) {
                // TODO: 2018/5/23 merge  作为布局里面的元素 会报错的哦 ,注意哦
                //想象下 两个merge 标签重合在一起
                throw new InflateException("<merge /> must be the root element");
  }
复制代码

include:标签,在这里如果include作为了一个根元素,也会抛出异常,当然,如果在代码中见到别人那么用的话,建议自己去面壁思过会!

if (TAG_INCLUDE.equals(name)) {// 如果xml中的节点是include节点,则调用parseInclude方法
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                //解析include标签
                parseInclude(parser, context, parent, attrs);
   }
复制代码

parseInclude(parser, context, parent, attrs);,这个方法也就是include必须走的方法,搞明白的话,就可以搞明白原理,而且在特殊的情况下,include标签会和 merge 标签一起使用。这个方法也可以解释include标签的原理!

  • 可以从这里可以看到,include只能在ViewGroup中使用,在 'View'中使用的话,会报错
   private void parseInclude(XmlPullParser parser, Context context, View parent,
            AttributeSet attrs) throws XmlPullParserException, IOException {
        int type;
   if (parent instanceof ViewGroup) {
            // Apply a theme wrapper, if requested. This is sort of a weird
            // edge case, since developers think the <include> overwrites
            // values in the AttributeSet of the included View. So, if the
            // included View has a theme attribute, we'll need to ignore it.
            //如果VIEW视图有一个主题属性,我们需要忽略它。
            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();

            //如果布局指向主题属性,我们必须
            //按摩该值以从中获取资源标识符。
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
            if (layout == 0) {
                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
                // TODO: 2018/6/6 include标签中没有设置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 {
                final XmlResourceParser childParser = context.getResources().getLayout(layout);

                try {// 获取属性集,即在include标签中设置的属性
                    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!");
                    }
                   // 1、解析include中的第一个元素
                    final String childName = childParser.getName();
                   // 如果第一个元素是merge标签,那么调用rInflate函数解析
                    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 {
                        //2、我们例子中的情况会走到这一步,首先根据include的属性集创建被include进来的xml布局的根view
                        // 这里的根view对应为my_title_layout.xml中的RelativeLayout
                        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 ,如果include设置了这个id ,那么这个id就不等于 View.No_ID=-1
                        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 {
                            // 获3、取布局属性
                            params = group.generateLayoutParams(attrs);
                        } catch (RuntimeException e) {
                            // Ignore, just fail over to child attrs.
                        }
                        // 被inlcude进来的根view设置布局参数
                        if (params == null) {
                            params = group.generateLayoutParams(childAttrs);
                        }
                        view.setLayoutParams(params);
                         // 4、Inflate all children. 解析所有子控件
                        // Inflate all children.
                        rInflateChildren(childParser, view, childAttrs, true);

                        if (id != View.NO_ID) {
                         // 5、将include中设置的id设置给根view,因此实际上my_title_layout.xml中
                          // 的RelativeLayout的id会变成include标签中的id,include不设置id,那么也可以通过relative的找到.
                            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;
                        }
                        // 6、将根view添加到父控件中
                        group.addView(view);
                    }
                } finally {
                    childParser.close();
                }
            }
        } else {
            throw new InflateException("<include /> can only be used inside of a ViewGroup");
        }
        //默认的可见性使得LayoutInflater_Delegate代表可以调用它。
        LayoutInflater.consumeChildElements(parser);
复制代码
  • 分段分析上面的那段代码, 1、如果VIEW视图有一个主题属性,我们需要忽略它。
            //如果VIEW视图有一个主题属性,我们需要忽略它。
            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();
复制代码

2、如果布局指向主题属性,我们必须从该值以从中获取资源标识符!

            //如果布局指向主题属性,我们必须
            //按摩该值以从中获取资源标识符。
            int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
复制代码

3、在这里出现value == null || value.length() <= 0的话,就会抛出异常,也就是说include标签中没有设置layout属性,会抛出异常。

            if (layout == 0) {
                final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
                // TODO: 2018/6/6 include标签中没有设置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());

            }
复制代码

3、解析include中的第一个元素,如果这里第一个childeNamemerge,那么就是 includemerge 一起使用的情况,调用的方法是rInflate(childParser, parent, context, childAttrs, false);和上面一样,不会生成多余的层级

                   // 1、解析include中的第一个元素
                    final String childName = childParser.getName();
                   // 如果第一个元素是merge标签,那么调用rInflate函数解析
                    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);
                    } 
复制代码

4、首先根据include的属性集创建被include进来的xml布局的根view,这个view其实就include 进来的根ViewGroup,因为在前面已经把merge的情况过滤掉了,

                     else {
                        //2、我们例子中的情况会走到这一步,首先根据include的属性集创建被include进来的xml布局的根view
                        final View view = createViewFromTag(parent, childName,
                                context, childAttrs, hasThemeOverride);
复制代码

5、特别关心这个final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);函数,其实就是给include标签设是否设置了id,就好像下面的布局一样,如果设置了这个android:id="@+id/my_title_ly"

    <include
        android:id="@+id/my_title_ly"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        layout="@layout/my_include_layout" />
复制代码

这个是上面得include的布局my_include_layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:id="@+id/my_title_parent_id"
    android:layout_height="wrap_content" >
   ....
</RelativeLayout>
复制代码

下面的这段代码就会执行,这也就解释了:安卓include标签的找不到id的问题,因为你在include设置了这个id属性,那么你找这个my_title_parent_id这个id,findViewById(R.id.my_title_parent_id)肯定也会找出来为null!

                        final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
                        if (id != View.NO_ID) {
                         // 5、将include中设置的id设置给根view,因此实际上my_title_layout.xml中
                          // 的RelativeLayout的id会变成include标签中的id,include不设置id,那么也可以通过relative的找到.
                            view.setId(id);
                        }

}
复制代码

为什么findViewById(R.id.my_title_parent_id)会找出来为null,简单叙述下就是在Activity中调用方法其实是WindowfindViewById,getDecorView().findViewById(id),也就是PhoneWindow里面的DecorViewfindViewById,也就是ViewfindViewById;可以看这边文章Android源码分析(事件传递)涉及到这几个类了。如下面的方法,这个View 有id,找id的话就会走findViewTraversal这个方法,找的id和setId不一样的话,就会返回null,这就是找不到 id的原因。这个原因和ViewStub标签中设置了inflatedId一样的效果,下篇文章会讲到,非常有意思!已经写好Android源码分析(ViewStub源码解析)

    public final <T extends View> T findViewById(@IdRes int id) {
       if (id == NO_ID) {
            return null;
      }
       return findViewTraversal(id);
    }
   protected <T extends View> T findViewTraversal(@IdRes int id) {
      if (id == mID) {
           return (T) this;
      }
      return null;
    }
复制代码

View中设置id的方法

    public void setId(@IdRes int id) {
      mID = id;
       if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
            mID = generateViewId();
       }
   }
复制代码

6、关于final int visibility = a.getInt(R.styleable.Include_visibility, -1);这行代码,也就是说我们设置了include标签'visibility'其实也就是设置layoutvisibility的属性!

  final int visibility = a.getInt(R.styleable.Include_visibility, -1);
    switch (visibility) {
                            case 0:
                                view.setVisibility(View.VISIBLE);
                                break;
                            case 1:
                                view.setVisibility(View.INVISIBLE);
                                break;
                            case 2:
                                view.setVisibility(View.GONE);
                                break;
                        }
复制代码

7、将根view添加到父控件中,最后都会走到这里来,只不过使用了merge是不会产生多余的层级的,但是使用的时候有场景的要求!

        group.addView(view);
复制代码
  • 最后做了一张图,谢谢

  • 说明几点

    • 布局转化为View,关键方法就是LayoutInflater.from(this),inflate()
    • PhoneWindow这个类很关键
    • 如果需要打断点,建议使用模拟器,手机的厂商基本上改动过framework的代码,打出来不准确
    • merge原理就是通过addView(view),从而减少了层级
    • BlinkLayout这个是LayoutInflater的内部类,感觉是谷歌工程师皮了一下
  • include只能在ViewGroup中使用,在 'View'结点中使用的话,会报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值