美文网首页Android知识Android技术知识Android开发经验谈
Android 深入理解LayoutInflater工作机制

Android 深入理解LayoutInflater工作机制

作者: 草丛伦 | 来源:发表于2017-06-14 14:18 被阅读272次

    Android里面有很多场景会用到LayoutInflate这个类,我们通过这个类去解析指定的布局,然后展示在布局里面。api的调用是如此的简单,我们如果每次都是单纯的调用,那就无法得到提升了,所以现在我们来看一下这个流程究竟是怎么一回事。
    先来看下用法跟场景:

    @Override
        public AllPavilionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            //在recyclerview/listview的适配器中 构建每个item的布局
            return new AllPavilionViewHolder(inflater.inflate(R.layout.item_location_pavilion, parent, false));
        }
    
     public AmountView(Context context, AttributeSet attrs) {
            super(context, attrs);
            //自定义UI中 解析指定布局去依附在自定义view中
            LayoutInflater.from(context).inflate(R.layout.ui_amount, this);
        }
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //观察源码 其实这个setContentView 内部也是调用Layoutflate.inflate的方式来构建view的
            setContentView(R.layout.activity_reserve);
        }
    

    可能还有一些其他场景会用到这个方法,这些就不一一举例了,我们直接来通过源码分析这块的流程吧。

    
       /**
         * Obtains the LayoutInflater from the given context.
         * //首先是初始化 可以看出我们常用的LayoutInflater其实是对如下方法的简单封装,意味着我们其实有两种方式来构建出LayoutInflater的实例
         */
        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;//为什么变量名字大写开头 我也不知道
        }
    

    //拿到我们想要的实例之后,之后就是调用inflate方法去构建view了。

      //四种inflate方法 到最后还是殊途同归
     public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            //里面调用的还是下面那个方法 只是做了一点判断而已
            return inflate(resource, root, root != null);
        }
      //说一下参数 
      //resource代表需要解析的xml文件
      //root 表示这个xml文件外围包裹的父布局,如果不需要 直接传null便可 
      //attachToRoot 是否需要布局文件依附在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);//得到一个xml解析器
            try {
                return inflate(parser, root, attachToRoot);//这个方法才是真正的开始解析布局文件
            } finally {
                parser.close();//解析器用完 关掉
            }
        }
    
      //重点来了 
     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) {//通过循环 找到开始节点 或者结束节点
                        // Empty
                    }
    
                    if (type != XmlPullParser.START_TAG) {//没找到开始节点  直接抛异常
                        throw new InflateException(parser.getPositionDescription()
                                + ": No start tag found!");
                    }
                    //开始构建 root view
                    final String name = parser.getName();
                    if (TAG_MERGE.equals(name)) {
                      //根布局为merge版
                        if (root == null || !attachToRoot) {
                        //判断根布局是不是merge 如果是的话 需要有父布局并且attachToRoot为true 否则抛出异常
                            throw new InflateException("<merge /> can be used only with a valid "
                                    + "ViewGroup root and attachToRoot=true");
                        }
    
                        rInflate(parser, root, inflaterContext, attrs, false);//这里开始循环解析
                    } else {
                           //根布局为非merge版
                        // Temp is the root view that was found in the xml
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);//解析构造整个布局实例
    
                        ViewGroup.LayoutParams params = null;
    
                        if (root != null) {//如果root 不为null 然后attachToRoot 为false
                        //只有在这种情况下 他的属性才会被真正设置进去 否则无效 root!=null&&attachToRoot==false (会影响宽高跟margin  ,关于padding 毕竟是作用在view的onDraw方法里面的,还是有效果的)
                            // 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);
                            }
                        }
    
                        //开始解析布局文件中 子view 循环解析所有子view
                        // Inflate all children under temp against its context.
                        rInflateChildren(parser, temp, attrs, true);
                        // 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);
                        }
    
                        // Decide whether to return the root that was passed in or the
                        // top view found in xml.
                        if (root == null || !attachToRoot) {//判断是返回刚构建的view 还是之前传进来的root
                            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;
            }
        }
    

    我们先看createViewFromTag方法

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
            return createViewFromTag(parent, name, context, attrs, false);
        }
    //这里才是主导
    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) {//默认ignoreThemeAttr 给的是false
                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();//TypedArray用完都是要回收的
            }
    
            if (name.equals(TAG_1995)) {
                // Let's party like it's 1995!
                return new BlinkLayout(context, attrs);//一种闪烁的布局 
              //想要看效果 可以看http://blog.csdn.net/qq_22644219/article/details/69367150
            }
    
            try {
                View 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);
                }
    
                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);//主体还是这个方法 通过反射的方式创建了view
                        }
                    } 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创建好了之后,之后会调用 rInflate(parser, root, inflaterContext, attrs, false)一步步的实现每一个子view

     /**
         * Recursive method used to descend down the xml hierarchy and instantiate
         * views, instantiate their children, and then call onFinishInflate().
         * 递归的调用方法构建整个xml的布局,沿层次一个个的实例化view
         */
        void rInflate(XmlPullParser parser, View parent, Context context,
                AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
            final int depth = parser.getDepth();//获取当前布局的深度
            int type;
    
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {//循环拿到那个start_tag
                if (type != XmlPullParser.START_TAG) {
                    continue;
                }
    
                final String name = parser.getName();
                //对一些特殊的节点做处理
                if (TAG_REQUEST_FOCUS.equals(name)) {
                    parseRequestFocus(parser, parent);
                } else if (TAG_TAG.equals(name)) {
                    parseViewTag(parser, parent, attrs);
                } else if (TAG_INCLUDE.equals(name)) {
                    if (parser.getDepth() == 0) {
                        throw new InflateException("<include /> cannot be the root element");
                    }
                    parseInclude(parser, context, parent, attrs);//这里做include内容的解析
                } else if (TAG_MERGE.equals(name)) {//意思是 merge 不能作为一个子view 除非他是根布局
                    throw new InflateException("<merge /> must be the root element");
                } else {
                  //这里做真正的创建view 还是通过反射的形式 另外把一些属性设置进去 再添加到父view中
                    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 都被构建成功 
            if (finishInflate) {//当根布局不是merge 那就为true
                parent.onFinishInflate();//就是一个回调吧
            }
        }
    

    整个流程就是这样子 ,我们再来看一下,我在最初的时候说 setContentView也是通过LayoutInflate方法的:

     @Override
        public void setContentView(@LayoutRes int layoutResID) {//我们在activity里面的调用 就是这个方法
            getDelegate().setContentView(layoutResID);
        }
    
    //这是一个抽象的方法
        public abstract void setContentView(@LayoutRes int resId);
    
    //然后我们看AppCompatDelegateImplV9的实现
     @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            LayoutInflater.from(mContext).inflate(resId, contentParent);//最后 还是调用到这个方法了
            mOriginalWindowCallback.onContentChanged();
        }
    

    总结一下LayoutInflate里面的实现,其实也是很简单的用了android提供的pull解析一步步的解析下来的,里面的每一个节点就构建成一个view了(通过反射),从根布局开始一层层的解析构建,最终形成一个完整的DOM结构,然后把根布局的引用传出去,这样inflate方法就成功完成了。

    最后说一下三个inflate的三个参数作用,毕竟有时候会疑惑该如何传参:

    1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
    2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root,此时是root设置的那些宽高跟margin ,是没有效果的。
    3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。
    4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。

    还有一点,请不要在listview\recyclerview里面构建每个item的时候设置root!=null 并且attachToRoot又给了true ,这样会直接报错的,因为

    @Override
      public void addView(View child) {
            throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
      }
    

    参考:

    Android LayoutInflater原理分析,带你一步步深入了解View(一)
    Android LayoutInflate深度解析 给你带来全新的认识

    相关文章

      网友评论

        本文标题:Android 深入理解LayoutInflater工作机制

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